From 185693c0b21c2492fae6d6930c12b3e40a626eb6 Mon Sep 17 00:00:00 2001 From: damnPat Date: Mon, 3 Aug 2026 07:40:45 +0800 Subject: [PATCH 1/2] feat: port contentforge/desktop latest changes from main (266552a) Port commit 266552a 'update latest codes' from the flat layout into the monorepo structure: - packages/contentforge-core: pipeline engine/presets/runner and processing (analyzer/summarizer/translator/ai_engine/xiaohongshu) updates - apps/contentforge-desktop/src-tauri: major Rust backend refactor (commands/ and db/ module dirs replace commands.rs/database.rs, new agent_cli/agent_runner/audio_extractor/downloader/queue/vtt_analysis, app icons, generated Tauri schemas, REBUILD_PLAN.md) - contentforge/docs: spec/*, vizplainer-slides/, EXTERNAL_REPO_INTEGRATION_PLAN - contentforge/external-repos: analysis docs + upstream repo gitlinks - docs/how-to/desktop-app.md, tasks/refactor + tasks/tracing entries - apps/vytdl-desktop/Taskfile.yml replaces build-desktop.py / start-desktop.* launchers; root Taskfile and package.json desktop:* delegate to it - scripts/download-yt-dlp-binaries.py: fix resources path to apps/vytdl-desktop - .gitignore: add .task/, downloadeds/*, tools/vytdl-cli/bin/*.zip; untrack .task cache; drop stale vYtDL-desktop/ leftover files Skipped (runtime/build output, covered by .gitignore): contentforge Go binary, scrape/download JSON records. Kept monorepo's new setup-macos.sh. --- .gitignore | 3 + .task/checksum/cli-build | 1 - .task/checksum/contentforge-build | 1 - .vscode-desktop/tasks.json | 4 +- AGENTS.md | 11 +- Taskfile.yml | 30 +- .../contentforge-desktop/src-tauri/Cargo.toml | 2 +- .../src-tauri/REBUILD_PLAN.md | 31 + .../src-tauri/gen/schemas/acl-manifests.json | 1 + .../src-tauri/gen/schemas/capabilities.json | 1 + .../src-tauri/gen/schemas/desktop-schema.json | 6446 +++++++++++++++++ .../src-tauri/gen/schemas/macOS-schema.json | 6446 +++++++++++++++++ .../src-tauri/icons/128x128.png | Bin 0 -> 1025 bytes .../src-tauri/icons/128x128@2x.png | Bin 0 -> 2106 bytes .../src-tauri/icons/32x32.png | Bin 0 -> 332 bytes .../src-tauri/icons/README.md | 13 + .../src-tauri/icons/icon.icns | Bin 0 -> 61637 bytes .../src-tauri/icons/icon.ico | Bin 0 -> 5009 bytes .../src-tauri/src/agent_cli.rs | 497 ++ .../src-tauri/src/agent_runner.rs | 198 + .../src-tauri/src/audio_extractor.rs | 190 + .../src-tauri/src/commands.rs | 195 - .../src-tauri/src/commands/agent.rs | 257 + .../src-tauri/src/commands/ai.rs | 168 + .../src-tauri/src/commands/asset.rs | 272 + .../src-tauri/src/commands/chat.rs | 489 ++ .../src-tauri/src/commands/download.rs | 226 + .../src-tauri/src/commands/mod.rs | 43 + .../src-tauri/src/commands/settings.rs | 187 + .../src-tauri/src/commands/video.rs | 53 + .../src-tauri/src/database.rs | 137 - .../src-tauri/src/db/agent_switches.rs | 34 + .../src-tauri/src/db/assets.rs | 166 + .../src-tauri/src/db/downloads.rs | 399 + .../src-tauri/src/db/messages.rs | 94 + .../src-tauri/src/db/mod.rs | 266 + .../src-tauri/src/db/pipeline_runs.rs | 68 + .../src-tauri/src/db/sessions.rs | 107 + .../src-tauri/src/db/settings.rs | 39 + .../src-tauri/src/db/types.rs | 166 + .../src-tauri/src/downloader.rs | 717 ++ .../contentforge-desktop/src-tauri/src/lib.rs | 221 +- .../src-tauri/src/queue.rs | 273 + .../src-tauri/src/vtt_analysis.rs | 280 + apps/vytdl-desktop/Taskfile.yml | 164 + .../docs/EXTERNAL_REPO_INTEGRATION_PLAN.md | 390 + contentforge/docs/spec/CLI_SPEC.md | 442 ++ contentforge/docs/spec/FRONTEND_SPEC.md | 695 ++ .../spec/MULTI_AGENT_ARCHITECTURE_PLAN.md | 673 ++ contentforge/docs/spec/PROJECT_OVERVIEW.md | 422 ++ contentforge/docs/spec/PYTHON_CORE_SPEC.md | 1690 +++++ contentforge/docs/spec/RUST_BACKEND_SPEC.md | 595 ++ .../docs/spec/VIZPLAINER_INTEGRATION.md | 314 + contentforge/docs/vizplainer-slides/design.md | 100 + .../docs/vizplainer-slides/outline.md | 49 + .../vizplainer-slides/pages/bullbear.page | 171 + .../docs/vizplainer-slides/pages/ch1.page | 40 + .../docs/vizplainer-slides/pages/ch2.page | 40 + .../docs/vizplainer-slides/pages/ch3.page | 40 + .../docs/vizplainer-slides/pages/cover.page | 81 + .../docs/vizplainer-slides/pages/final.page | 94 + .../vizplainer-slides/pages/insight1.page | 192 + .../vizplainer-slides/pages/insight2.page | 204 + .../vizplainer-slides/pages/insight3.page | 210 + .../docs/vizplainer-slides/pages/metrics.page | 216 + .../vizplainer-slides/pages/takeaway.page | 173 + .../docs/vizplainer-slides/pages/toc.page | 162 + .../docs/vizplainer-slides/slides.pptd | 63 + .../docs/vizplainer-slides/slides.pptx | Bin 0 -> 74628 bytes contentforge/external-repos/OpenMontage | 1 + .../external-repos/OpenMontage_ANALYSIS.md | 397 + .../external-repos/Video-Note-Extractor | 1 + .../Video-Note-Extractor_ANALYSIS.md | 193 + contentforge/external-repos/capsummarize | 1 + .../external-repos/capsummarize_ANALYSIS.md | 328 + contentforge/external-repos/frameflow | 1 + .../external-repos/frameflow_ANALYSIS.md | 335 + contentforge/external-repos/skill-studio | 1 + .../external-repos/skill-studio_ANALYSIS.md | 351 + contentforge/external-repos/skill-zoo | 1 + .../external-repos/skill-zoo_ANALYSIS.md | 269 + .../external-repos/youtube-rag-system | 1 + .../youtube-rag-system_ANALYSIS.md | 295 + docs/how-to/desktop-app.md | 254 + package.json | 6 +- .../python/contentforge/pipeline/engine.py | 27 + .../python/contentforge/pipeline/presets.py | 9 + .../python/contentforge/pipeline/runner.py | 3 + .../contentforge/processing/ai_engine.py | 2 +- .../contentforge/processing/analyzer.py | 10 +- .../contentforge/processing/summarizer.py | 6 +- .../contentforge/processing/translator.py | 5 +- .../processing/xiaohongshu_converter.py | 6 +- scripts/build-desktop.py | 404 -- scripts/download-yt-dlp-binaries.py | 2 +- scripts/start-desktop.ps1 | 19 - scripts/start-desktop.py | 76 - scripts/start-desktop.sh | 19 - scripts/vytdl-launcher.py | 337 - tasks/refactor/desktop/desktop.md | 6 + .../issue-1.md | 20 + vYtDL-desktop/package.json | 22 - vYtDL-desktop/pnpm-workspace.yaml | 3 - 103 files changed, 28098 insertions(+), 1265 deletions(-) delete mode 100644 .task/checksum/cli-build delete mode 100644 .task/checksum/contentforge-build create mode 100644 apps/contentforge-desktop/src-tauri/REBUILD_PLAN.md create mode 100644 apps/contentforge-desktop/src-tauri/gen/schemas/acl-manifests.json create mode 100644 apps/contentforge-desktop/src-tauri/gen/schemas/capabilities.json create mode 100644 apps/contentforge-desktop/src-tauri/gen/schemas/desktop-schema.json create mode 100644 apps/contentforge-desktop/src-tauri/gen/schemas/macOS-schema.json create mode 100644 apps/contentforge-desktop/src-tauri/icons/128x128.png create mode 100644 apps/contentforge-desktop/src-tauri/icons/128x128@2x.png create mode 100644 apps/contentforge-desktop/src-tauri/icons/32x32.png create mode 100644 apps/contentforge-desktop/src-tauri/icons/README.md create mode 100644 apps/contentforge-desktop/src-tauri/icons/icon.icns create mode 100644 apps/contentforge-desktop/src-tauri/icons/icon.ico create mode 100644 apps/contentforge-desktop/src-tauri/src/agent_cli.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/agent_runner.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/audio_extractor.rs delete mode 100644 apps/contentforge-desktop/src-tauri/src/commands.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/agent.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/ai.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/asset.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/chat.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/download.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/mod.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/settings.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/commands/video.rs delete mode 100644 apps/contentforge-desktop/src-tauri/src/database.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/agent_switches.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/assets.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/downloads.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/messages.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/mod.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/pipeline_runs.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/sessions.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/settings.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/db/types.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/downloader.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/queue.rs create mode 100644 apps/contentforge-desktop/src-tauri/src/vtt_analysis.rs create mode 100644 apps/vytdl-desktop/Taskfile.yml create mode 100644 contentforge/docs/EXTERNAL_REPO_INTEGRATION_PLAN.md create mode 100644 contentforge/docs/spec/CLI_SPEC.md create mode 100644 contentforge/docs/spec/FRONTEND_SPEC.md create mode 100644 contentforge/docs/spec/MULTI_AGENT_ARCHITECTURE_PLAN.md create mode 100644 contentforge/docs/spec/PROJECT_OVERVIEW.md create mode 100644 contentforge/docs/spec/PYTHON_CORE_SPEC.md create mode 100644 contentforge/docs/spec/RUST_BACKEND_SPEC.md create mode 100644 contentforge/docs/spec/VIZPLAINER_INTEGRATION.md create mode 100644 contentforge/docs/vizplainer-slides/design.md create mode 100644 contentforge/docs/vizplainer-slides/outline.md create mode 100644 contentforge/docs/vizplainer-slides/pages/bullbear.page create mode 100644 contentforge/docs/vizplainer-slides/pages/ch1.page create mode 100644 contentforge/docs/vizplainer-slides/pages/ch2.page create mode 100644 contentforge/docs/vizplainer-slides/pages/ch3.page create mode 100644 contentforge/docs/vizplainer-slides/pages/cover.page create mode 100644 contentforge/docs/vizplainer-slides/pages/final.page create mode 100644 contentforge/docs/vizplainer-slides/pages/insight1.page create mode 100644 contentforge/docs/vizplainer-slides/pages/insight2.page create mode 100644 contentforge/docs/vizplainer-slides/pages/insight3.page create mode 100644 contentforge/docs/vizplainer-slides/pages/metrics.page create mode 100644 contentforge/docs/vizplainer-slides/pages/takeaway.page create mode 100644 contentforge/docs/vizplainer-slides/pages/toc.page create mode 100644 contentforge/docs/vizplainer-slides/slides.pptd create mode 100644 contentforge/docs/vizplainer-slides/slides.pptx create mode 160000 contentforge/external-repos/OpenMontage create mode 100644 contentforge/external-repos/OpenMontage_ANALYSIS.md create mode 160000 contentforge/external-repos/Video-Note-Extractor create mode 100644 contentforge/external-repos/Video-Note-Extractor_ANALYSIS.md create mode 160000 contentforge/external-repos/capsummarize create mode 100644 contentforge/external-repos/capsummarize_ANALYSIS.md create mode 160000 contentforge/external-repos/frameflow create mode 100644 contentforge/external-repos/frameflow_ANALYSIS.md create mode 160000 contentforge/external-repos/skill-studio create mode 100644 contentforge/external-repos/skill-studio_ANALYSIS.md create mode 160000 contentforge/external-repos/skill-zoo create mode 100644 contentforge/external-repos/skill-zoo_ANALYSIS.md create mode 160000 contentforge/external-repos/youtube-rag-system create mode 100644 contentforge/external-repos/youtube-rag-system_ANALYSIS.md create mode 100644 docs/how-to/desktop-app.md delete mode 100755 scripts/build-desktop.py delete mode 100644 scripts/start-desktop.ps1 delete mode 100644 scripts/start-desktop.py delete mode 100755 scripts/start-desktop.sh delete mode 100755 scripts/vytdl-launcher.py create mode 100644 tasks/refactor/desktop/desktop.md create mode 100644 tasks/tracing/Monorepo-refactor-unify-all-projects-into-single-monorepo/issue-1.md delete mode 100644 vYtDL-desktop/package.json delete mode 100644 vYtDL-desktop/pnpm-workspace.yaml diff --git a/.gitignore b/.gitignore index 950ecd9..1119e8f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Thumbs.db # ── Build artifacts ─────────────────────────────────────── tools/vytdl-cli/vYtDL +tools/vytdl-cli/bin/*.zip tools/contentforge-cli/contentforge *.exe *.mp4 @@ -20,6 +21,8 @@ dist/ .next/ .turbo/ *.tsbuildinfo +.task/ +downloadeds/* # ── Tauri / Rust ────────────────────────────────────────── **/src-tauri/target/ diff --git a/.task/checksum/cli-build b/.task/checksum/cli-build deleted file mode 100644 index 10e3625..0000000 --- a/.task/checksum/cli-build +++ /dev/null @@ -1 +0,0 @@ -d2337e7d9dba63423766fa0bf8a82e7 diff --git a/.task/checksum/contentforge-build b/.task/checksum/contentforge-build deleted file mode 100644 index e983141..0000000 --- a/.task/checksum/contentforge-build +++ /dev/null @@ -1 +0,0 @@ -40965df0dbc54ec6b3d40cdd8a1d92a3 diff --git a/.vscode-desktop/tasks.json b/.vscode-desktop/tasks.json index 6c5732a..e889689 100644 --- a/.vscode-desktop/tasks.json +++ b/.vscode-desktop/tasks.json @@ -50,8 +50,8 @@ { "label": "desktop:check", "type": "shell", - "command": "python3", - "args": ["scripts/build-desktop.py", "check"], + "command": "task", + "args": ["-d", "apps/vytdl-desktop", "check"], "options": { "cwd": "${workspaceFolder}" } } ] diff --git a/AGENTS.md b/AGENTS.md index 4565bf0..be6cd0f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -255,10 +255,13 @@ Located in `tools/vytdl-cli/scripts/`: - `build.ps1` - Cross-build helper for macOS/Linux/Windows targets on PowerShell Located in `scripts/`: -- `start-desktop.sh` - Mac/Linux desktop startup -- `start-desktop.ps1` - Windows desktop startup -- `start-desktop.py` - Cross-platform desktop launcher -- `vytdl-launcher.py` - Python launcher (dev/build/clean/schedule) +- `download-yt-dlp-binaries.py` - Download yt-dlp binaries for bundling +- `bootstrap-ytdlp-dev.sh` - Bootstrap yt-dlp dev resources from system PATH + +Desktop dev/build is driven by Task (`apps/vytdl-desktop/Taskfile.yml`): +- `task -d apps/vytdl-desktop dev` - Start desktop app in dev mode +- `task -d apps/vytdl-desktop build` / `bundle` - Production build +- Root shortcuts: `task desktop:dev` / `desktop:build` / `desktop:bundle` / `desktop:check` Download wrapper scripts validate `yt-dlp`/`youtube-dl` availability before running. diff --git a/Taskfile.yml b/Taskfile.yml index 91902c9..0f283c0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -61,58 +61,52 @@ tasks: cmds: - go install -trimpath -ldflags "-s -w" . - # ── Desktop App (via Python scripts) ───────────────────── + # ── Desktop App (via apps/vytdl-desktop Taskfile) ──────── desktop:check: desc: Check desktop build dependencies (Node, pnpm, Rust) - dir: "{{.DESKTOP_DIR}}" cmds: - - "{{.PYTHON}} scripts/build-desktop.py check" + - task -d apps/vytdl-desktop check desktop:install: desc: Install desktop Node dependencies - dir: "{{.DESKTOP_DIR}}" cmds: - - pnpm install + - task -d apps/vytdl-desktop install desktop:yt-dlp: desc: Download yt-dlp binaries for production bundling - dir: "{{.DESKTOP_DIR}}" cmds: - "{{.PYTHON}} scripts/download-yt-dlp-binaries.py" desktop:bootstrap-ytdlp: desc: Bootstrap yt-dlp dev resources from system PATH (fallback) - dir: "{{.DESKTOP_DIR}}" cmds: - bash scripts/bootstrap-ytdlp-dev.sh desktop:dev: desc: Start desktop app in development mode - dir: "{{.DESKTOP_DIR}}" deps: [desktop:bootstrap-ytdlp] cmds: - - "{{.PYTHON}} scripts/build-desktop.py dev" + - task -d apps/vytdl-desktop dev + interactive: true desktop:dev:fast: - desc: Start desktop dev mode (skip pnpm install) - dir: "{{.DESKTOP_DIR}}" + desc: Start desktop dev mode (install auto-skips when up to date) deps: [desktop:bootstrap-ytdlp] cmds: - - "{{.PYTHON}} scripts/build-desktop.py dev --skip-deps" + - task -d apps/vytdl-desktop dev + interactive: true desktop:build: desc: Build desktop app for production - dir: "{{.DESKTOP_DIR}}" deps: [desktop:yt-dlp] cmds: - - "{{.PYTHON}} scripts/build-desktop.py build" + - task -d apps/vytdl-desktop build desktop:bundle: desc: Build desktop app and create distributable package - dir: "{{.DESKTOP_DIR}}" deps: [desktop:yt-dlp] cmds: - - "{{.PYTHON}} scripts/build-desktop.py bundle" + - task -d apps/vytdl-desktop bundle # ── ContentForge (via Go CLI + Python core) ────────────── contentforge:env: @@ -211,6 +205,6 @@ tasks: echo " task contentforge:build" echo " source packages/contentforge-core/scripts/cf-env.sh" echo "" - echo " Direct Python (still supported):" - echo " cd vYtDL-desktop && python3 scripts/build-desktop.py dev" + echo " Desktop Taskfile (direct):" + echo " task -d apps/vytdl-desktop dev" echo "" diff --git a/apps/contentforge-desktop/src-tauri/Cargo.toml b/apps/contentforge-desktop/src-tauri/Cargo.toml index 768bbc3..d220d5a 100644 --- a/apps/contentforge-desktop/src-tauri/Cargo.toml +++ b/apps/contentforge-desktop/src-tauri/Cargo.toml @@ -24,7 +24,7 @@ tauri-plugin-dialog = "2" tauri-plugin-fs = "2" tauri-plugin-store = "2" tokio = { version = "1", features = ["full"] } -uuid = { version = "1", features = ["v4"] } +uuid = { version = "1", features = ["v4", "serde"] } chrono = { version = "0.4", features = ["serde"] } regex = "1" sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "chrono", "macros"] } diff --git a/apps/contentforge-desktop/src-tauri/REBUILD_PLAN.md b/apps/contentforge-desktop/src-tauri/REBUILD_PLAN.md new file mode 100644 index 0000000..67e39f1 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/REBUILD_PLAN.md @@ -0,0 +1,31 @@ +# ContentForge Rust Backend Rebuild 计划 + +## 目标 +将 ContentForge 桌面端的 Rust 后端从 placeholder 实现重建为功能完整的生产级后端,参照 vYtDL-desktop 的架构模式。 + +## Stage 1: Database 层完善 +- **文件**: `database.rs` +- **内容**: 添加所有 CRUD 方法(sessions, messages, assets, settings, pipeline_runs) +- **参考**: vYtDL-desktop/database.rs + +## Stage 2: Pipeline Queue 实现 +- **文件**: `pipeline_queue.rs` (新增) +- **内容**: 异步 Pipeline 执行队列管理器,支持并发控制和状态追踪 +- **参考**: vYtDL-desktop/queue.rs + +## Stage 3: Commands 实现 +- **文件**: `commands.rs` +- **内容**: 实现所有 IPC 命令的实际逻辑 +- **参考**: vYtDL-desktop/commands.rs + +## Stage 4: Lib.rs 集成 +- **文件**: `lib.rs` +- **内容**: 集成 queue manager,完善启动流程 + +## 依赖确认 +- sqlx (已有) +- tokio (已有) +- uuid (已有) +- chrono (已有) +- serde (已有) +- tauri (已有) diff --git a/apps/contentforge-desktop/src-tauri/gen/schemas/acl-manifests.json b/apps/contentforge-desktop/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 0000000..862b8e0 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/gen/schemas/acl-manifests.json @@ -0,0 +1 @@ +{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener","allow-supports-multiple-windows"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-supports-multiple-windows":{"identifier":"allow-supports-multiple-windows","description":"Enables the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":["supports_multiple_windows"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-supports-multiple-windows":{"identifier":"deny-supports-multiple-windows","description":"Denies the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["supports_multiple_windows"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-icon-with-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-icon-with-as-template":{"identifier":"allow-set-icon-with-as-template","description":"Enables the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_with_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-icon-with-as-template":{"identifier":"deny-set-icon-with-as-template","description":"Denies the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_with_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-activity-name","allow-scene-identifier","allow-internal-toggle-maximize"]},"permissions":{"allow-activity-name":{"identifier":"allow-activity-name","description":"Enables the activity_name command without any pre-configured scope.","commands":{"allow":["activity_name"],"deny":[]}},"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-scene-identifier":{"identifier":"allow-scene-identifier","description":"Enables the scene_identifier command without any pre-configured scope.","commands":{"allow":["scene_identifier"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-activity-name":{"identifier":"deny-activity-name","description":"Denies the activity_name command without any pre-configured scope.","commands":{"allow":[],"deny":["activity_name"]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-scene-identifier":{"identifier":"deny-scene-identifier","description":"Denies the scene_identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["scene_identifier"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"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-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)","commands":{"allow":["message"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)","commands":{"allow":["message"],"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. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)","commands":{"allow":[],"deny":["message"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)","commands":{"allow":[],"deny":["message"]}},"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-start-accessing-security-scoped-resource":{"identifier":"allow-start-accessing-security-scoped-resource","description":"Enables the start_accessing_security_scoped_resource command without any pre-configured scope.","commands":{"allow":["start_accessing_security_scoped_resource"],"deny":[]}},"allow-stat":{"identifier":"allow-stat","description":"Enables the stat command without any pre-configured scope.","commands":{"allow":["stat"],"deny":[]}},"allow-stop-accessing-security-scoped-resource":{"identifier":"allow-stop-accessing-security-scoped-resource","description":"Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.","commands":{"allow":["stop_accessing_security_scoped_resource"],"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-start-accessing-security-scoped-resource":{"identifier":"deny-start-accessing-security-scoped-resource","description":"Denies the start_accessing_security_scoped_resource command without any pre-configured scope.","commands":{"allow":[],"deny":["start_accessing_security_scoped_resource"]}},"deny-stat":{"identifier":"deny-stat","description":"Denies the stat command without any pre-configured scope.","commands":{"allow":[],"deny":["stat"]}},"deny-stop-accessing-security-scoped-resource":{"identifier":"deny-stop-accessing-security-scoped-resource","description":"Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.","commands":{"allow":[],"deny":["stop_accessing_security_scoped_resource"]}},"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"}},"log":{"default_permission":{"identifier":"default","description":"Allows the log command","permissions":["allow-log"]},"permissions":{"allow-log":{"identifier":"allow-log","description":"Enables the log command without any pre-configured scope.","commands":{"allow":["log"],"deny":[]}},"deny-log":{"identifier":"deny-log","description":"Denies the log command without any pre-configured scope.","commands":{"allow":[],"deny":["log"]}}},"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"}},"store":{"default_permission":{"identifier":"default","description":"This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n","permissions":["allow-load","allow-get-store","allow-set","allow-get","allow-has","allow-delete","allow-clear","allow-reset","allow-keys","allow-values","allow-entries","allow-length","allow-reload","allow-save"]},"permissions":{"allow-clear":{"identifier":"allow-clear","description":"Enables the clear command without any pre-configured scope.","commands":{"allow":["clear"],"deny":[]}},"allow-delete":{"identifier":"allow-delete","description":"Enables the delete command without any pre-configured scope.","commands":{"allow":["delete"],"deny":[]}},"allow-entries":{"identifier":"allow-entries","description":"Enables the entries command without any pre-configured scope.","commands":{"allow":["entries"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-get-store":{"identifier":"allow-get-store","description":"Enables the get_store command without any pre-configured scope.","commands":{"allow":["get_store"],"deny":[]}},"allow-has":{"identifier":"allow-has","description":"Enables the has command without any pre-configured scope.","commands":{"allow":["has"],"deny":[]}},"allow-keys":{"identifier":"allow-keys","description":"Enables the keys command without any pre-configured scope.","commands":{"allow":["keys"],"deny":[]}},"allow-length":{"identifier":"allow-length","description":"Enables the length command without any pre-configured scope.","commands":{"allow":["length"],"deny":[]}},"allow-load":{"identifier":"allow-load","description":"Enables the load command without any pre-configured scope.","commands":{"allow":["load"],"deny":[]}},"allow-reload":{"identifier":"allow-reload","description":"Enables the reload command without any pre-configured scope.","commands":{"allow":["reload"],"deny":[]}},"allow-reset":{"identifier":"allow-reset","description":"Enables the reset command without any pre-configured scope.","commands":{"allow":["reset"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"allow-set":{"identifier":"allow-set","description":"Enables the set command without any pre-configured scope.","commands":{"allow":["set"],"deny":[]}},"allow-values":{"identifier":"allow-values","description":"Enables the values command without any pre-configured scope.","commands":{"allow":["values"],"deny":[]}},"deny-clear":{"identifier":"deny-clear","description":"Denies the clear command without any pre-configured scope.","commands":{"allow":[],"deny":["clear"]}},"deny-delete":{"identifier":"deny-delete","description":"Denies the delete command without any pre-configured scope.","commands":{"allow":[],"deny":["delete"]}},"deny-entries":{"identifier":"deny-entries","description":"Denies the entries command without any pre-configured scope.","commands":{"allow":[],"deny":["entries"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-get-store":{"identifier":"deny-get-store","description":"Denies the get_store command without any pre-configured scope.","commands":{"allow":[],"deny":["get_store"]}},"deny-has":{"identifier":"deny-has","description":"Denies the has command without any pre-configured scope.","commands":{"allow":[],"deny":["has"]}},"deny-keys":{"identifier":"deny-keys","description":"Denies the keys command without any pre-configured scope.","commands":{"allow":[],"deny":["keys"]}},"deny-length":{"identifier":"deny-length","description":"Denies the length command without any pre-configured scope.","commands":{"allow":[],"deny":["length"]}},"deny-load":{"identifier":"deny-load","description":"Denies the load command without any pre-configured scope.","commands":{"allow":[],"deny":["load"]}},"deny-reload":{"identifier":"deny-reload","description":"Denies the reload command without any pre-configured scope.","commands":{"allow":[],"deny":["reload"]}},"deny-reset":{"identifier":"deny-reset","description":"Denies the reset command without any pre-configured scope.","commands":{"allow":[],"deny":["reset"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}},"deny-set":{"identifier":"deny-set","description":"Denies the set command without any pre-configured scope.","commands":{"allow":[],"deny":["set"]}},"deny-values":{"identifier":"deny-values","description":"Denies the values command without any pre-configured scope.","commands":{"allow":[],"deny":["values"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/apps/contentforge-desktop/src-tauri/gen/schemas/capabilities.json b/apps/contentforge-desktop/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 0000000..0f21c98 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{"default":{"identifier":"default","description":"enables the default permissions for ContentForge","local":true,"windows":["main"],"permissions":["core:default","dialog:default","dialog:allow-open","dialog:allow-confirm","fs:default",{"identifier":"fs:allow-read-dir","allow":[{"path":"$HOME/**"}]},{"identifier":"fs:allow-read-file","allow":[{"path":"$HOME/**"}]},{"identifier":"fs:allow-mkdir","allow":[{"path":"$HOME/**"}]},{"identifier":"fs:allow-exists","allow":[{"path":"$HOME/**"}]},{"identifier":"fs:allow-write-file","allow":[{"path":"$HOME/**"}]},{"identifier":"fs:allow-remove","allow":[{"path":"$HOME/**"}]},{"identifier":"fs:allow-rename","allow":[{"path":"$HOME/**"}]},"store:default","shell:allow-execute","shell:allow-spawn","shell:allow-kill","shell:allow-stdin-write",{"identifier":"shell:allow-open","allow":[{"url":"https://**"},{"url":"http://**"}]}]}} \ No newline at end of file diff --git a/apps/contentforge-desktop/src-tauri/gen/schemas/desktop-schema.json b/apps/contentforge-desktop/src-tauri/gen/schemas/desktop-schema.json new file mode 100644 index 0000000..541884c --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,6446 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This 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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-start-accessing-security-scoped-resource", + "markdownDescription": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stop-accessing-security-scoped-resource", + "markdownDescription": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-start-accessing-security-scoped-resource", + "markdownDescription": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stop-accessing-security-scoped-resource", + "markdownDescription": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "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." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "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." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "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", + "type": "string", + "const": "fs:scope", + "markdownDescription": "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" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "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" + }, + { + "type": "object", + "required": [ + "path" + ], + "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" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "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" + }, + { + "type": "object", + "required": [ + "path" + ], + "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" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This 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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-start-accessing-security-scoped-resource", + "markdownDescription": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stop-accessing-security-scoped-resource", + "markdownDescription": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-start-accessing-security-scoped-resource", + "markdownDescription": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stop-accessing-security-scoped-resource", + "markdownDescription": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "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." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "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." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "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", + "type": "string", + "const": "fs:scope", + "markdownDescription": "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" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + }, + { + "description": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`", + "type": "string", + "const": "log:default", + "markdownDescription": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`" + }, + { + "description": "Enables the log command without any pre-configured scope.", + "type": "string", + "const": "log:allow-log", + "markdownDescription": "Enables the log command without any pre-configured scope." + }, + { + "description": "Denies the log command without any pre-configured scope.", + "type": "string", + "const": "log:deny-log", + "markdownDescription": "Denies the log command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`", + "type": "string", + "const": "store:default", + "markdownDescription": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`" + }, + { + "description": "Enables the clear command without any pre-configured scope.", + "type": "string", + "const": "store:allow-clear", + "markdownDescription": "Enables the clear command without any pre-configured scope." + }, + { + "description": "Enables the delete command without any pre-configured scope.", + "type": "string", + "const": "store:allow-delete", + "markdownDescription": "Enables the delete command without any pre-configured scope." + }, + { + "description": "Enables the entries command without any pre-configured scope.", + "type": "string", + "const": "store:allow-entries", + "markdownDescription": "Enables the entries command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "store:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the get_store command without any pre-configured scope.", + "type": "string", + "const": "store:allow-get-store", + "markdownDescription": "Enables the get_store command without any pre-configured scope." + }, + { + "description": "Enables the has command without any pre-configured scope.", + "type": "string", + "const": "store:allow-has", + "markdownDescription": "Enables the has command without any pre-configured scope." + }, + { + "description": "Enables the keys command without any pre-configured scope.", + "type": "string", + "const": "store:allow-keys", + "markdownDescription": "Enables the keys command without any pre-configured scope." + }, + { + "description": "Enables the length command without any pre-configured scope.", + "type": "string", + "const": "store:allow-length", + "markdownDescription": "Enables the length command without any pre-configured scope." + }, + { + "description": "Enables the load command without any pre-configured scope.", + "type": "string", + "const": "store:allow-load", + "markdownDescription": "Enables the load command without any pre-configured scope." + }, + { + "description": "Enables the reload command without any pre-configured scope.", + "type": "string", + "const": "store:allow-reload", + "markdownDescription": "Enables the reload command without any pre-configured scope." + }, + { + "description": "Enables the reset command without any pre-configured scope.", + "type": "string", + "const": "store:allow-reset", + "markdownDescription": "Enables the reset command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "store:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Enables the set command without any pre-configured scope.", + "type": "string", + "const": "store:allow-set", + "markdownDescription": "Enables the set command without any pre-configured scope." + }, + { + "description": "Enables the values command without any pre-configured scope.", + "type": "string", + "const": "store:allow-values", + "markdownDescription": "Enables the values command without any pre-configured scope." + }, + { + "description": "Denies the clear command without any pre-configured scope.", + "type": "string", + "const": "store:deny-clear", + "markdownDescription": "Denies the clear command without any pre-configured scope." + }, + { + "description": "Denies the delete command without any pre-configured scope.", + "type": "string", + "const": "store:deny-delete", + "markdownDescription": "Denies the delete command without any pre-configured scope." + }, + { + "description": "Denies the entries command without any pre-configured scope.", + "type": "string", + "const": "store:deny-entries", + "markdownDescription": "Denies the entries command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "store:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the get_store command without any pre-configured scope.", + "type": "string", + "const": "store:deny-get-store", + "markdownDescription": "Denies the get_store command without any pre-configured scope." + }, + { + "description": "Denies the has command without any pre-configured scope.", + "type": "string", + "const": "store:deny-has", + "markdownDescription": "Denies the has command without any pre-configured scope." + }, + { + "description": "Denies the keys command without any pre-configured scope.", + "type": "string", + "const": "store:deny-keys", + "markdownDescription": "Denies the keys command without any pre-configured scope." + }, + { + "description": "Denies the length command without any pre-configured scope.", + "type": "string", + "const": "store:deny-length", + "markdownDescription": "Denies the length command without any pre-configured scope." + }, + { + "description": "Denies the load command without any pre-configured scope.", + "type": "string", + "const": "store:deny-load", + "markdownDescription": "Denies the load command without any pre-configured scope." + }, + { + "description": "Denies the reload command without any pre-configured scope.", + "type": "string", + "const": "store:deny-reload", + "markdownDescription": "Denies the reload command without any pre-configured scope." + }, + { + "description": "Denies the reset command without any pre-configured scope.", + "type": "string", + "const": "store:deny-reset", + "markdownDescription": "Denies the reset command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "store:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "Denies the set command without any pre-configured scope.", + "type": "string", + "const": "store:deny-set", + "markdownDescription": "Denies the set command without any pre-configured scope." + }, + { + "description": "Denies the values command without any pre-configured scope.", + "type": "string", + "const": "store:deny-values", + "markdownDescription": "Denies the values command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "ShellScopeEntryAllowedArg": { + "description": "A command argument allowed to be executed by the webview API.", + "anyOf": [ + { + "description": "A non-configurable argument that is passed to the command in the order it was specified.", + "type": "string" + }, + { + "description": "A variable that is set while calling the command from the webview API.", + "type": "object", + "required": [ + "validator" + ], + "properties": { + "raw": { + "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", + "default": false, + "type": "boolean" + }, + "validator": { + "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ShellScopeEntryAllowedArgs": { + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", + "anyOf": [ + { + "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", + "type": "boolean" + }, + { + "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", + "type": "array", + "items": { + "$ref": "#/definitions/ShellScopeEntryAllowedArg" + } + } + ] + } + } +} \ No newline at end of file diff --git a/apps/contentforge-desktop/src-tauri/gen/schemas/macOS-schema.json b/apps/contentforge-desktop/src-tauri/gen/schemas/macOS-schema.json new file mode 100644 index 0000000..541884c --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/gen/schemas/macOS-schema.json @@ -0,0 +1,6446 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This 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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-start-accessing-security-scoped-resource", + "markdownDescription": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stop-accessing-security-scoped-resource", + "markdownDescription": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-start-accessing-security-scoped-resource", + "markdownDescription": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stop-accessing-security-scoped-resource", + "markdownDescription": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "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." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "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." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "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", + "type": "string", + "const": "fs:scope", + "markdownDescription": "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" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "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" + }, + { + "type": "object", + "required": [ + "path" + ], + "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" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "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" + }, + { + "type": "object", + "required": [ + "path" + ], + "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" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This 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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "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\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-start-accessing-security-scoped-resource", + "markdownDescription": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stop-accessing-security-scoped-resource", + "markdownDescription": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-start-accessing-security-scoped-resource", + "markdownDescription": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stop-accessing-security-scoped-resource", + "markdownDescription": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "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." + }, + { + "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.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "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." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "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", + "type": "string", + "const": "fs:scope", + "markdownDescription": "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" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + }, + { + "description": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`", + "type": "string", + "const": "log:default", + "markdownDescription": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`" + }, + { + "description": "Enables the log command without any pre-configured scope.", + "type": "string", + "const": "log:allow-log", + "markdownDescription": "Enables the log command without any pre-configured scope." + }, + { + "description": "Denies the log command without any pre-configured scope.", + "type": "string", + "const": "log:deny-log", + "markdownDescription": "Denies the log command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`", + "type": "string", + "const": "store:default", + "markdownDescription": "This permission set configures what kind of\noperations are available from the store plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-load`\n- `allow-get-store`\n- `allow-set`\n- `allow-get`\n- `allow-has`\n- `allow-delete`\n- `allow-clear`\n- `allow-reset`\n- `allow-keys`\n- `allow-values`\n- `allow-entries`\n- `allow-length`\n- `allow-reload`\n- `allow-save`" + }, + { + "description": "Enables the clear command without any pre-configured scope.", + "type": "string", + "const": "store:allow-clear", + "markdownDescription": "Enables the clear command without any pre-configured scope." + }, + { + "description": "Enables the delete command without any pre-configured scope.", + "type": "string", + "const": "store:allow-delete", + "markdownDescription": "Enables the delete command without any pre-configured scope." + }, + { + "description": "Enables the entries command without any pre-configured scope.", + "type": "string", + "const": "store:allow-entries", + "markdownDescription": "Enables the entries command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "store:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the get_store command without any pre-configured scope.", + "type": "string", + "const": "store:allow-get-store", + "markdownDescription": "Enables the get_store command without any pre-configured scope." + }, + { + "description": "Enables the has command without any pre-configured scope.", + "type": "string", + "const": "store:allow-has", + "markdownDescription": "Enables the has command without any pre-configured scope." + }, + { + "description": "Enables the keys command without any pre-configured scope.", + "type": "string", + "const": "store:allow-keys", + "markdownDescription": "Enables the keys command without any pre-configured scope." + }, + { + "description": "Enables the length command without any pre-configured scope.", + "type": "string", + "const": "store:allow-length", + "markdownDescription": "Enables the length command without any pre-configured scope." + }, + { + "description": "Enables the load command without any pre-configured scope.", + "type": "string", + "const": "store:allow-load", + "markdownDescription": "Enables the load command without any pre-configured scope." + }, + { + "description": "Enables the reload command without any pre-configured scope.", + "type": "string", + "const": "store:allow-reload", + "markdownDescription": "Enables the reload command without any pre-configured scope." + }, + { + "description": "Enables the reset command without any pre-configured scope.", + "type": "string", + "const": "store:allow-reset", + "markdownDescription": "Enables the reset command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "store:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Enables the set command without any pre-configured scope.", + "type": "string", + "const": "store:allow-set", + "markdownDescription": "Enables the set command without any pre-configured scope." + }, + { + "description": "Enables the values command without any pre-configured scope.", + "type": "string", + "const": "store:allow-values", + "markdownDescription": "Enables the values command without any pre-configured scope." + }, + { + "description": "Denies the clear command without any pre-configured scope.", + "type": "string", + "const": "store:deny-clear", + "markdownDescription": "Denies the clear command without any pre-configured scope." + }, + { + "description": "Denies the delete command without any pre-configured scope.", + "type": "string", + "const": "store:deny-delete", + "markdownDescription": "Denies the delete command without any pre-configured scope." + }, + { + "description": "Denies the entries command without any pre-configured scope.", + "type": "string", + "const": "store:deny-entries", + "markdownDescription": "Denies the entries command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "store:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the get_store command without any pre-configured scope.", + "type": "string", + "const": "store:deny-get-store", + "markdownDescription": "Denies the get_store command without any pre-configured scope." + }, + { + "description": "Denies the has command without any pre-configured scope.", + "type": "string", + "const": "store:deny-has", + "markdownDescription": "Denies the has command without any pre-configured scope." + }, + { + "description": "Denies the keys command without any pre-configured scope.", + "type": "string", + "const": "store:deny-keys", + "markdownDescription": "Denies the keys command without any pre-configured scope." + }, + { + "description": "Denies the length command without any pre-configured scope.", + "type": "string", + "const": "store:deny-length", + "markdownDescription": "Denies the length command without any pre-configured scope." + }, + { + "description": "Denies the load command without any pre-configured scope.", + "type": "string", + "const": "store:deny-load", + "markdownDescription": "Denies the load command without any pre-configured scope." + }, + { + "description": "Denies the reload command without any pre-configured scope.", + "type": "string", + "const": "store:deny-reload", + "markdownDescription": "Denies the reload command without any pre-configured scope." + }, + { + "description": "Denies the reset command without any pre-configured scope.", + "type": "string", + "const": "store:deny-reset", + "markdownDescription": "Denies the reset command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "store:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "Denies the set command without any pre-configured scope.", + "type": "string", + "const": "store:deny-set", + "markdownDescription": "Denies the set command without any pre-configured scope." + }, + { + "description": "Denies the values command without any pre-configured scope.", + "type": "string", + "const": "store:deny-values", + "markdownDescription": "Denies the values command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "ShellScopeEntryAllowedArg": { + "description": "A command argument allowed to be executed by the webview API.", + "anyOf": [ + { + "description": "A non-configurable argument that is passed to the command in the order it was specified.", + "type": "string" + }, + { + "description": "A variable that is set while calling the command from the webview API.", + "type": "object", + "required": [ + "validator" + ], + "properties": { + "raw": { + "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", + "default": false, + "type": "boolean" + }, + "validator": { + "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ShellScopeEntryAllowedArgs": { + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", + "anyOf": [ + { + "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", + "type": "boolean" + }, + { + "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", + "type": "array", + "items": { + "$ref": "#/definitions/ShellScopeEntryAllowedArg" + } + } + ] + } + } +} \ No newline at end of file diff --git a/apps/contentforge-desktop/src-tauri/icons/128x128.png b/apps/contentforge-desktop/src-tauri/icons/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..4ccfe037ac7697e3958fd75455bfc26890cd9634 GIT binary patch literal 1025 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU_RmL;uumf=k4r`LAM=5T(`=7 zm&zsW`q#TS^#^7ia!Nb__NNlNsLduzA1+HdhmAGdGsY~R22TH|c2 zJU!f8zxdvLWB%{k7an<@@>hT?Rr(dwF4$A)%@%i=ns|)ouXc=-G zlJ~bxe`)pn4=Xpj%>tR1nGfFndfvI?2Jbu z&6VD~tLy#s|Kl(3#SF3zGGI|hhC%^SndxgQo>*VEUEI9*v7VJfufn3{4|h2NtR}or zsR?JQ;9e%Ojzz<FH}3!Bw0W-MRG+|kVNNTI=kL!s}c?}Dqo zlg(d1`R8t3>XuttTA!Zsr-Y&K+O*Y&z1?q=uDW|`h7ae1w}1Z&9XlbUz{dpAKST7u zt+bU_@`b#TgBVs%ep(o##@bNSa(eab=}q(5*>2d|{@-VJkJSof4Nyn&tI!Qk!#g_26vs`7GFVdQ I&MBb@0J8(bjsO4v literal 0 HcmV?d00001 diff --git a/apps/contentforge-desktop/src-tauri/icons/128x128@2x.png b/apps/contentforge-desktop/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..630df7d4b3d5189f34058fca4205d9fe2ef6c96d GIT binary patch literal 2106 zcmchZYgCg*8pr1i0Ya7#ZYm_)mWz0+faMb877H$=ln^-FRD=M<8=@GXAR2f9H;9PT zr6?5^&{Yu;6XhszsU$Q4wm|_41w{w}1VpX^A(A(FH&nap+1=BR{q)Q^&pf}GXa4`0 zGx>CyF9A=&0{{qqpZf#@0I7x$z-g#LB=g2T0C13>&yJAv^63EwW`CF=J1D3)YQ*vN z@b`%!1Ea-qT-za@|+5`m`aD;ccIy~=bY-e{X2@wsTQu&ih z(yWsaOY^mq6#bkhF3&m}9QynX&UnUhpR7A7RE=1^zL<8uT>{LQu@?lD{#I(krebf`Df0hdRx;!fJqj5EFYgXJmv1R1WKtiqLDge@!in~10raWYDSwJBOCwdD5M)=-dM z3Zqs>9?{g={};yjKzgFA*L}m!no6f8X>XyRg&1{E)=QCTBk@zkWuaC{U25UR%4@`J z-u!c;ij;_rn0oW>x#bh~q2KVtI-7kM7q=t+CR7ho_Y#|Fk?;q-QiQ?gSLWIT{FLdj z#VGp*xN2hY`sVDoWZ`0&q%yF;hw&`eS|{s`rrLLMR>THO0B?!t*dzsU_XxHN6cu8yKCec!Rq;4kim(=J{ z$^&c6Kej>{!SU61n-q^&;>A~jcK__XCH4Gf+jlZ!&#ZT|z4@E7*M$RirM3KNxLO-I zCmBgHxO(r<&tvn)#C=JD4-HH2;&}nCa&l(tiz4@+kO=h@mOPLXD}gF77+u?$~b#9rZS?GUi`5`$2933EZeq$(gw{mQ@GmgYR})P0n4T) z$<^Sb9mWO&+W$mNTL$necdX#K`Q-5x=8(UOS{<+`1yp+oW?;adpAKm9Qq^=PGp6O3 zTOTEwT`=k(M+;yJR`PZPBZ}7@p=Qg|jnU!9Bk#wlgJ$fnNdtge4|Ko$xbBZmOy$+N zz^FpC!PC zybs9awxB&$mY@q;-gff8o|P7bN)LE`hvz#^1siG+Xi?<;Cd8nzw@5mqy>w>b3=f>XS$a``_ts8v>cdqT~cL?SC|IiP}rHOrj63lFokT!b7~ zq)dG%5@)8|qG9OpXKWF= zSGTV(NkV?P+XyM%=lm5-+YGJP!2Mgy<+S?yj*Ed#4}y}tL7g$l6e+&$GGpRUmFhj> zjfS|8SM+x<61;uY(8va|(&LI?H~U59t$ literal 0 HcmV?d00001 diff --git a/apps/contentforge-desktop/src-tauri/icons/32x32.png b/apps/contentforge-desktop/src-tauri/icons/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..2aa21b87f5f125c375ab4f4cd2128120b750ab8f GIT binary patch literal 332 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 literal 0 HcmV?d00001 diff --git a/apps/contentforge-desktop/src-tauri/src/agent_cli.rs b/apps/contentforge-desktop/src-tauri/src/agent_cli.rs new file mode 100644 index 0000000..e43c894 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/agent_cli.rs @@ -0,0 +1,497 @@ +use chrono::{DateTime, Utc}; +use serde::Serialize; +use serde_json::Value; +use std::path::{Path, PathBuf}; +use std::process::Command; + +#[derive(Debug, Clone, Serialize)] +pub struct AgentCliDetection { + pub id: String, + pub label: String, + pub found: bool, + pub path: Option, + pub version: Option, + pub source: String, + pub config: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ConfigCheck { + pub id: String, + pub label: String, + pub ok: bool, + pub detail: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct KimiConfigStatus { + pub status: String, + pub ready: bool, + pub config_dir: Option, + pub config_toml_path: Option, + pub tui_toml_path: Option, + pub credentials_path: Option, + pub default_model: Option, + pub authenticated: bool, + pub token_expired: bool, + pub token_expires_at: Option, + pub skills_count: u32, + pub project_skills_exists: bool, + pub project_skills_path: Option, + pub checks: Vec, +} + +#[derive(Debug, Serialize)] +pub struct DetectAgentCliResult { + pub kimi: AgentCliDetection, + pub other: AgentCliDetection, +} + +fn find_in_path(names: &[&str]) -> Option { + for name in names { + if let Some(path) = find_executable(name) { + return Some(path); + } + } + None +} + +fn find_executable(name: &str) -> Option { + let path_var = std::env::var("PATH").unwrap_or_default(); + for dir in path_var.split(std::path::MAIN_SEPARATOR) { + let candidate = Path::new(dir).join(name); + if candidate.is_file() { + return candidate.to_str().map(|s| s.to_string()); + } + #[cfg(windows)] + { + let candidate = Path::new(dir).join(format!("{name}.exe")); + if candidate.is_file() { + return candidate.to_str().map(|s| s.to_string()); + } + } + } + None +} + +fn known_kimi_bin_paths() -> Vec { + let mut paths = Vec::new(); + if let Some(home) = dirs::home_dir() { + paths.push(home.join(".kimi-code").join("bin").join("kimi")); + paths.push(home.join(".kimi").join("bin").join("kimi")); + paths.push(home.join(".local").join("bin").join("kimi-cli")); + paths.push(home.join(".local").join("bin").join("kimi")); + } + paths +} + +fn resolve_kimi_binary(configured: Option<&str>) -> AgentCliDetection { + if let Some(path) = configured.filter(|p| !p.trim().is_empty()) { + let path = path.trim().to_string(); + let exists = Path::new(&path).is_file(); + let version = if exists { detect_version(&path) } else { None }; + let mut detection = AgentCliDetection { + id: "kimi".to_string(), + label: "Kimi CLI".to_string(), + found: exists, + path: Some(path.clone()), + version, + source: if exists { + "configured".to_string() + } else { + "configured_missing".to_string() + }, + config: None, + }; + if exists { + detection.config = Some(inspect_kimi_config(Some(&path))); + } + return detection; + } + + for candidate in known_kimi_bin_paths() { + if candidate.is_file() { + let path = candidate.to_string_lossy().to_string(); + let version = detect_version(&path); + let mut detection = AgentCliDetection { + id: "kimi".to_string(), + label: "Kimi CLI".to_string(), + found: true, + path: Some(path.clone()), + version, + source: "local_install".to_string(), + config: None, + }; + detection.config = Some(inspect_kimi_config(Some(&path))); + return detection; + } + } + + if let Some(path) = find_in_path(&["kimi", "kimi-cli", "kimi-code"]) { + let version = detect_version(&path); + let mut detection = AgentCliDetection { + id: "kimi".to_string(), + label: "Kimi CLI".to_string(), + found: true, + path: Some(path.clone()), + version, + source: "path".to_string(), + config: None, + }; + detection.config = Some(inspect_kimi_config(Some(&path))); + return detection; + } + + AgentCliDetection { + id: "kimi".to_string(), + label: "Kimi CLI".to_string(), + found: false, + path: None, + version: None, + source: "not_found".to_string(), + config: Some(inspect_kimi_config(None)), + } +} + +fn resolve_other_binary(configured: Option<&str>) -> AgentCliDetection { + if let Some(path) = configured.filter(|p| !p.trim().is_empty()) { + let path = path.trim().to_string(); + let exists = Path::new(&path).is_file(); + return AgentCliDetection { + id: "other".to_string(), + label: "Other".to_string(), + found: exists, + path: Some(path), + version: None, + source: if exists { + "configured".to_string() + } else { + "configured_missing".to_string() + }, + config: None, + }; + } + + AgentCliDetection { + id: "other".to_string(), + label: "Other".to_string(), + found: false, + path: None, + version: None, + source: "disabled".to_string(), + config: None, + } +} + +fn detect_version(bin_path: &str) -> Option { + for args in [["--version"], ["version"], ["-V"], ["-v"]] { + let output = Command::new(bin_path).args(args).output().ok()?; + if output.status.success() { + let text = String::from_utf8_lossy(&output.stdout); + let line = text.lines().next()?.trim(); + if !line.is_empty() { + return Some(line.to_string()); + } + } + } + None +} + +fn kimi_config_root() -> Option { + if let Some(home) = dirs::home_dir() { + let modern = home.join(".kimi-code"); + if modern.join("config.toml").is_file() { + return Some(modern); + } + let legacy = home.join(".kimi"); + if legacy.join("config.toml").is_file() { + return Some(legacy); + } + if modern.is_dir() { + return Some(modern); + } + } + None +} + +fn parse_default_model(config_text: &str) -> Option { + for line in config_text.lines() { + let line = line.trim(); + if line.starts_with("default_model") { + if let Some((_, value)) = line.split_once('=') { + let value = value.trim().trim_matches('"').trim_matches('\''); + if !value.is_empty() { + return Some(value.to_string()); + } + } + } + } + None +} + +fn read_credentials_status(credentials_path: &Path) -> (bool, bool, Option) { + let text = match std::fs::read_to_string(credentials_path) { + Ok(t) => t, + Err(_) => return (false, false, None), + }; + + let json: Value = match serde_json::from_str(&text) { + Ok(v) => v, + Err(_) => return (false, false, None), + }; + + let has_token = json + .get("access_token") + .and_then(|v| v.as_str()) + .map(|s| !s.is_empty()) + .unwrap_or(false); + + if !has_token { + return (false, false, None); + } + + let expires_at = json.get("expires_at").and_then(|v| { + if let Some(n) = v.as_i64() { + return Some(n); + } + v.as_str().and_then(|s| s.parse::().ok()) + }); + + if let Some(exp) = expires_at { + let expires_dt = DateTime::::from_timestamp(exp, 0); + let expired = expires_dt.map(|dt| dt < Utc::now()).unwrap_or(false); + let expires_label = expires_dt.map(|dt| dt.to_rfc3339()); + return (true, expired, expires_label); + } + + (true, false, None) +} + +fn count_skills(skills_dir: &Path) -> u32 { + std::fs::read_dir(skills_dir) + .map(|entries| { + entries + .flatten() + .filter(|e| e.path().is_dir() || e.path().extension().is_some()) + .count() as u32 + }) + .unwrap_or(0) +} + +fn find_project_skills() -> Option { + if let Ok(root) = std::env::var("CONTENTFORGE_PROJECT_ROOT") { + let path = PathBuf::from(root).join(".agents").join("skills"); + if path.is_dir() { + return Some(path); + } + } + + if let Ok(mut cwd) = std::env::current_dir() { + for _ in 0..8 { + let candidate = cwd.join(".agents").join("skills"); + if candidate.is_dir() { + return Some(candidate); + } + if !cwd.pop() { + break; + } + } + } + + None +} + +pub fn inspect_kimi_config(bin_path: Option<&str>) -> KimiConfigStatus { + let mut checks: Vec = Vec::new(); + let config_root = kimi_config_root(); + + let config_dir = config_root + .as_ref() + .map(|p| p.to_string_lossy().to_string()); + let config_toml_path = config_root + .as_ref() + .map(|p| p.join("config.toml")) + .filter(|p| p.is_file()) + .map(|p| p.to_string_lossy().to_string()); + let tui_toml_path = config_root + .as_ref() + .map(|p| p.join("tui.toml")) + .filter(|p| p.is_file()) + .map(|p| p.to_string_lossy().to_string()); + + let config_exists = config_toml_path.is_some(); + checks.push(ConfigCheck { + id: "config_toml".to_string(), + label: "config.toml".to_string(), + ok: config_exists, + detail: config_toml_path.clone(), + }); + + let tui_exists = tui_toml_path.is_some(); + checks.push(ConfigCheck { + id: "tui_toml".to_string(), + label: "tui.toml".to_string(), + ok: tui_exists, + detail: tui_toml_path.clone(), + }); + + let default_model = config_root + .as_ref() + .and_then(|root| std::fs::read_to_string(root.join("config.toml")).ok()) + .and_then(|text| parse_default_model(&text)); + + if let Some(model) = &default_model { + checks.push(ConfigCheck { + id: "default_model".to_string(), + label: "default_model".to_string(), + ok: true, + detail: Some(model.clone()), + }); + } + + let credentials_path = config_root.as_ref().and_then(|root| { + let modern = root.join("credentials").join("kimi-code.json"); + if modern.is_file() { + return Some(modern); + } + let legacy = root.join("kimi.json"); + if legacy.is_file() { + return Some(legacy); + } + None + }); + + let credentials_path_str = credentials_path + .as_ref() + .map(|p| p.to_string_lossy().to_string()); + + let (authenticated, token_expired, token_expires_at) = credentials_path + .as_ref() + .map(|p| read_credentials_status(p)) + .unwrap_or((false, false, None)); + + checks.push(ConfigCheck { + id: "credentials".to_string(), + label: "credentials".to_string(), + ok: authenticated && !token_expired, + detail: credentials_path_str.clone(), + }); + + let bin_found = bin_path + .map(|p| Path::new(p).is_file()) + .unwrap_or(false) + || known_kimi_bin_paths().iter().any(|p| p.is_file()); + + let bin_detail = bin_path + .map(|s| s.to_string()) + .or_else(|| { + known_kimi_bin_paths() + .into_iter() + .find(|p| p.is_file()) + .map(|p| p.to_string_lossy().to_string()) + }); + + checks.push(ConfigCheck { + id: "binary".to_string(), + label: "kimi binary".to_string(), + ok: bin_found, + detail: bin_detail, + }); + + let skills_count = config_root + .as_ref() + .map(|root| root.join("skills")) + .filter(|p| p.is_dir()) + .map(|p| count_skills(&p)) + .unwrap_or(0); + + if skills_count > 0 { + checks.push(ConfigCheck { + id: "skills".to_string(), + label: "user skills".to_string(), + ok: true, + detail: Some(format!("{skills_count} items")), + }); + } + + let project_skills = find_project_skills(); + let project_skills_exists = project_skills.is_some(); + let project_skills_path = project_skills + .as_ref() + .map(|p| p.to_string_lossy().to_string()); + + checks.push(ConfigCheck { + id: "project_skills".to_string(), + label: "project skills".to_string(), + ok: project_skills_exists, + detail: project_skills_path.clone(), + }); + + let status = if !bin_found { + "not_installed".to_string() + } else if !config_exists { + "config_missing".to_string() + } else if !authenticated { + "needs_login".to_string() + } else if token_expired { + "token_expired".to_string() + } else { + "ready".to_string() + }; + + let ready = status == "ready"; + + KimiConfigStatus { + status, + ready, + config_dir, + config_toml_path, + tui_toml_path, + credentials_path: credentials_path_str, + default_model, + authenticated, + token_expired, + token_expires_at, + skills_count, + project_skills_exists, + project_skills_path, + checks, + } +} + +pub fn detect_agent_cli_tools( + kimi_bin: Option, + other_bin: Option, +) -> DetectAgentCliResult { + DetectAgentCliResult { + kimi: resolve_kimi_binary(kimi_bin.as_deref()), + other: resolve_other_binary(other_bin.as_deref()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_default_model_from_sample() { + let text = r#"default_model = "kimi-code/kimi-for-coding" +default_thinking = true"#; + assert_eq!( + parse_default_model(text), + Some("kimi-code/kimi-for-coding".to_string()) + ); + } + + #[test] + fn inspect_kimi_config_reads_local_files() { + let status = inspect_kimi_config(None); + // On dev machines with kimi installed this should find config + if dirs::home_dir() + .map(|h| h.join(".kimi-code").join("config.toml").is_file()) + .unwrap_or(false) + { + assert!(status.config_toml_path.is_some()); + } + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/agent_runner.rs b/apps/contentforge-desktop/src-tauri/src/agent_runner.rs new file mode 100644 index 0000000..2f18d2f --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/agent_runner.rs @@ -0,0 +1,198 @@ +use serde::{Deserialize, Serialize}; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use tauri::{AppHandle, Emitter}; + +#[derive(Debug, Clone, Deserialize)] +pub struct AssetContext { + pub id: String, + pub title: String, + #[serde(rename = "type")] + pub asset_type: String, + pub source_url: Option, + pub transcript_excerpt: Option, + pub language: Option, + pub duration_sec: Option, +} + +#[derive(Debug, Serialize, Clone)] +pub struct AgentTokenEvent { + pub session_id: String, + pub token: String, +} + +#[derive(Debug, Serialize, Clone)] +pub struct AgentDoneEvent { + pub session_id: String, +} + +#[derive(Debug, Serialize, Clone)] +pub struct AgentErrorEvent { + pub session_id: String, + pub error: String, +} + +#[derive(Debug, Deserialize)] +struct KimiStreamLine { + role: Option, + content: Option, +} + +pub fn find_project_root() -> PathBuf { + if let Ok(root) = std::env::var("CONTENTFORGE_PROJECT_ROOT") { + let path = PathBuf::from(root); + if path.join(".agents").join("skills").exists() { + return path; + } + } + + if let Ok(mut cwd) = std::env::current_dir() { + for _ in 0..8 { + if cwd.join(".agents").join("skills").exists() { + return cwd; + } + if !cwd.pop() { + break; + } + } + } + + std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) +} + +pub fn build_prompt(message: &str, context: &[AssetContext]) -> String { + let mut prompt = String::from( + "You are an AI assistant helping the user understand and analyze their downloaded videos and transcript reports.\n\ + Answer in the same language as the user's question when possible.\n\ + Reference timestamps when transcript content includes them.\n\n", + ); + + if !context.is_empty() { + prompt.push_str("## Selected Media Context\n\n"); + for asset in context { + prompt.push_str(&format!("### {} ({}) \n", asset.title, asset.asset_type)); + if let Some(url) = &asset.source_url { + prompt.push_str(&format!("Source: {url}\n")); + } + if let Some(lang) = &asset.language { + prompt.push_str(&format!("Language: {lang}\n")); + } + if let Some(duration) = asset.duration_sec { + prompt.push_str(&format!("Duration: {duration:.0}s\n")); + } + if let Some(excerpt) = &asset.transcript_excerpt { + prompt.push_str("\nTranscript excerpt:\n"); + prompt.push_str(excerpt); + prompt.push_str("\n"); + } + prompt.push('\n'); + } + } + + prompt.push_str("## User Question\n"); + prompt.push_str(message); + prompt.push('\n'); + prompt +} + +pub fn spawn_kimi_chat( + app: AppHandle, + session_id: String, + kimi_bin: String, + prompt: String, + project_root: PathBuf, +) { + tauri::async_runtime::spawn(async move { + let result = run_kimi_stream(&app, &session_id, &kimi_bin, &prompt, &project_root).await; + if let Err(error) = result { + let _ = app.emit( + "agent:error", + AgentErrorEvent { + session_id, + error, + }, + ); + } + }); +} + +async fn run_kimi_stream( + app: &AppHandle, + session_id: &str, + kimi_bin: &str, + prompt: &str, + project_root: &PathBuf, +) -> Result<(), String> { + let session_id_owned = session_id.to_string(); + let kimi_bin = kimi_bin.to_string(); + let prompt = prompt.to_string(); + let project_root = project_root.clone(); + let app = app.clone(); + + tokio::task::spawn_blocking(move || { + let mut child = Command::new(&kimi_bin) + .args([ + "-p", + &prompt, + "--output-format", + "stream-json", + "-y", + ]) + .current_dir(&project_root) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| format!("Failed to start Kimi CLI: {e}"))?; + + let stdout = child + .stdout + .take() + .ok_or_else(|| "Failed to capture Kimi stdout".to_string())?; + + let reader = BufReader::new(stdout); + for line in reader.lines() { + let line = line.map_err(|e| format!("Failed to read Kimi output: {e}"))?; + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + + if let Ok(parsed) = serde_json::from_str::(trimmed) { + if parsed.role.as_deref() == Some("assistant") { + if let Some(token) = parsed.content.filter(|c| !c.is_empty()) { + let _ = app.emit( + "agent:token", + AgentTokenEvent { + session_id: session_id_owned.clone(), + token, + }, + ); + } + } + } + } + + let status = child + .wait() + .map_err(|e| format!("Kimi CLI process error: {e}"))?; + + if !status.success() { + return Err(format!( + "Kimi CLI exited with status {}", + status.code().unwrap_or(-1) + )); + } + + let _ = app.emit( + "agent:done", + AgentDoneEvent { + session_id: session_id_owned, + }, + ); + + Ok(()) + }) + .await + .map_err(|e| format!("Agent task panicked: {e}"))? +} diff --git a/apps/contentforge-desktop/src-tauri/src/audio_extractor.rs b/apps/contentforge-desktop/src-tauri/src/audio_extractor.rs new file mode 100644 index 0000000..33d1d11 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/audio_extractor.rs @@ -0,0 +1,190 @@ +use serde::{Deserialize, Serialize}; +use std::path::Path; +use tokio::process::Command; + +/// Find ffmpeg executable using multiple strategies. +async fn find_ffmpeg() -> Result { + // 1. Check FFMPEG_PATH env var + if let Ok(path) = std::env::var("FFMPEG_PATH") { + if tokio::fs::metadata(&path).await.is_ok() { + return Ok(path); + } + } + + // 2. Cross-platform PATH lookup + let is_windows = std::env::consts::OS == "windows"; + let lookup_cmd = if is_windows { "where" } else { "which" }; + let binaries = if is_windows { + vec!["ffmpeg.exe", "ffmpeg"] + } else { + vec!["ffmpeg"] + }; + + for bin in &binaries { + if let Ok(output) = Command::new(lookup_cmd).arg(bin).output().await { + if output.status.success() { + let path = String::from_utf8_lossy(&output.stdout) + .lines() + .next() + .unwrap_or("") + .trim() + .to_string(); + if !path.is_empty() && tokio::fs::metadata(&path).await.is_ok() { + return Ok(path); + } + } + } + } + + // 3. Common installation paths + let common_paths = if is_windows { + vec![ + r"C:\ffmpeg\bin\ffmpeg.exe", + r"C:\Program Files\ffmpeg\bin\ffmpeg.exe", + r"C:\ProgramData\chocolatey\bin\ffmpeg.exe", + ] + } else if std::env::consts::OS == "macos" { + vec![ + "/opt/homebrew/bin/ffmpeg", + "/usr/local/bin/ffmpeg", + "/usr/bin/ffmpeg", + "~/.local/bin/ffmpeg", + ] + } else { + vec![ + "/usr/bin/ffmpeg", + "/usr/local/bin/ffmpeg", + "/bin/ffmpeg", + "~/.local/bin/ffmpeg", + ] + }; + + for path in &common_paths { + let expanded = shellexpand::tilde(path).to_string(); + if tokio::fs::metadata(&expanded).await.is_ok() { + return Ok(expanded); + } + } + + Err(ffmpeg_install_hint()) +} + +fn ffmpeg_install_hint() -> String { + let os = std::env::consts::OS; + let hint = match os { + "macos" => { + "ffmpeg not found.\n\n\ + Install hints for macOS:\n\ + • Homebrew: brew install ffmpeg\n\ + • MacPorts: sudo port install ffmpeg\n\n\ + After installing, ensure ffmpeg is in your PATH." + } + "windows" => { + "ffmpeg not found.\n\n\ + Install hints for Windows:\n\ + • WinGet: winget install Gyan.FFmpeg\n\ + • Chocolatey: choco install ffmpeg\n\ + • Manual: download from https://ffmpeg.org/download.html\n\n\ + After installing, ensure ffmpeg is in your PATH." + } + _ => { + "ffmpeg not found.\n\n\ + Install hints for Linux:\n\ + • apt: sudo apt install ffmpeg\n\ + • dnf: sudo dnf install ffmpeg\n\ + • pacman: sudo pacman -S ffmpeg\n\n\ + After installing, ensure ffmpeg is in your PATH." + } + }; + hint.to_string() +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExtractAudioOptions { + pub video_path: String, + pub output_dir: Option, + pub audio_format: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ExtractAudioResult { + pub audio_path: String, +} + +/// Extract audio from a video file using ffmpeg. +pub async fn extract_audio(options: ExtractAudioOptions) -> Result { + let ffmpeg_path = find_ffmpeg().await?; + + let video_path = Path::new(&options.video_path); + if !video_path.exists() { + return Err(format!("Video file not found: {}", options.video_path)); + } + + // Determine output directory + let output_dir = options + .output_dir + .or_else(|| { + video_path + .parent() + .map(|p| p.to_string_lossy().to_string()) + }) + .unwrap_or_else(|| ".".to_string()); + + // Ensure output directory exists + tokio::fs::create_dir_all(&output_dir) + .await + .map_err(|e| format!("Failed to create output directory: {}", e))?; + + // Determine output filename + let stem = video_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("audio"); + + let audio_format = options.audio_format.unwrap_or_else(|| "mp3".to_string()); + let (ext, codec_args): (&str, Vec) = match audio_format.as_str() { + "mp3" => ("mp3", vec!["-c:a".to_string(), "libmp3lame".to_string(), "-q:a".to_string(), "2".to_string()]), + "m4a" | "aac" => ("m4a", vec!["-c:a".to_string(), "aac".to_string(), "-b:a".to_string(), "192k".to_string()]), + "flac" => ("flac", vec!["-c:a".to_string(), "flac".to_string()]), + "wav" => ("wav", vec!["-c:a".to_string(), "pcm_s16le".to_string()]), + "ogg" => ("ogg", vec!["-c:a".to_string(), "libvorbis".to_string(), "-q:a".to_string(), "4".to_string()]), + "opus" => ("opus", vec!["-c:a".to_string(), "libopus".to_string(), "-b:a".to_string(), "128k".to_string()]), + _ => ("mp3", vec!["-c:a".to_string(), "libmp3lame".to_string(), "-q:a".to_string(), "2".to_string()]), + }; + + let output_path = format!("{}/{}.{}", output_dir, stem, ext); + + // Build ffmpeg command + let mut args = vec![ + "-i".to_string(), + options.video_path.clone(), + "-vn".to_string(), // no video + "-y".to_string(), // overwrite output + ]; + args.extend(codec_args); + args.push(output_path.clone()); + + log::info!("Running ffmpeg: {} {}", ffmpeg_path, args.join(" ")); + + let output = Command::new(&ffmpeg_path) + .args(&args) + .output() + .await + .map_err(|e| format!("Failed to execute ffmpeg: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!( + "ffmpeg failed: {}", + if stderr.is_empty() { + "Unknown error".to_string() + } else { + stderr.to_string() + } + )); + } + + log::info!("Audio extracted successfully: {}", output_path); + + Ok(ExtractAudioResult { audio_path: output_path }) +} diff --git a/apps/contentforge-desktop/src-tauri/src/commands.rs b/apps/contentforge-desktop/src-tauri/src/commands.rs deleted file mode 100644 index d405557..0000000 --- a/apps/contentforge-desktop/src-tauri/src/commands.rs +++ /dev/null @@ -1,195 +0,0 @@ -use serde::{Deserialize, Serialize}; -use tauri::State; - -use crate::database::Database; - -// ─────────────────────────── Chat Commands ─────────────────────────── - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChatSession { - pub id: String, - pub title: String, - pub agent_id: String, - pub status: String, - pub created_at: String, - pub updated_at: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChatMessage { - pub id: String, - pub session_id: String, - pub role: String, - pub content: String, - pub status: String, - pub created_at: String, - pub updated_at: String, -} - -#[tauri::command] -pub async fn get_chat_sessions(db: State<'_, Database>) -> Result, String> { - // Placeholder implementation - Ok(vec![]) -} - -#[tauri::command] -pub async fn create_chat_session( - db: State<'_, Database>, - session_id: String, - agent_id: String, - title: String, -) -> Result<(), String> { - // Placeholder implementation - Ok(()) -} - -#[tauri::command] -pub async fn get_chat_history( - db: State<'_, Database>, - session_id: String, - cursor: Option, -) -> Result, String> { - // Placeholder implementation - Ok(vec![]) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChatSendRequest { - pub session_id: String, - pub message: String, - pub agent_id: Option, - pub selected_asset_ids: Option>, - pub streaming: Option, -} - -#[tauri::command] -pub async fn chat_send( - db: State<'_, Database>, - request: ChatSendRequest, -) -> Result { - // Placeholder implementation - Ok(serde_json::json!({ - "messageId": uuid::Uuid::new_v4().to_string(), - "status": "accepted" - })) -} - -#[tauri::command] -pub async fn cancel_chat_stream(db: State<'_, Database>, message_id: String) -> Result<(), String> { - // Placeholder implementation - Ok(()) -} - -// ─────────────────────────── Agent Commands ─────────────────────────── - -#[derive(Debug, Serialize, Deserialize)] -pub struct AgentRole { - pub id: String, - pub name: String, - pub description: String, - pub capabilities: Vec, - pub model: String, - pub color: String, - pub icon: String, - pub order: i32, -} - -#[tauri::command] -pub async fn get_agents(db: State<'_, Database>) -> Result, String> { - // Placeholder implementation - return default agents - Ok(vec![ - AgentRole { - id: "general".to_string(), - name: "通用助手".to_string(), - description: "ContentForge 通用助手".to_string(), - capabilities: vec!["general".to_string(), "search".to_string()], - model: "gpt-4o-mini".to_string(), - color: "#6366f1".to_string(), - icon: "bot".to_string(), - order: 0, - }, - AgentRole { - id: "content_analyst".to_string(), - name: "内容分析师".to_string(), - description: "分析内容结构、提取要点".to_string(), - capabilities: vec!["analyze".to_string(), "search".to_string()], - model: "gpt-4o".to_string(), - color: "#0ea5e9".to_string(), - icon: "microscope".to_string(), - order: 1, - }, - ]) -} - -#[tauri::command] -pub async fn switch_agent( - db: State<'_, Database>, - from_agent_id: String, - to_agent_id: String, - triggered_by: String, - reason: Option, -) -> Result<(), String> { - // Placeholder implementation - Ok(()) -} - -// ─────────────────────────── Asset Commands ─────────────────────────── - -#[derive(Debug, Serialize, Deserialize)] -pub struct ContentAsset { - pub id: String, - pub title: String, - pub asset_type: String, - pub status: String, - pub platform: String, - pub url: String, - pub created_at: String, - pub updated_at: String, -} - -#[tauri::command] -pub async fn search_assets( - db: State<'_, Database>, - filter: Option, - sort: Option, - pagination: Option, -) -> Result { - // Placeholder implementation - Ok(serde_json::json!({ - "assets": [], - "total": 0, - "page": 1, - "pageSize": 20, - "hasMore": false - })) -} - -#[tauri::command] -pub async fn get_asset_detail( - db: State<'_, Database>, - asset_id: String, -) -> Result { - // Placeholder implementation - Err("Asset not found".to_string()) -} - -// ─────────────────────────── Settings Commands ─────────────────────────── - -#[tauri::command] -pub async fn get_settings(db: State<'_, Database>) -> Result { - // Placeholder implementation - Ok(serde_json::json!({ - "language": "zh", - "theme": "dark", - "maxConcurrent": 3 - })) -} - -#[tauri::command] -pub async fn update_settings( - db: State<'_, Database>, - settings: serde_json::Value, -) -> Result<(), String> { - // Placeholder implementation - Ok(()) -} diff --git a/apps/contentforge-desktop/src-tauri/src/commands/agent.rs b/apps/contentforge-desktop/src-tauri/src/commands/agent.rs new file mode 100644 index 0000000..c4ee34f --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/agent.rs @@ -0,0 +1,257 @@ +use serde::{Deserialize, Serialize}; +use tauri::State; + +use crate::db::{AgentSwitchRecord, Database}; +use crate::commands::ApiResponse; + +// ─────────────────────────── Agent Types ─────────────────────────── + +#[derive(Debug, Serialize)] +pub struct AgentRoleOut { + pub id: String, + pub name: String, + pub description: String, + pub system_prompt: String, + pub capabilities: Vec, + pub tools: Vec, + pub model: String, + pub temperature: f64, + pub max_tokens: i32, + pub context_window: i32, + pub icon: String, + pub color: String, + pub auto_switch: bool, + pub streaming: bool, + pub requires_context: bool, + pub order: i32, +} + +#[derive(Debug, Serialize)] +pub struct QuickActionOut { + pub id: String, + pub agent_id: String, + pub label: String, + pub description: String, + pub prompt_template: String, + pub icon: String, +} + +#[derive(Debug, Serialize)] +pub struct SkillOut { + pub id: String, + pub name: String, + pub description: String, + pub metadata: serde_json::Value, + pub content: String, + pub triggers: Vec, +} + +// ─────────────────────────── Agent Commands ─────────────────────────── + +fn builtin_agents() -> Vec { + vec![ + AgentRoleOut { + id: "general".to_string(), + name: "通用助手".to_string(), + description: "ContentForge 通用助手,帮助用户管理和处理内容".to_string(), + system_prompt: "你是 ContentForge 的通用助手。你帮助用户管理内容资产、执行内容处理任务、导航应用功能。当用户请求需要专业分析时,你会建议切换到对应的专家 Agent。".to_string(), + capabilities: vec!["general".to_string(), "search".to_string()], + tools: vec!["search_assets".to_string(), "get_asset_detail".to_string(), "list_sessions".to_string()], + model: "gpt-4o-mini".to_string(), + temperature: 0.7, + max_tokens: 4000, + context_window: 128000, + icon: "bot".to_string(), + color: "#6366f1".to_string(), + auto_switch: false, + streaming: true, + requires_context: false, + order: 0, + }, + AgentRoleOut { + id: "content_analyst".to_string(), + name: "内容分析师".to_string(), + description: "分析内容结构、提取要点、情感分析".to_string(), + system_prompt: "你是内容分析专家,擅长从文本/视频中提取结构化洞察。你能分析主题、关键词、情感倾向、内容质量,并给出结构化的分析报告。".to_string(), + capabilities: vec!["analyze".to_string(), "search".to_string()], + tools: vec!["analyze".to_string(), "extract_keywords".to_string(), "detect_language".to_string()], + model: "gpt-4o".to_string(), + temperature: 0.3, + max_tokens: 4000, + context_window: 128000, + icon: "microscope".to_string(), + color: "#0ea5e9".to_string(), + auto_switch: true, + streaming: true, + requires_context: true, + order: 1, + }, + AgentRoleOut { + id: "summarizer".to_string(), + name: "摘要专家".to_string(), + description: "生成多风格摘要".to_string(), + system_prompt: "你是摘要专家,擅长将长内容转化为精炼的要点。你支持多种摘要风格:结构化、简洁、详细、要点列表、执行摘要。".to_string(), + capabilities: vec!["summarize".to_string(), "search".to_string()], + tools: vec!["summarize".to_string(), "chunk_text".to_string()], + model: "gpt-4o-mini".to_string(), + temperature: 0.5, + max_tokens: 4000, + context_window: 128000, + icon: "scroll-text".to_string(), + color: "#8b5cf6".to_string(), + auto_switch: true, + streaming: true, + requires_context: true, + order: 2, + }, + AgentRoleOut { + id: "rewriter".to_string(), + name: "改写专家".to_string(), + description: "改写风格、翻译、润色".to_string(), + system_prompt: "你是文案改写专家,能根据不同平台调性调整内容。你支持专业、casual、幽默、学术、营销等多种风格,也支持中英日翻译。".to_string(), + capabilities: vec!["rewrite".to_string(), "translate".to_string(), "search".to_string()], + tools: vec!["rewrite".to_string(), "translate".to_string(), "xiaohongshu_convert".to_string()], + model: "gpt-4o".to_string(), + temperature: 0.8, + max_tokens: 4000, + context_window: 128000, + icon: "pen-tool".to_string(), + color: "#ec4899".to_string(), + auto_switch: true, + streaming: true, + requires_context: true, + order: 3, + }, + AgentRoleOut { + id: "publisher".to_string(), + name: "发布助手".to_string(), + description: "格式转换、发布准备".to_string(), + system_prompt: "你是发布专家,负责将内容转化为各平台可用格式。你支持 Markdown、小红书、JSON 等格式导出,并确保内容符合平台规范。".to_string(), + capabilities: vec!["publish".to_string(), "search".to_string()], + tools: vec!["publish".to_string(), "generate_markdown".to_string(), "generate_xhs".to_string()], + model: "gpt-4o-mini".to_string(), + temperature: 0.6, + max_tokens: 4000, + context_window: 128000, + icon: "send".to_string(), + color: "#10b981".to_string(), + auto_switch: true, + streaming: true, + requires_context: true, + order: 4, + }, + AgentRoleOut { + id: "pipeline_runner".to_string(), + name: "流水线执行器".to_string(), + description: "执行预设 Pipeline".to_string(), + system_prompt: "你是流水线调度员,负责执行和管理内容处理 Pipeline。你了解所有预设流程,能根据用户需求选择最佳流程。".to_string(), + capabilities: vec!["pipeline".to_string(), "search".to_string()], + tools: vec!["run_pipeline".to_string(), "list_presets".to_string()], + model: "gpt-4o-mini".to_string(), + temperature: 0.3, + max_tokens: 4000, + context_window: 128000, + icon: "workflow".to_string(), + color: "#f59e0b".to_string(), + auto_switch: true, + streaming: true, + requires_context: true, + order: 5, + }, + ] +} + +fn builtin_quick_actions() -> Vec { + vec![ + QuickActionOut { + id: "summarize".to_string(), + agent_id: "summarizer".to_string(), + label: "生成摘要".to_string(), + description: "为选中的内容生成结构化摘要".to_string(), + prompt_template: "请为以下内容生成摘要:\n\n{{asset_content}}".to_string(), + icon: "scroll-text".to_string(), + }, + QuickActionOut { + id: "rewrite-xhs".to_string(), + agent_id: "rewriter".to_string(), + label: "转小红书".to_string(), + description: "将内容改写为小红书风格".to_string(), + prompt_template: "请将以下内容改写为小红书风格的文案:\n\n{{asset_content}}".to_string(), + icon: "pen-tool".to_string(), + }, + QuickActionOut { + id: "analyze".to_string(), + agent_id: "content_analyst".to_string(), + label: "分析内容".to_string(), + description: "分析内容的主题、情感和关键词".to_string(), + prompt_template: "请分析以下内容:\n\n{{asset_content}}".to_string(), + icon: "microscope".to_string(), + }, + ] +} + +#[tauri::command] +pub async fn get_agents() -> Result>, String> { + Ok(ApiResponse::ok(builtin_agents())) +} + +#[derive(Debug, Deserialize)] +pub struct SwitchAgentRequest { + pub from_agent_id: String, + pub to_agent_id: String, + pub triggered_by: String, + pub reason: Option, +} + +#[tauri::command] +pub async fn switch_agent( + db: State<'_, Database>, + request: SwitchAgentRequest, +) -> Result, String> { + let record = AgentSwitchRecord { + id: uuid::Uuid::new_v4().to_string(), + session_id: "".to_string(), + from_agent_id: request.from_agent_id, + to_agent_id: request.to_agent_id.clone(), + reason: request.reason, + triggered_by: request.triggered_by, + created_at: chrono::Utc::now(), + }; + + match db.record_agent_switch(&record).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to record switch: {}", e))), + } +} + +#[tauri::command] +pub async fn get_quick_actions() -> Result>, String> { + Ok(ApiResponse::ok(builtin_quick_actions())) +} + +#[tauri::command] +pub async fn get_skills() -> Result>, String> { + Ok(ApiResponse::ok(vec![])) +} + +#[derive(Debug, Deserialize)] +pub struct ExecuteSkillRequest { + pub skill_id: String, + pub params: serde_json::Value, +} + +#[derive(Debug, Serialize)] +pub struct SkillExecutionResult { + pub skill_id: String, + pub status: String, + pub output: Option, + pub error: Option, + pub duration_ms: u64, +} + +#[tauri::command] +pub async fn execute_skill( + _request: ExecuteSkillRequest, +) -> Result, String> { + Ok(ApiResponse::err("Skill execution not yet implemented".to_string())) +} diff --git a/apps/contentforge-desktop/src-tauri/src/commands/ai.rs b/apps/contentforge-desktop/src-tauri/src/commands/ai.rs new file mode 100644 index 0000000..cddd6f8 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/ai.rs @@ -0,0 +1,168 @@ +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, State}; + +use crate::commands::ApiResponse; +use crate::db::{Database, VttReport}; + +// ─────────────────────────── AI Types ─────────────────────────── + +#[derive(Debug, Deserialize)] +pub struct SummarizeRequest { + pub video_id: String, + pub prompt: Option, +} + +#[derive(Debug, Serialize)] +pub struct SummaryResult { + pub markdown: String, + pub key_points: Vec, +} + +#[derive(Debug, Serialize)] +pub struct AnalyzeVttResult { + #[serde(rename = "reportId")] + pub report_id: String, +} + +#[derive(Debug, Serialize)] +pub struct ListVttReportsResult { + pub reports: Vec, + pub total: i64, +} + +// ─────────────────────────── AI Commands ─────────────────────────── + +#[tauri::command] +pub async fn summarize_video( + _db: State<'_, Database>, + _request: SummarizeRequest, +) -> Result, String> { + let summary = SummaryResult { + markdown: "# Summary\n\nComing soon...".to_string(), + key_points: vec![], + }; + Ok(ApiResponse::ok(summary)) +} + +#[tauri::command] +pub async fn extract_audio( + request: crate::audio_extractor::ExtractAudioOptions, +) -> Result, String> { + match crate::audio_extractor::extract_audio(request).await { + Ok(result) => Ok(ApiResponse::ok(result)), + Err(e) => Ok(ApiResponse::err(e)), + } +} + +#[tauri::command] +pub async fn analyze_vtt( + app: AppHandle, + db: State<'_, Database>, + url: String, +) -> Result, String> { + if url.trim().is_empty() { + return Ok(ApiResponse::err("url is required".to_string())); + } + + let report_id = crate::vtt_analysis::start_analysis(app, db.inner().clone(), url) + .await + .map_err(|e| e.to_string())?; + + Ok(ApiResponse::ok(AnalyzeVttResult { report_id })) +} + +#[tauri::command] +pub async fn get_vtt_report( + db: State<'_, Database>, + id: String, +) -> Result, String> { + match db.get_vtt_report(&id).await { + Ok(Some(report)) => Ok(ApiResponse::ok(report)), + Ok(None) => Ok(ApiResponse::err("Report not found".to_string())), + Err(e) => Ok(ApiResponse::err(e.to_string())), + } +} + +#[tauri::command] +pub async fn list_vtt_reports( + db: State<'_, Database>, + page: Option, + limit: Option, + lang: Option, +) -> Result, String> { + let page = page.unwrap_or(1).max(1); + let limit = limit.unwrap_or(20).clamp(1, 100); + + match db.list_vtt_reports(page, limit, lang.as_deref()).await { + Ok((reports, total)) => Ok(ApiResponse::ok(ListVttReportsResult { reports, total })), + Err(e) => Ok(ApiResponse::err(e.to_string())), + } +} + +#[tauri::command] +pub async fn delete_vtt_report( + db: State<'_, Database>, + id: String, +) -> Result, String> { + match db.delete_vtt_report(&id).await { + Ok(()) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(e.to_string())), + } +} + +#[tauri::command] +pub async fn agent_chat_send( + app: AppHandle, + db: State<'_, Database>, + session_id: String, + message: String, + agent_id: String, + context: Vec, +) -> Result, String> { + if message.trim().is_empty() { + return Ok(ApiResponse::err("message is required".to_string())); + } + + if agent_id != "kimi" { + return Ok(ApiResponse::err(format!("Unsupported agent: {agent_id}"))); + } + + let kimi_bin = db + .get_setting("agent_cli_kimi_bin") + .await + .ok() + .flatten() + .filter(|p| !p.trim().is_empty()); + + let kimi_bin = if let Some(path) = kimi_bin { + path + } else { + let detection = crate::agent_cli::detect_agent_cli_tools(None, None); + detection + .kimi + .path + .ok_or_else(|| "Kimi CLI not found. Configure it in Settings → Agent CLI.".to_string())? + }; + + let prompt = crate::agent_runner::build_prompt(&message, &context); + let project_root = crate::agent_runner::find_project_root(); + + crate::agent_runner::spawn_kimi_chat( + app, + session_id, + kimi_bin, + prompt, + project_root, + ); + + Ok(ApiResponse::ok(())) +} + +#[tauri::command] +pub async fn detect_agent_cli( + kimi_bin: Option, + other_bin: Option, +) -> Result, String> { + let result = crate::agent_cli::detect_agent_cli_tools(kimi_bin, other_bin); + Ok(ApiResponse::ok(result)) +} diff --git a/apps/contentforge-desktop/src-tauri/src/commands/asset.rs b/apps/contentforge-desktop/src-tauri/src/commands/asset.rs new file mode 100644 index 0000000..6b3396a --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/asset.rs @@ -0,0 +1,272 @@ +use serde::{Deserialize, Serialize}; +use tauri::State; + +use crate::db::{Asset, AssetStatus, Database}; +use crate::commands::ApiResponse; + +// ─────────────────────────── Asset Types ─────────────────────────── + +#[derive(Debug, Serialize)] +pub struct ContentAssetOut { + pub id: String, + pub asset_type: String, + pub title: String, + pub description: Option, + pub source: AssetSourceOut, + pub extracted_text: Option, + pub summary: Option, + pub transcript: Option, + pub translated_text: Option, + pub rewritten_text: Option, + pub file_path: Option, + pub thumbnail_url: Option, + pub duration_sec: Option, + pub analysis: Option, + pub status: String, + pub tags: Vec, + pub pipeline_id: Option, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Debug, Serialize)] +pub struct AssetSourceOut { + pub platform: String, + pub url: String, + pub author: Option, + pub published_at: Option, + pub engagement: Option, +} + +#[derive(Debug, Deserialize)] +pub struct SearchAssetsRequest { + pub filter: Option, + pub sort: Option, + pub pagination: Option, +} + +#[derive(Debug, Deserialize)] +pub struct GetAssetDetailRequest { + pub asset_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct DeleteAssetRequest { + pub asset_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateAssetTagsRequest { + pub asset_id: String, + pub tags: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct AddAssetToSessionRequest { + pub asset_id: String, + pub session_id: String, +} + +// ─────────────────────────── Helpers ─────────────────────────── + +fn asset_to_out(a: Asset) -> ContentAssetOut { + let platform = a.platform.clone().unwrap_or_else(|| "unknown".to_string()); + let url = a.url.clone().unwrap_or_default(); + ContentAssetOut { + id: a.id.clone(), + asset_type: a.asset_type.clone(), + title: a.title, + description: a.description, + source: AssetSourceOut { + platform: platform.clone(), + url, + author: a.author.clone(), + published_at: a.published_at.clone(), + engagement: a.engagement.as_ref().and_then(|e| serde_json::from_str(e).ok()), + }, + extracted_text: a.extracted_text, + summary: a.summary, + transcript: a.transcript, + translated_text: a.translated_text, + rewritten_text: a.rewritten_text, + file_path: a.file_path, + thumbnail_url: a.thumbnail_url, + duration_sec: a.duration_sec, + analysis: a.analysis.as_ref().and_then(|a| serde_json::from_str(a).ok()), + status: match a.status { + AssetStatus::Ingested => "ingested".to_string(), + AssetStatus::Processing => "processing".to_string(), + AssetStatus::Processed => "processed".to_string(), + AssetStatus::Ready => "ready".to_string(), + AssetStatus::Published => "published".to_string(), + AssetStatus::Failed => "failed".to_string(), + }, + tags: serde_json::from_str(&a.tags).unwrap_or_default(), + pipeline_id: a.pipeline_id, + created_at: a.created_at.to_rfc3339(), + updated_at: a.updated_at.to_rfc3339(), + } +} + +// ─────────────────────────── Asset Commands ─────────────────────────── + +#[tauri::command] +pub async fn search_assets( + db: State<'_, Database>, + request: SearchAssetsRequest, +) -> Result, String> { + let filter = request.filter.as_ref(); + let query = filter + .and_then(|f| f.get("query")) + .and_then(|v| v.as_str()); + let asset_type = filter + .and_then(|f| f.get("type")) + .and_then(|v| v.as_str()); + let status = filter + .and_then(|f| f.get("status")) + .and_then(|v| v.as_str()); + let platform = filter + .and_then(|f| f.get("platform")) + .and_then(|v| v.as_str()); + let tags = filter + .and_then(|f| f.get("tags")) + .and_then(|v| v.as_array()) + .and_then(|arr| arr.first()) + .and_then(|v| v.as_str()); + + let sort = request.sort.as_ref(); + let sort_field = sort + .and_then(|s| s.get("field")) + .and_then(|v| v.as_str()) + .unwrap_or("updated_at"); + let sort_order = sort + .and_then(|s| s.get("order")) + .and_then(|v| v.as_str()) + .unwrap_or("desc"); + + let pagination = request.pagination.as_ref(); + let page = pagination + .and_then(|p| p.get("page")) + .and_then(|v| v.as_i64()) + .unwrap_or(1); + let page_size = pagination + .and_then(|p| p.get("pageSize")) + .and_then(|v| v.as_i64()) + .unwrap_or(20) + .clamp(1, 100); + + let safe_sort_field = match sort_field { + "createdAt" => "created_at", + "updatedAt" => "updated_at", + "title" => "title", + _ => "updated_at", + }; + let safe_sort_order = if sort_order == "asc" { "ASC" } else { "DESC" }; + + match db + .search_assets(query, asset_type, status, platform, tags, safe_sort_field, safe_sort_order, page, page_size) + .await + { + Ok((assets, total)) => { + let out: Vec = assets.into_iter().map(asset_to_out).collect(); + let has_more = out.len() as i64 >= page_size; + Ok(ApiResponse::ok(serde_json::json!({ + "assets": out, + "total": total, + "page": page, + "pageSize": page_size, + "hasMore": has_more, + }))) + } + Err(e) => Ok(ApiResponse::err(format!("Failed to search assets: {}", e))), + } +} + +#[tauri::command] +pub async fn get_asset_detail( + db: State<'_, Database>, + request: GetAssetDetailRequest, +) -> Result, String> { + match db.get_asset_by_id(&request.asset_id).await { + Ok(Some(asset)) => Ok(ApiResponse::ok(asset_to_out(asset))), + Ok(None) => Ok(ApiResponse::err("Asset not found".to_string())), + Err(e) => Ok(ApiResponse::err(format!("Failed to get asset: {}", e))), + } +} + +#[tauri::command] +pub async fn delete_asset( + db: State<'_, Database>, + request: DeleteAssetRequest, +) -> Result, String> { + match db.delete_asset(&request.asset_id).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to delete asset: {}", e))), + } +} + +#[tauri::command] +pub async fn update_asset_tags( + db: State<'_, Database>, + request: UpdateAssetTagsRequest, +) -> Result, String> { + let tags_json = serde_json::to_string(&request.tags).unwrap_or_else(|_| "[]".to_string()); + match db.update_asset_tags(&request.asset_id, &tags_json).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to update tags: {}", e))), + } +} + +#[tauri::command] +pub async fn add_asset_to_session( + db: State<'_, Database>, + request: AddAssetToSessionRequest, +) -> Result, String> { + match db.get_session_by_id(&request.session_id).await { + Ok(Some(session)) => { + let mut linked: Vec = serde_json::from_str(&session.linked_asset_ids).unwrap_or_default(); + if !linked.contains(&request.asset_id) { + linked.push(request.asset_id); + let linked_json = serde_json::to_string(&linked).unwrap_or_else(|_| "[]".to_string()); + match db + .update_session(&request.session_id, |s| { + s.linked_asset_ids = linked_json; + }) + .await + { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to update session: {}", e))), + } + } else { + Ok(ApiResponse::ok(())) + } + } + Ok(None) => Ok(ApiResponse::err("Session not found".to_string())), + Err(e) => Ok(ApiResponse::err(format!("Database error: {}", e))), + } +} + +#[tauri::command] +pub async fn get_asset_groups( + db: State<'_, Database>, +) -> Result, String> { + match db.get_asset_groups().await { + Ok(groups) => { + let out: Vec = groups + .into_iter() + .map(|(group_type, value, count)| { + serde_json::json!({ + "id": format!("{}-{}", group_type, value), + "label": value, + "type": group_type, + "value": value, + "assetIds": [], + "count": count, + }) + }) + .collect(); + Ok(ApiResponse::ok(serde_json::json!({ "groups": out }))) + } + Err(e) => Ok(ApiResponse::err(format!("Failed to get groups: {}", e))), + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/commands/chat.rs b/apps/contentforge-desktop/src-tauri/src/commands/chat.rs new file mode 100644 index 0000000..888ee53 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/chat.rs @@ -0,0 +1,489 @@ +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter, State}; + +use crate::db::{Database, Message, MessageStatus, Session, SessionStatus}; +use crate::commands::ApiResponse; + +// ─────────────────────────── Chat Types ─────────────────────────── + +#[derive(Debug, Serialize)] +pub struct ChatSessionOut { + pub id: String, + pub title: String, + pub agent_id: String, + pub status: String, + pub linked_task_id: Option, + pub linked_asset_ids: Vec, + pub metadata: Option, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Debug, Serialize)] +pub struct ChatMessageOut { + pub id: String, + pub session_id: String, + pub role: String, + pub content: String, + pub status: String, + pub model: Option, + pub tokens_used: Option, + pub tool_calls: Option>, + pub tool_results: Option>, + pub selected_asset_ids: Option>, + pub error: Option, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Debug, Deserialize)] +pub struct CreateChatSessionRequest { + pub session_id: String, + pub agent_id: String, + pub title: String, +} + +#[derive(Debug, Deserialize)] +pub struct GetChatHistoryRequest { + pub session_id: String, + pub cursor: Option, +} + +#[derive(Debug, Deserialize)] +pub struct ChatSendRequest { + pub session_id: String, + pub message: String, + pub agent_id: Option, + pub selected_asset_ids: Option>, + pub streaming: Option, +} + +#[derive(Debug, Serialize)] +pub struct ChatSendResponse { + pub message_id: String, + pub session_id: String, + pub status: String, +} + +#[derive(Debug, Deserialize)] +pub struct CancelChatStreamRequest { + pub message_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct ArchiveSessionRequest { + pub session_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct PinSessionRequest { + pub session_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateSessionTitleRequest { + pub session_id: String, + pub title: String, +} + +#[derive(Debug, Deserialize)] +pub struct DeleteSessionRequest { + pub session_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct ChatRetryRequest { + pub session_id: String, + pub message_id: String, + pub message: String, + pub selected_asset_ids: Option>, +} + +#[derive(Debug, Deserialize)] +pub struct DeleteMessageRequest { + pub session_id: String, + pub message_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct ConfirmToolCallRequest { + pub message_id: String, + pub call_id: String, + pub approved: bool, +} + +// ─────────────────────────── Helpers ─────────────────────────── + +fn session_to_out(s: Session) -> ChatSessionOut { + ChatSessionOut { + id: s.id, + title: s.title, + agent_id: s.agent_id, + status: match s.status { + SessionStatus::Active => "active".to_string(), + SessionStatus::Archived => "archived".to_string(), + SessionStatus::Pinned => "pinned".to_string(), + }, + linked_task_id: s.linked_task_id, + linked_asset_ids: serde_json::from_str(&s.linked_asset_ids).unwrap_or_default(), + metadata: s.metadata.and_then(|m| serde_json::from_str(&m).ok()), + created_at: s.created_at.to_rfc3339(), + updated_at: s.updated_at.to_rfc3339(), + } +} + +fn message_to_out(m: Message) -> ChatMessageOut { + ChatMessageOut { + id: m.id.clone(), + session_id: m.session_id, + role: m.role, + content: m.content, + status: match m.status { + MessageStatus::Sending => "sending".to_string(), + MessageStatus::Streaming => "streaming".to_string(), + MessageStatus::Completed => "completed".to_string(), + MessageStatus::Failed => "failed".to_string(), + MessageStatus::Cancelled => "cancelled".to_string(), + }, + model: m.model, + tokens_used: m.tokens_used.and_then(|t| serde_json::from_str(&t).ok()), + tool_calls: m.tool_calls.and_then(|t| serde_json::from_str(&t).ok()), + tool_results: m.tool_results.and_then(|t| serde_json::from_str(&t).ok()), + selected_asset_ids: m.selected_asset_ids.and_then(|a| serde_json::from_str(&a).ok()), + error: m.error, + created_at: m.created_at.to_rfc3339(), + updated_at: m.updated_at.to_rfc3339(), + } +} + +// ─────────────────────────── Chat Commands ─────────────────────────── + +#[tauri::command] +pub async fn get_chat_sessions( + db: State<'_, Database>, +) -> Result>, String> { + match db.get_sessions().await { + Ok(sessions) => { + let out: Vec = sessions.into_iter().map(session_to_out).collect(); + Ok(ApiResponse::ok(out)) + } + Err(e) => Ok(ApiResponse::err(format!("Failed to get sessions: {}", e))), + } +} + +#[tauri::command] +pub async fn create_chat_session( + db: State<'_, Database>, + request: CreateChatSessionRequest, +) -> Result, String> { + let now = chrono::Utc::now(); + let session = Session { + id: request.session_id.clone(), + title: request.title, + agent_id: request.agent_id, + status: SessionStatus::Active, + linked_task_id: None, + linked_asset_ids: "[]".to_string(), + metadata: None, + created_at: now, + updated_at: now, + }; + + match db.create_session(&session).await { + Ok(_) => Ok(ApiResponse::ok(session_to_out(session))), + Err(e) => Ok(ApiResponse::err(format!("Failed to create session: {}", e))), + } +} + +#[tauri::command] +pub async fn get_chat_history( + db: State<'_, Database>, + request: GetChatHistoryRequest, +) -> Result, String> { + let page_size = 50; + let offset = request.cursor.and_then(|c| c.parse::().ok()).unwrap_or(0); + + match db.get_messages_by_session(&request.session_id, page_size, offset).await { + Ok(messages) => { + let out: Vec = messages.into_iter().map(message_to_out).collect(); + let has_more = out.len() as i64 >= page_size; + let next_cursor = if has_more { + Some((offset + page_size).to_string()) + } else { + None + }; + Ok(ApiResponse::ok(serde_json::json!({ + "messages": out, + "hasMore": has_more, + "nextCursor": next_cursor, + }))) + } + Err(e) => Ok(ApiResponse::err(format!("Failed to get history: {}", e))), + } +} + +#[tauri::command] +pub async fn chat_send( + app: AppHandle, + db: State<'_, Database>, + request: ChatSendRequest, +) -> Result, String> { + if request.message.trim().is_empty() { + return Ok(ApiResponse::err("Message is empty".to_string())); + } + + let user_message_id = uuid::Uuid::new_v4().to_string(); + let assistant_message_id = uuid::Uuid::new_v4().to_string(); + let now = chrono::Utc::now(); + + // Ensure session exists + if db.get_session_by_id(&request.session_id).await.map_err(|e| e.to_string())?.is_none() { + let session = Session { + id: request.session_id.clone(), + title: request.message.chars().take(30).collect::(), + agent_id: request.agent_id.clone().unwrap_or_else(|| "general".to_string()), + status: SessionStatus::Active, + linked_task_id: None, + linked_asset_ids: "[]".to_string(), + metadata: None, + created_at: now, + updated_at: now, + }; + if let Err(e) = db.create_session(&session).await { + return Ok(ApiResponse::err(format!("Failed to create session: {}", e))); + } + } + + // Save user message + let user_msg = Message { + id: user_message_id, + session_id: request.session_id.clone(), + role: "user".to_string(), + content: request.message.clone(), + status: MessageStatus::Completed, + model: None, + tokens_used: None, + tool_calls: None, + tool_results: None, + selected_asset_ids: request.selected_asset_ids.as_ref().map(|ids| serde_json::to_string(ids).unwrap_or_default()), + error: None, + created_at: now, + updated_at: now, + }; + + if let Err(e) = db.create_message(&user_msg).await { + return Ok(ApiResponse::err(format!("Failed to save user message: {}", e))); + } + + // Save assistant placeholder message + let assistant_msg = Message { + id: assistant_message_id.clone(), + session_id: request.session_id.clone(), + role: "assistant".to_string(), + content: "".to_string(), + status: MessageStatus::Streaming, + model: None, + tokens_used: None, + tool_calls: None, + tool_results: None, + selected_asset_ids: None, + error: None, + created_at: now, + updated_at: now, + }; + + if let Err(e) = db.create_message(&assistant_msg).await { + return Ok(ApiResponse::err(format!("Failed to save assistant message: {}", e))); + } + + // Update session title if it's the first message + if let Ok(Some(session)) = db.get_session_by_id(&request.session_id).await { + if session.title == "新会话" || session.title.is_empty() { + let title = request.message.chars().take(30).collect::(); + let _ = db.update_session_title(&request.session_id, &title).await; + } + let _ = db.update_session_agent(&request.session_id, &request.agent_id.clone().unwrap_or_else(|| "general".to_string())).await; + } + + // Emit event to notify frontend that message is accepted + let _ = app.emit( + &format!("chat:accepted:{}", request.session_id), + serde_json::json!({ + "userMessageId": user_msg.id, + "assistantMessageId": assistant_message_id.clone(), + }), + ); + + // Simulate streaming response (placeholder for actual AI integration) + let app_clone = app.clone(); + let db_clone = db.inner().clone(); + let assistant_id = assistant_message_id.clone(); + let message_text = request.message.clone(); + + tokio::spawn(async move { + let response_text = format!("收到: {}", message_text); + let words: Vec<&str> = response_text.split_whitespace().collect(); + let mut accumulated = String::new(); + + for word in words { + tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + accumulated.push_str(word); + accumulated.push(' '); + + let _ = app_clone.emit( + &format!("message:delta:{}", assistant_id), + serde_json::json!({ + "messageId": assistant_id, + "delta": format!("{} ", word), + "accumulated": accumulated.trim(), + }), + ); + } + + let _ = db_clone.update_message_content(&assistant_id, accumulated.trim()).await; + let _ = db_clone.update_message_status(&assistant_id, MessageStatus::Completed).await; + + let _ = app_clone.emit( + &format!("message:completed:{}", assistant_id), + serde_json::json!({ + "messageId": assistant_id, + "content": accumulated.trim(), + }), + ); + }); + + Ok(ApiResponse::ok(ChatSendResponse { + message_id: assistant_message_id, + session_id: request.session_id, + status: "accepted".to_string(), + })) +} + +#[tauri::command] +pub async fn cancel_chat_stream( + db: State<'_, Database>, + request: CancelChatStreamRequest, +) -> Result, String> { + match db.update_message_status(&request.message_id, MessageStatus::Cancelled).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to cancel: {}", e))), + } +} + +#[tauri::command] +pub async fn archive_chat_session( + db: State<'_, Database>, + request: ArchiveSessionRequest, +) -> Result, String> { + match db.update_session_status(&request.session_id, SessionStatus::Archived).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to archive: {}", e))), + } +} + +#[tauri::command] +pub async fn pin_chat_session( + db: State<'_, Database>, + request: PinSessionRequest, +) -> Result, String> { + match db.update_session_status(&request.session_id, SessionStatus::Pinned).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to pin: {}", e))), + } +} + +#[tauri::command] +pub async fn update_chat_session_title( + db: State<'_, Database>, + request: UpdateSessionTitleRequest, +) -> Result, String> { + match db.update_session_title(&request.session_id, &request.title).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to update title: {}", e))), + } +} + +#[tauri::command] +pub async fn delete_chat_session( + db: State<'_, Database>, + request: DeleteSessionRequest, +) -> Result, String> { + match db.delete_session(&request.session_id).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to delete session: {}", e))), + } +} + +#[tauri::command] +pub async fn chat_retry( + app: AppHandle, + db: State<'_, Database>, + request: ChatRetryRequest, +) -> Result, String> { + let assistant_message_id = request.message_id.clone(); + let now = chrono::Utc::now(); + + let _ = db.update_message_content(&assistant_message_id, "").await; + let _ = db.update_message_status(&assistant_message_id, MessageStatus::Streaming).await; + let _ = db.update_message_error(&assistant_message_id, "").await; + + let app_clone = app.clone(); + let db_clone = db.inner().clone(); + let assistant_id = assistant_message_id.clone(); + let message_text = request.message.clone(); + + tokio::spawn(async move { + let response_text = format!("重试回复: {}", message_text); + let words: Vec<&str> = response_text.split_whitespace().collect(); + let mut accumulated = String::new(); + + for word in words { + tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; + accumulated.push_str(word); + accumulated.push(' '); + + let _ = app_clone.emit( + &format!("message:delta:{}", assistant_id), + serde_json::json!({ + "messageId": assistant_id, + "delta": format!("{} ", word), + }), + ); + } + + let _ = db_clone.update_message_content(&assistant_id, accumulated.trim()).await; + let _ = db_clone.update_message_status(&assistant_id, MessageStatus::Completed).await; + + let _ = app_clone.emit( + &format!("message:completed:{}", assistant_id), + serde_json::json!({ + "messageId": assistant_id, + "content": accumulated.trim(), + }), + ); + }); + + Ok(ApiResponse::ok(ChatSendResponse { + message_id: assistant_message_id, + session_id: request.session_id, + status: "accepted".to_string(), + })) +} + +#[tauri::command] +pub async fn delete_chat_message( + db: State<'_, Database>, + request: DeleteMessageRequest, +) -> Result, String> { + match db.delete_message(&request.message_id).await { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to delete message: {}", e))), + } +} + +#[tauri::command] +pub async fn confirm_tool_call( + _request: ConfirmToolCallRequest, +) -> Result, String> { + Ok(ApiResponse::ok(())) +} diff --git a/apps/contentforge-desktop/src-tauri/src/commands/download.rs b/apps/contentforge-desktop/src-tauri/src/commands/download.rs new file mode 100644 index 0000000..64293e2 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/download.rs @@ -0,0 +1,226 @@ +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, State}; + +use crate::commands::ApiResponse; +use crate::db::{Database, DownloadRecord, DownloadStatus}; +use crate::downloader::DownloadOptions; +use crate::queue::QueueManager; + +// ─────────────────────────── Download Commands ─────────────────────────── + +#[derive(Debug, Serialize, Deserialize)] +pub struct StartDownloadRequest { + pub url: String, + pub is_playlist: bool, + pub quality: Option, + pub format: Option, + pub output_dir: Option, + pub sub_langs: Option>, + pub write_subs: Option, + pub write_auto_subs: Option, + pub start_time: Option, + pub end_time: Option, +} + +#[tauri::command] +pub async fn start_download( + app: AppHandle, + db: State<'_, Database>, + queue: State<'_, QueueManager>, + request: StartDownloadRequest, +) -> Result, String> { + let download_id = uuid::Uuid::new_v4().to_string(); + + let options_json = serde_json::to_string(&request).unwrap_or_default(); + + let record = DownloadRecord { + id: download_id.clone(), + url: request.url.clone(), + title: None, + status: DownloadStatus::Pending, + progress: 0.0, + speed: None, + eta: None, + output_dir: request.output_dir.clone(), + filename: None, + subtitles: vec![], + error: None, + queue_position: 0, + options: Some(options_json), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + }; + + if let Err(e) = db.create_download(record).await { + return Ok(ApiResponse::err(format!("Failed to create download: {}", e))); + } + + let mut yt_dlp_path = db.get_setting("yt_dlp_path").await.unwrap_or(None); + if yt_dlp_path.is_none() { + // Try loading from vYtDL config + if let Ok(mut cwd) = std::env::current_dir() { + for _ in 0..6 { + let candidate = cwd.join("vYtDL").join("config.json"); + if candidate.exists() { + if let Some(bin) = std::fs::read_to_string(&candidate) + .ok() + .and_then(|s| serde_json::from_str::(&s).ok()) + .and_then(|v| v.get("yt_dlp_bin").and_then(|v| v.as_str()).map(|s| s.to_string())) + { + yt_dlp_path = Some(bin); + } + break; + } + if !cwd.pop() { + break; + } + } + } + } + + let options = DownloadOptions { + url: request.url, + is_playlist: request.is_playlist, + quality: request.quality, + format: request.format, + output_dir: request.output_dir, + sub_langs: request.sub_langs, + write_subs: request.write_subs.unwrap_or(true), + write_auto_subs: request.write_auto_subs.unwrap_or(true), + start_time: request.start_time, + end_time: request.end_time, + }; + + queue.enqueue(download_id.clone(), options, yt_dlp_path, app).await; + + Ok(ApiResponse::ok(download_id)) +} + +#[tauri::command] +pub async fn cancel_download( + db: State<'_, Database>, + queue: State<'_, QueueManager>, + download_id: String, +) -> Result, String> { + queue.cancel(download_id.clone()).await; + if let Err(e) = db.update_download_status(&download_id, DownloadStatus::Cancelled).await { + return Ok(ApiResponse::err(format!("Failed to cancel download: {}", e))); + } + Ok(ApiResponse::ok(())) +} + +#[tauri::command] +pub async fn get_downloads( + db: State<'_, Database>, +) -> Result>, String> { + match db.get_all_downloads().await { + Ok(downloads) => Ok(ApiResponse::ok(downloads)), + Err(e) => Ok(ApiResponse::err(format!("Failed to get downloads: {}", e))), + } +} + +#[tauri::command] +pub async fn get_download_by_id( + db: State<'_, Database>, + id: String, +) -> Result>, String> { + match db.get_download_by_id(&id).await { + Ok(download) => Ok(ApiResponse::ok(download)), + Err(e) => Ok(ApiResponse::err(format!("Failed to get download: {}", e))), + } +} + +#[tauri::command] +pub async fn delete_download( + db: State<'_, Database>, + id: String, +) -> Result, String> { + if let Err(e) = db.delete_download(&id).await { + return Ok(ApiResponse::err(format!("Failed to delete download: {}", e))); + } + Ok(ApiResponse::ok(())) +} + +#[tauri::command] +pub async fn open_download_folder(path: String) -> Result, String> { + match opener::open(path) { + Ok(_) => Ok(ApiResponse::ok(())), + Err(e) => Ok(ApiResponse::err(format!("Failed to open folder: {}", e))), + } +} + +#[tauri::command] +pub async fn retry_download( + app: AppHandle, + db: State<'_, Database>, + queue: State<'_, QueueManager>, + id: String, +) -> Result, String> { + let original = match db.get_download_by_id(&id).await { + Ok(Some(record)) => record, + Ok(None) => return Ok(ApiResponse::err("Download not found".to_string())), + Err(e) => return Ok(ApiResponse::err(format!("Failed to get download: {}", e))), + }; + + let download_id = uuid::Uuid::new_v4().to_string(); + let record = DownloadRecord { + id: download_id.clone(), + url: original.url.clone(), + title: None, + status: DownloadStatus::Pending, + progress: 0.0, + speed: None, + eta: None, + output_dir: original.output_dir.clone(), + filename: None, + subtitles: vec![], + error: None, + queue_position: 0, + options: original.options.clone(), + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + }; + + if let Err(e) = db.create_download(record).await { + return Ok(ApiResponse::err(format!("Failed to create download: {}", e))); + } + + let mut yt_dlp_path = db.get_setting("yt_dlp_path").await.unwrap_or(None); + if yt_dlp_path.is_none() { + if let Ok(mut cwd) = std::env::current_dir() { + for _ in 0..6 { + let candidate = cwd.join("vYtDL").join("config.json"); + if candidate.exists() { + if let Some(bin) = std::fs::read_to_string(&candidate) + .ok() + .and_then(|s| serde_json::from_str::(&s).ok()) + .and_then(|v| v.get("yt_dlp_bin").and_then(|v| v.as_str()).map(|s| s.to_string())) + { + yt_dlp_path = Some(bin); + } + break; + } + if !cwd.pop() { + break; + } + } + } + } + + let options = DownloadOptions { + url: original.url, + is_playlist: false, + quality: None, + format: None, + output_dir: original.output_dir, + sub_langs: Some(vec!["en".to_string(), "zh".to_string()]), + write_subs: true, + write_auto_subs: true, + start_time: None, + end_time: None, + }; + + queue.enqueue(download_id.clone(), options, yt_dlp_path, app).await; + + Ok(ApiResponse::ok(download_id)) +} diff --git a/apps/contentforge-desktop/src-tauri/src/commands/mod.rs b/apps/contentforge-desktop/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..cff2c3b --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/mod.rs @@ -0,0 +1,43 @@ +use serde::Serialize; + +#[derive(Debug, Serialize, Clone)] +pub struct ApiResponse { + pub success: bool, + pub data: Option, + pub error: Option, +} + +impl ApiResponse { + pub fn ok(data: T) -> Self { + Self { + success: true, + data: Some(data), + error: None, + } + } + + pub fn err(message: String) -> Self { + Self { + success: false, + data: None, + error: Some(message), + } + } +} + +pub mod agent; +pub mod ai; +pub mod asset; +pub mod chat; +pub mod download; +pub mod settings; +pub mod video; + +// Re-export all command functions so lib.rs can reference them easily +pub use agent::*; +pub use ai::*; +pub use asset::*; +pub use chat::*; +pub use download::*; +pub use settings::*; +pub use video::*; diff --git a/apps/contentforge-desktop/src-tauri/src/commands/settings.rs b/apps/contentforge-desktop/src-tauri/src/commands/settings.rs new file mode 100644 index 0000000..d7c709a --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/settings.rs @@ -0,0 +1,187 @@ +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, State, Manager}; + +use crate::commands::ApiResponse; +use crate::db::Database; +use crate::queue::QueueManager; + +// ─────────────────────────── Settings Types ─────────────────────────── + +#[derive(Debug, Serialize, Deserialize)] +pub struct Settings { + // ContentForge original fields + pub language: String, + pub theme: String, + pub max_concurrent: i64, + pub default_output_dir: Option, + pub ai_provider: Option, + pub ai_api_key: Option, + pub ai_model: Option, + // vYtDL additional fields + pub yt_dlp_path: Option, + pub default_quality: String, + pub default_format: String, + pub default_sub_langs: Vec, + pub agent_cli_kimi_bin: Option, + pub agent_cli_other_bin: Option, +} + +// ─────────────────────────── Settings Commands ─────────────────────────── + +/// Load yt-dlp binary path from the shared vYtDL config file. +fn load_vytdl_config_yt_dlp_path() -> Option { + if let Ok(path) = std::env::var("VYTDL_CONFIG") { + if let Some(bin) = parse_yt_dlp_bin_from_file(&path) { + return Some(bin); + } + } + + if let Ok(mut cwd) = std::env::current_dir() { + for _ in 0..6 { + let candidate = cwd.join("vYtDL").join("config.json"); + if candidate.exists() { + if let Some(bin) = parse_yt_dlp_bin_from_file(candidate.to_str().unwrap_or("")) { + return Some(bin); + } + break; + } + if !cwd.pop() { + break; + } + } + } + + if let Ok(exe) = std::env::current_exe() { + if let Some(dir) = exe.parent() { + let candidate = dir.join("config.json"); + if candidate.exists() { + if let Some(bin) = parse_yt_dlp_bin_from_file(candidate.to_str().unwrap_or("")) { + return Some(bin); + } + } + } + } + + None +} + +fn parse_yt_dlp_bin_from_file(path: &str) -> Option { + let content = std::fs::read_to_string(path).ok()?; + let config: serde_json::Value = serde_json::from_str(&content).ok()?; + config + .get("yt_dlp_bin") + .and_then(|v| v.as_str()) + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) +} + +#[tauri::command] +pub async fn get_settings(db: State<'_, Database>) -> Result, String> { + let language = db.get_setting("language").await.unwrap_or(None).unwrap_or_else(|| "zh".to_string()); + let theme = db.get_setting("theme").await.unwrap_or(None).unwrap_or_else(|| "dark".to_string()); + let max_concurrent = db + .get_setting("max_concurrent") + .await + .unwrap_or(None) + .and_then(|s| s.parse::().ok()) + .unwrap_or(3); + let default_output_dir = db.get_setting("default_output_dir").await.unwrap_or(None); + let ai_provider = db.get_setting("ai_provider").await.unwrap_or(None); + let ai_api_key = db.get_setting("ai_api_key").await.unwrap_or(None); + let ai_model = db.get_setting("ai_model").await.unwrap_or(None); + + let mut yt_dlp_path = db.get_setting("yt_dlp_path").await.unwrap_or(None); + if yt_dlp_path.is_none() { + yt_dlp_path = load_vytdl_config_yt_dlp_path(); + } + let default_quality = db.get_setting("default_quality").await.unwrap_or(None).unwrap_or_else(|| "best".to_string()); + let default_format = db.get_setting("default_format").await.unwrap_or(None).unwrap_or_else(|| "mp4".to_string()); + let default_sub_langs_str = db.get_setting("default_sub_langs").await.unwrap_or(None).unwrap_or_else(|| "[\"en\",\"zh\"]".to_string()); + let default_sub_langs: Vec = serde_json::from_str(&default_sub_langs_str).unwrap_or_else(|_| vec!["en".to_string(), "zh".to_string()]); + let agent_cli_kimi_bin = db.get_setting("agent_cli_kimi_bin").await.unwrap_or(None); + let agent_cli_other_bin = db.get_setting("agent_cli_other_bin").await.unwrap_or(None); + + Ok(ApiResponse::ok(Settings { + language, + theme, + max_concurrent, + default_output_dir, + ai_provider, + ai_api_key, + ai_model, + yt_dlp_path, + default_quality, + default_format, + default_sub_langs, + agent_cli_kimi_bin, + agent_cli_other_bin, + })) +} + +#[tauri::command] +pub async fn update_settings( + app: AppHandle, + db: State<'_, Database>, + settings: Settings, +) -> Result, String> { + if let Err(e) = db.set_setting("language", &settings.language).await { + return Ok(ApiResponse::err(format!("Failed to save language: {}", e))); + } + if let Err(e) = db.set_setting("theme", &settings.theme).await { + return Ok(ApiResponse::err(format!("Failed to save theme: {}", e))); + } + if let Err(e) = db.set_setting("max_concurrent", &settings.max_concurrent.to_string()).await { + return Ok(ApiResponse::err(format!("Failed to save max_concurrent: {}", e))); + } + if let Some(ref dir) = settings.default_output_dir { + if let Err(e) = db.set_setting("default_output_dir", dir).await { + return Ok(ApiResponse::err(format!("Failed to save output dir: {}", e))); + } + } + if let Some(ref provider) = settings.ai_provider { + if let Err(e) = db.set_setting("ai_provider", provider).await { + return Ok(ApiResponse::err(format!("Failed to save AI provider: {}", e))); + } + } + if let Some(ref key) = settings.ai_api_key { + if let Err(e) = db.set_setting("ai_api_key", key).await { + return Ok(ApiResponse::err(format!("Failed to save API key: {}", e))); + } + } + if let Some(ref model) = settings.ai_model { + if let Err(e) = db.set_setting("ai_model", model).await { + return Ok(ApiResponse::err(format!("Failed to save model: {}", e))); + } + } + if let Some(ref path) = settings.yt_dlp_path { + if let Err(e) = db.set_setting("yt_dlp_path", path).await { + return Ok(ApiResponse::err(format!("Failed to save yt-dlp path: {}", e))); + } + } + if let Err(e) = db.set_setting("default_quality", &settings.default_quality).await { + return Ok(ApiResponse::err(format!("Failed to save quality: {}", e))); + } + if let Err(e) = db.set_setting("default_format", &settings.default_format).await { + return Ok(ApiResponse::err(format!("Failed to save format: {}", e))); + } + let sub_langs_json = serde_json::to_string(&settings.default_sub_langs).unwrap_or_else(|_| "[\"en\",\"zh\"]".to_string()); + if let Err(e) = db.set_setting("default_sub_langs", &sub_langs_json).await { + return Ok(ApiResponse::err(format!("Failed to save sub langs: {}", e))); + } + if let Some(ref path) = settings.agent_cli_kimi_bin { + if let Err(e) = db.set_setting("agent_cli_kimi_bin", path).await { + return Ok(ApiResponse::err(format!("Failed to save Kimi CLI path: {}", e))); + } + } + if let Some(ref path) = settings.agent_cli_other_bin { + if let Err(e) = db.set_setting("agent_cli_other_bin", path).await { + return Ok(ApiResponse::err(format!("Failed to save other agent CLI path: {}", e))); + } + } + + // Notify queue manager to update concurrency + let queue = app.state::(); + queue.set_max_concurrent(settings.max_concurrent as usize).await; + + Ok(ApiResponse::ok(())) +} diff --git a/apps/contentforge-desktop/src-tauri/src/commands/video.rs b/apps/contentforge-desktop/src-tauri/src/commands/video.rs new file mode 100644 index 0000000..d1d65b3 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/commands/video.rs @@ -0,0 +1,53 @@ +use tauri::State; + +use crate::commands::ApiResponse; +use crate::db::Database; +use crate::downloader::{Downloader, FormatInfo, PlaylistInfo, VideoInfo}; + +// ─────────────────────────── Video Commands ─────────────────────────── + +#[tauri::command] +pub async fn get_video_info( + db: State<'_, Database>, + url: String, +) -> Result, String> { + log::info!("get_video_info called for URL: {}", url); + let yt_dlp_path = db.get_setting("yt_dlp_path").await.unwrap_or(None); + let downloader = Downloader::new_default().with_yt_dlp_path(yt_dlp_path); + match downloader.get_info(&url).await { + Ok(info) => { + log::info!("get_video_info success: {} ({})", info.title, info.id); + Ok(ApiResponse::ok(info)) + } + Err(e) => { + log::error!("get_video_info failed: {}", e); + Ok(ApiResponse::err(format!("Failed to get video info: {}", e))) + } + } +} + +#[tauri::command] +pub async fn get_video_formats( + db: State<'_, Database>, + url: String, +) -> Result>, String> { + let yt_dlp_path = db.get_setting("yt_dlp_path").await.unwrap_or(None); + let downloader = Downloader::new_default().with_yt_dlp_path(yt_dlp_path); + match downloader.get_formats(&url).await { + Ok(formats) => Ok(ApiResponse::ok(formats)), + Err(e) => Ok(ApiResponse::err(format!("Failed to get video formats: {}", e))), + } +} + +#[tauri::command] +pub async fn get_playlist_info( + db: State<'_, Database>, + url: String, +) -> Result, String> { + let yt_dlp_path = db.get_setting("yt_dlp_path").await.unwrap_or(None); + let downloader = Downloader::new_default().with_yt_dlp_path(yt_dlp_path); + match downloader.get_playlist_info(&url).await { + Ok(info) => Ok(ApiResponse::ok(info)), + Err(e) => Ok(ApiResponse::err(format!("Failed to get playlist info: {}", e))), + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/database.rs b/apps/contentforge-desktop/src-tauri/src/database.rs deleted file mode 100644 index ceaa753..0000000 --- a/apps/contentforge-desktop/src-tauri/src/database.rs +++ /dev/null @@ -1,137 +0,0 @@ -use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite}; -use std::path::Path; -use tauri::Manager; - -pub struct Database { - pool: Pool, -} - -impl Database { - pub async fn new(app: &tauri::App) -> Result { - let app_data_dir = app - .path() - .app_data_dir() - .map_err(|e| sqlx::Error::Configuration(Box::new(e)))?; - - std::fs::create_dir_all(&app_data_dir).map_err(|e| sqlx::Error::Io(e))?; - - let db_path = app_data_dir.join("contentforge.db"); - let db_url = format!("sqlite:{}", db_path.to_string_lossy()); - - let pool = SqlitePoolOptions::new() - .max_connections(5) - .connect(&db_url) - .await?; - - Ok(Self { pool }) - } - - pub async fn new_with_path(path: &Path) -> Result { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent).map_err(|e| sqlx::Error::Io(e))?; - } - - let db_url = format!("sqlite:{}", path.to_string_lossy()); - - let pool = SqlitePoolOptions::new() - .max_connections(5) - .connect(&db_url) - .await?; - - Ok(Self { pool }) - } - - pub async fn new_in_memory() -> Result { - let pool = SqlitePoolOptions::new() - .max_connections(5) - .connect("sqlite::memory:") - .await?; - - Ok(Self { pool }) - } - - pub async fn init(&self) -> Result<(), sqlx::Error> { - // Create sessions table - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS sessions ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - agent_id TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'active', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - "#, - ) - .execute(&self.pool) - .await?; - - // Create messages table - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS messages ( - id TEXT PRIMARY KEY, - session_id TEXT NOT NULL, - role TEXT NOT NULL, - content TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'completed', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (session_id) REFERENCES sessions(id) - ) - "#, - ) - .execute(&self.pool) - .await?; - - // Create assets table - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS assets ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - asset_type TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'ingested', - platform TEXT, - url TEXT, - file_path TEXT, - extracted_text TEXT, - summary TEXT, - tags TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - "#, - ) - .execute(&self.pool) - .await?; - - // Create settings table - sqlx::query( - r#" - CREATE TABLE IF NOT EXISTS settings ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - "#, - ) - .execute(&self.pool) - .await?; - - Ok(()) - } - - pub fn pool(&self) -> &Pool { - &self.pool - } -} - -impl Clone for Database { - fn clone(&self) -> Self { - Self { - pool: self.pool.clone(), - } - } -} diff --git a/apps/contentforge-desktop/src-tauri/src/db/agent_switches.rs b/apps/contentforge-desktop/src-tauri/src/db/agent_switches.rs new file mode 100644 index 0000000..fa102fa --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/agent_switches.rs @@ -0,0 +1,34 @@ +use crate::db::{AgentSwitchRecord, Database}; + +impl Database { + pub async fn record_agent_switch(&self, record: &AgentSwitchRecord) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO agent_switches (id, session_id, from_agent_id, to_agent_id, reason, triggered_by, created_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) + "#, + ) + .bind(&record.id) + .bind(&record.session_id) + .bind(&record.from_agent_id) + .bind(&record.to_agent_id) + .bind(&record.reason) + .bind(&record.triggered_by) + .bind(record.created_at) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_agent_switches_by_session( + &self, + session_id: &str, + ) -> Result, sqlx::Error> { + sqlx::query_as::<_, AgentSwitchRecord>( + "SELECT * FROM agent_switches WHERE session_id = ?1 ORDER BY created_at DESC" + ) + .bind(session_id) + .fetch_all(&self.pool) + .await + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/assets.rs b/apps/contentforge-desktop/src-tauri/src/db/assets.rs new file mode 100644 index 0000000..73f3abc --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/assets.rs @@ -0,0 +1,166 @@ +use crate::db::{Asset, Database}; + +impl Database { + pub async fn create_asset(&self, asset: &Asset) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO assets ( + id, title, asset_type, status, platform, url, file_path, thumbnail_url, + description, extracted_text, summary, transcript, translated_text, rewritten_text, + duration_sec, analysis, tags, pipeline_id, author, published_at, engagement, created_at, updated_at + ) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) + "#, + ) + .bind(&asset.id) + .bind(&asset.title) + .bind(&asset.asset_type) + .bind(&asset.status) + .bind(&asset.platform) + .bind(&asset.url) + .bind(&asset.file_path) + .bind(&asset.thumbnail_url) + .bind(&asset.description) + .bind(&asset.extracted_text) + .bind(&asset.summary) + .bind(&asset.transcript) + .bind(&asset.translated_text) + .bind(&asset.rewritten_text) + .bind(asset.duration_sec) + .bind(&asset.analysis) + .bind(&asset.tags) + .bind(&asset.pipeline_id) + .bind(&asset.author) + .bind(&asset.published_at) + .bind(&asset.engagement) + .bind(asset.created_at) + .bind(asset.updated_at) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_asset_by_id(&self, id: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Asset>( + "SELECT * FROM assets WHERE id = ?1" + ) + .bind(id) + .fetch_optional(&self.pool) + .await + } + + pub async fn search_assets( + &self, + query: Option<&str>, + asset_type: Option<&str>, + status: Option<&str>, + platform: Option<&str>, + tags: Option<&str>, + sort_field: &str, + sort_order: &str, + page: i64, + page_size: i64, + ) -> Result<(Vec, i64), sqlx::Error> { + let offset = (page - 1) * page_size; + + // Build dynamic WHERE clause + let mut conditions = vec!["1=1".to_string()]; + if let Some(q) = query { + if !q.is_empty() { + conditions.push(format!( + "(title LIKE '%{}%' OR description LIKE '%{}%' OR extracted_text LIKE '%{}%' OR summary LIKE '%{}%')", + q, q, q, q + )); + } + } + if let Some(t) = asset_type { + conditions.push(format!("asset_type = '{}'", t)); + } + if let Some(s) = status { + conditions.push(format!("status = '{}'", s)); + } + if let Some(p) = platform { + conditions.push(format!("platform = '{}'", p)); + } + if let Some(t) = tags { + conditions.push(format!("tags LIKE '%{}%'", t)); + } + + let where_clause = conditions.join(" AND "); + let order_clause = format!("{} {}", sort_field, sort_order); + + let count_sql = format!("SELECT COUNT(*) FROM assets WHERE {}", where_clause); + let total: (i64,) = sqlx::query_as(&count_sql) + .fetch_one(&self.pool) + .await?; + + let query_sql = format!( + "SELECT * FROM assets WHERE {} ORDER BY {} LIMIT ?1 OFFSET ?2", + where_clause, order_clause + ); + let assets = sqlx::query_as::<_, Asset>(&query_sql) + .bind(page_size) + .bind(offset) + .fetch_all(&self.pool) + .await?; + + Ok((assets, total.0)) + } + + pub async fn delete_asset(&self, id: &str) -> Result<(), sqlx::Error> { + sqlx::query("DELETE FROM assets WHERE id = ?1") + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn update_asset_tags(&self, id: &str, tags: &str) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE assets SET tags = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2" + ) + .bind(tags) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_asset_groups(&self) -> Result, sqlx::Error> { + let mut groups = Vec::new(); + + // Group by type + let type_groups: Vec<(String, i64)> = sqlx::query_as( + "SELECT asset_type, COUNT(*) FROM assets GROUP BY asset_type" + ) + .fetch_all(&self.pool) + .await?; + for (t, count) in type_groups { + groups.push(("type".to_string(), t, count)); + } + + // Group by platform + let platform_groups: Vec<(Option, i64)> = sqlx::query_as( + "SELECT platform, COUNT(*) FROM assets GROUP BY platform" + ) + .fetch_all(&self.pool) + .await?; + for (p, count) in platform_groups { + if let Some(p) = p { + groups.push(("platform".to_string(), p, count)); + } + } + + // Group by status + let status_groups: Vec<(String, i64)> = sqlx::query_as( + "SELECT status, COUNT(*) FROM assets GROUP BY status" + ) + .fetch_all(&self.pool) + .await?; + for (s, count) in status_groups { + groups.push(("status".to_string(), s, count)); + } + + Ok(groups) + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/downloads.rs b/apps/contentforge-desktop/src-tauri/src/db/downloads.rs new file mode 100644 index 0000000..56a970c --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/downloads.rs @@ -0,0 +1,399 @@ +use chrono::{DateTime, Utc}; + +use crate::db::{Database, DownloadRecord, DownloadStatus, VttReport}; + +// Internal row mapping struct for downloads +#[derive(sqlx::FromRow)] +struct DownloadRow { + id: String, + url: String, + title: Option, + status: DownloadStatus, + progress: f64, + speed: Option, + eta: Option, + output_dir: Option, + filename: Option, + subtitles: String, + error: Option, + queue_position: i64, + options: Option, + created_at: DateTime, + updated_at: DateTime, +} + +impl From for DownloadRecord { + fn from(row: DownloadRow) -> Self { + Self { + id: row.id, + url: row.url, + title: row.title, + status: row.status, + progress: row.progress, + speed: row.speed, + eta: row.eta, + output_dir: row.output_dir, + filename: row.filename, + subtitles: serde_json::from_str(&row.subtitles).unwrap_or_default(), + error: row.error, + queue_position: row.queue_position, + options: row.options, + created_at: row.created_at, + updated_at: row.updated_at, + } + } +} + +impl Database { + // ─── Downloads ─── + + pub async fn create_download(&self, record: DownloadRecord) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO downloads (id, url, title, status, progress, speed, eta, output_dir, filename, subtitles, error, queue_position, options, created_at, updated_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15) + "#, + ) + .bind(&record.id) + .bind(&record.url) + .bind(&record.title) + .bind(&record.status) + .bind(record.progress) + .bind(&record.speed) + .bind(&record.eta) + .bind(&record.output_dir) + .bind(&record.filename) + .bind(serde_json::to_string(&record.subtitles).unwrap()) + .bind(&record.error) + .bind(record.queue_position) + .bind(&record.options) + .bind(record.created_at) + .bind(record.updated_at) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn get_all_downloads(&self) -> Result, sqlx::Error> { + let rows = sqlx::query_as::<_, DownloadRow>( + r#" + SELECT id, url, title, status, progress, speed, eta, output_dir, filename, subtitles, error, queue_position, options, created_at, updated_at + FROM downloads + ORDER BY created_at DESC + "#, + ) + .fetch_all(&self.pool) + .await?; + + Ok(rows.into_iter().map(|r| r.into()).collect()) + } + + pub async fn get_download_by_id(&self, id: &str) -> Result, sqlx::Error> { + let row = sqlx::query_as::<_, DownloadRow>( + r#" + SELECT id, url, title, status, progress, speed, eta, output_dir, filename, subtitles, error, queue_position, options, created_at, updated_at + FROM downloads + WHERE id = ?1 + "#, + ) + .bind(id) + .fetch_optional(&self.pool) + .await?; + + Ok(row.map(|r| r.into())) + } + + pub async fn update_download_status( + &self, + id: &str, + status: DownloadStatus, + ) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + UPDATE downloads + SET status = ?1, updated_at = CURRENT_TIMESTAMP + WHERE id = ?2 + "#, + ) + .bind(status) + .bind(id) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn update_download_progress( + &self, + id: &str, + progress: f64, + speed: Option<&str>, + eta: Option<&str>, + ) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + UPDATE downloads + SET progress = ?1, speed = ?2, eta = ?3, updated_at = CURRENT_TIMESTAMP + WHERE id = ?4 + "#, + ) + .bind(progress) + .bind(speed) + .bind(eta) + .bind(id) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn update_download_complete( + &self, + id: &str, + title: Option, + filename: Option, + subtitles: Vec, + ) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + UPDATE downloads + SET title = ?1, filename = ?2, subtitles = ?3, status = 'completed', progress = 100.0, updated_at = CURRENT_TIMESTAMP + WHERE id = ?4 + "#, + ) + .bind(title) + .bind(filename) + .bind(serde_json::to_string(&subtitles).unwrap()) + .bind(id) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn update_download_error(&self, id: &str, error: &str) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + UPDATE downloads + SET status = 'failed', error = ?1, updated_at = CURRENT_TIMESTAMP + WHERE id = ?2 + "#, + ) + .bind(error) + .bind(id) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn update_queue_position(&self, id: &str, position: i64) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + UPDATE downloads + SET queue_position = ?1, updated_at = CURRENT_TIMESTAMP + WHERE id = ?2 + "#, + ) + .bind(position) + .bind(id) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn delete_download(&self, id: &str) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + DELETE FROM downloads + WHERE id = ?1 + "#, + ) + .bind(id) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn get_incomplete_downloads(&self) -> Result, sqlx::Error> { + let rows = sqlx::query_as::<_, DownloadRow>( + r#" + SELECT id, url, title, status, progress, speed, eta, output_dir, filename, subtitles, error, queue_position, options, created_at, updated_at + FROM downloads + WHERE status IN ('pending', 'downloading') + ORDER BY created_at ASC + "#, + ) + .fetch_all(&self.pool) + .await?; + + Ok(rows.into_iter().map(|r| r.into()).collect()) + } + + // ─── VTT Reports ─── + + pub async fn create_vtt_report(&self, report: &VttReport) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO vtt_reports ( + id, youtube_url, video_id, title, language, content, + cue_count, duration_sec, created_at, status, error + ) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) + "#, + ) + .bind(&report.id) + .bind(&report.youtube_url) + .bind(&report.video_id) + .bind(&report.title) + .bind(&report.language) + .bind(&report.content) + .bind(report.cue_count) + .bind(report.duration_sec) + .bind(report.created_at) + .bind(&report.status) + .bind(&report.error) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn get_vtt_report(&self, id: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, VttReport>( + "SELECT * FROM vtt_reports WHERE id = ?1", + ) + .bind(id) + .fetch_optional(&self.pool) + .await + } + + pub async fn list_vtt_reports( + &self, + page: u32, + limit: u32, + lang: Option<&str>, + ) -> Result<(Vec, i64), sqlx::Error> { + let offset = ((page.saturating_sub(1)) * limit) as i64; + let limit = limit as i64; + + let (reports, total) = if let Some(lang) = lang { + let total: (i64,) = sqlx::query_as( + "SELECT COUNT(*) FROM vtt_reports WHERE language = ?1", + ) + .bind(lang) + .fetch_one(&self.pool) + .await?; + + let reports = sqlx::query_as::<_, VttReport>( + "SELECT * FROM vtt_reports WHERE language = ?1 ORDER BY created_at DESC LIMIT ?2 OFFSET ?3", + ) + .bind(lang) + .bind(limit) + .bind(offset) + .fetch_all(&self.pool) + .await?; + + (reports, total.0) + } else { + let total: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM vtt_reports") + .fetch_one(&self.pool) + .await?; + + let reports = sqlx::query_as::<_, VttReport>( + "SELECT * FROM vtt_reports ORDER BY created_at DESC LIMIT ?1 OFFSET ?2", + ) + .bind(limit) + .bind(offset) + .fetch_all(&self.pool) + .await?; + + (reports, total.0) + }; + + Ok((reports, total)) + } + + pub async fn update_vtt_report( + &self, + id: &str, + title: Option<&str>, + language: Option<&str>, + content: Option<&str>, + cue_count: Option, + duration_sec: Option, + video_id: Option<&str>, + status: Option<&str>, + error: Option<&str>, + ) -> Result<(), sqlx::Error> { + if let Some(title) = title { + sqlx::query("UPDATE vtt_reports SET title = ?1 WHERE id = ?2") + .bind(title) + .bind(id) + .execute(&self.pool) + .await?; + } + if let Some(language) = language { + sqlx::query("UPDATE vtt_reports SET language = ?1 WHERE id = ?2") + .bind(language) + .bind(id) + .execute(&self.pool) + .await?; + } + if let Some(content) = content { + sqlx::query("UPDATE vtt_reports SET content = ?1 WHERE id = ?2") + .bind(content) + .bind(id) + .execute(&self.pool) + .await?; + } + if let Some(cue_count) = cue_count { + sqlx::query("UPDATE vtt_reports SET cue_count = ?1 WHERE id = ?2") + .bind(cue_count) + .bind(id) + .execute(&self.pool) + .await?; + } + if let Some(duration_sec) = duration_sec { + sqlx::query("UPDATE vtt_reports SET duration_sec = ?1 WHERE id = ?2") + .bind(duration_sec) + .bind(id) + .execute(&self.pool) + .await?; + } + if let Some(video_id) = video_id { + sqlx::query("UPDATE vtt_reports SET video_id = ?1 WHERE id = ?2") + .bind(video_id) + .bind(id) + .execute(&self.pool) + .await?; + } + if let Some(status) = status { + sqlx::query("UPDATE vtt_reports SET status = ?1 WHERE id = ?2") + .bind(status) + .bind(id) + .execute(&self.pool) + .await?; + } + if let Some(error) = error { + sqlx::query("UPDATE vtt_reports SET error = ?1 WHERE id = ?2") + .bind(error) + .bind(id) + .execute(&self.pool) + .await?; + } + + Ok(()) + } + + pub async fn delete_vtt_report(&self, id: &str) -> Result<(), sqlx::Error> { + sqlx::query("DELETE FROM vtt_reports WHERE id = ?1") + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/messages.rs b/apps/contentforge-desktop/src-tauri/src/db/messages.rs new file mode 100644 index 0000000..a548565 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/messages.rs @@ -0,0 +1,94 @@ +use crate::db::{Database, Message, MessageStatus}; + +impl Database { + pub async fn create_message(&self, message: &Message) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO messages (id, session_id, role, content, status, model, tokens_used, tool_calls, tool_results, selected_asset_ids, error, created_at, updated_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13) + "#, + ) + .bind(&message.id) + .bind(&message.session_id) + .bind(&message.role) + .bind(&message.content) + .bind(&message.status) + .bind(&message.model) + .bind(&message.tokens_used) + .bind(&message.tool_calls) + .bind(&message.tool_results) + .bind(&message.selected_asset_ids) + .bind(&message.error) + .bind(message.created_at) + .bind(message.updated_at) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_messages_by_session( + &self, + session_id: &str, + limit: i64, + offset: i64, + ) -> Result, sqlx::Error> { + sqlx::query_as::<_, Message>( + "SELECT * FROM messages WHERE session_id = ?1 ORDER BY created_at DESC LIMIT ?2 OFFSET ?3" + ) + .bind(session_id) + .bind(limit) + .bind(offset) + .fetch_all(&self.pool) + .await + } + + pub async fn get_message_by_id(&self, id: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Message>( + "SELECT * FROM messages WHERE id = ?1" + ) + .bind(id) + .fetch_optional(&self.pool) + .await + } + + pub async fn update_message_content(&self, id: &str, content: &str) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE messages SET content = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2" + ) + .bind(content) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn update_message_status(&self, id: &str, status: MessageStatus) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE messages SET status = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2" + ) + .bind(status) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn update_message_error(&self, id: &str, error: &str) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE messages SET error = ?1, status = 'failed', updated_at = CURRENT_TIMESTAMP WHERE id = ?2" + ) + .bind(error) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn delete_message(&self, id: &str) -> Result<(), sqlx::Error> { + sqlx::query("DELETE FROM messages WHERE id = ?1") + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/mod.rs b/apps/contentforge-desktop/src-tauri/src/db/mod.rs new file mode 100644 index 0000000..5acb747 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/mod.rs @@ -0,0 +1,266 @@ +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; +use tauri::Manager; + +use sqlx::{Pool, Sqlite}; +use std::path::Path; + +pub mod types; +pub mod agent_switches; +pub mod assets; +pub mod downloads; +pub mod messages; +pub mod pipeline_runs; +pub mod sessions; +pub mod settings; + +pub use types::*; + +#[derive(Clone)] +pub struct Database { + pub(crate) pool: Pool, +} + +impl Database { + pub async fn new_with_path(db_path: &Path) -> Result { + let parent = db_path.parent().ok_or_else(|| { + sqlx::Error::Io(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Invalid database path: no parent directory", + )) + })?; + + std::fs::create_dir_all(parent).map_err(|e| { + sqlx::Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to create database directory: {}", e), + )) + })?; + + let options = SqliteConnectOptions::new() + .filename(db_path) + .create_if_missing(true); + + let pool = SqlitePoolOptions::new() + .max_connections(5) + .connect_with(options) + .await?; + + Ok(Self { pool }) + } + + pub async fn new(app: &tauri::AppHandle) -> Result { + let app_dir = app + .path() + .app_data_dir() + .unwrap_or_else(|_| { + dirs::data_dir() + .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()) + .join("com.contentforge.desktop") + }); + + let db_path = app_dir.join("contentforge.db"); + Self::new_with_path(&db_path).await + } + + pub async fn new_in_memory() -> Result { + let pool = SqlitePoolOptions::new() + .max_connections(1) + .connect_with(SqliteConnectOptions::new()) + .await?; + Ok(Self { pool }) + } + + pub async fn init(&self) -> Result<(), sqlx::Error> { + // Sessions table + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + agent_id TEXT NOT NULL DEFAULT 'general', + status TEXT NOT NULL DEFAULT 'active', + linked_task_id TEXT, + linked_asset_ids TEXT DEFAULT '[]', + metadata TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "#, + ) + .execute(&self.pool) + .await?; + + // Messages table + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS messages ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL, + role TEXT NOT NULL, + content TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'completed', + model TEXT, + tokens_used TEXT, + tool_calls TEXT, + tool_results TEXT, + selected_asset_ids TEXT, + error TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE + ) + "#, + ) + .execute(&self.pool) + .await?; + + // Assets table + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS assets ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + asset_type TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'ingested', + platform TEXT, + url TEXT, + file_path TEXT, + thumbnail_url TEXT, + description TEXT, + extracted_text TEXT, + summary TEXT, + transcript TEXT, + translated_text TEXT, + rewritten_text TEXT, + duration_sec REAL, + analysis TEXT, + tags TEXT DEFAULT '[]', + pipeline_id TEXT, + author TEXT, + published_at TEXT, + engagement TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "#, + ) + .execute(&self.pool) + .await?; + + // Settings table + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "#, + ) + .execute(&self.pool) + .await?; + + // Agent switch history + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS agent_switches ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL, + from_agent_id TEXT NOT NULL, + to_agent_id TEXT NOT NULL, + reason TEXT, + triggered_by TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "#, + ) + .execute(&self.pool) + .await?; + + // Pipeline runs table + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS pipeline_runs ( + id TEXT PRIMARY KEY, + pipeline_id TEXT NOT NULL, + asset_id TEXT, + status TEXT NOT NULL DEFAULT 'pending', + progress REAL DEFAULT 0.0, + current_step TEXT, + step_results TEXT, + error TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "#, + ) + .execute(&self.pool) + .await?; + + // Downloads table (from vYtDL) + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS downloads ( + id TEXT PRIMARY KEY, + url TEXT NOT NULL, + title TEXT, + status TEXT NOT NULL DEFAULT 'pending', + progress REAL DEFAULT 0.0, + speed TEXT, + eta TEXT, + output_dir TEXT, + filename TEXT, + subtitles TEXT DEFAULT '[]', + error TEXT, + queue_position INTEGER DEFAULT 0, + options TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "#, + ) + .execute(&self.pool) + .await?; + + // VTT reports table (from vYtDL) + sqlx::query( + r#" + CREATE TABLE IF NOT EXISTS vtt_reports ( + id TEXT PRIMARY KEY, + youtube_url TEXT NOT NULL, + video_id TEXT, + title TEXT, + language TEXT, + content TEXT NOT NULL DEFAULT '', + cue_count INTEGER DEFAULT 0, + duration_sec REAL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status TEXT NOT NULL DEFAULT 'pending', + error TEXT + ) + "#, + ) + .execute(&self.pool) + .await?; + + // Create indexes for performance + let _ = sqlx::query("CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id)") + .execute(&self.pool) + .await; + let _ = sqlx::query("CREATE INDEX IF NOT EXISTS idx_assets_type ON assets(asset_type)") + .execute(&self.pool) + .await; + let _ = sqlx::query("CREATE INDEX IF NOT EXISTS idx_assets_status ON assets(status)") + .execute(&self.pool) + .await; + let _ = sqlx::query("CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status)") + .execute(&self.pool) + .await; + + Ok(()) + } + + pub fn pool(&self) -> &Pool { + &self.pool + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/pipeline_runs.rs b/apps/contentforge-desktop/src-tauri/src/db/pipeline_runs.rs new file mode 100644 index 0000000..1692d4d --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/pipeline_runs.rs @@ -0,0 +1,68 @@ +use crate::db::{Database, PipelineRun}; + +impl Database { + pub async fn create_pipeline_run(&self, run: &PipelineRun) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO pipeline_runs (id, pipeline_id, asset_id, status, progress, current_step, step_results, error, created_at, updated_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) + "#, + ) + .bind(&run.id) + .bind(&run.pipeline_id) + .bind(&run.asset_id) + .bind(&run.status) + .bind(run.progress) + .bind(&run.current_step) + .bind(&run.step_results) + .bind(&run.error) + .bind(run.created_at) + .bind(run.updated_at) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_pipeline_run_by_id(&self, id: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, PipelineRun>( + "SELECT * FROM pipeline_runs WHERE id = ?1" + ) + .bind(id) + .fetch_optional(&self.pool) + .await + } + + pub async fn update_pipeline_run_progress( + &self, + id: &str, + progress: f64, + current_step: Option<&str>, + ) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE pipeline_runs SET progress = ?1, current_step = ?2, updated_at = CURRENT_TIMESTAMP WHERE id = ?3" + ) + .bind(progress) + .bind(current_step) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn update_pipeline_run_status( + &self, + id: &str, + status: &str, + error: Option<&str>, + ) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE pipeline_runs SET status = ?1, error = ?2, updated_at = CURRENT_TIMESTAMP WHERE id = ?3" + ) + .bind(status) + .bind(error) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/sessions.rs b/apps/contentforge-desktop/src-tauri/src/db/sessions.rs new file mode 100644 index 0000000..fb36e27 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/sessions.rs @@ -0,0 +1,107 @@ +use crate::db::{Database, Session, SessionStatus}; + +impl Database { + pub async fn create_session(&self, session: &Session) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO sessions (id, title, agent_id, status, linked_task_id, linked_asset_ids, metadata, created_at, updated_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) + "#, + ) + .bind(&session.id) + .bind(&session.title) + .bind(&session.agent_id) + .bind(&session.status) + .bind(&session.linked_task_id) + .bind(&session.linked_asset_ids) + .bind(&session.metadata) + .bind(session.created_at) + .bind(session.updated_at) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_sessions(&self) -> Result, sqlx::Error> { + sqlx::query_as::<_, Session>( + "SELECT * FROM sessions ORDER BY updated_at DESC" + ) + .fetch_all(&self.pool) + .await + } + + pub async fn get_session_by_id(&self, id: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Session>( + "SELECT * FROM sessions WHERE id = ?1" + ) + .bind(id) + .fetch_optional(&self.pool) + .await + } + + pub async fn update_session_status(&self, id: &str, status: SessionStatus) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE sessions SET status = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2" + ) + .bind(status) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn update_session_title(&self, id: &str, title: &str) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE sessions SET title = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2" + ) + .bind(title) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn update_session_agent(&self, id: &str, agent_id: &str) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE sessions SET agent_id = ?1, updated_at = CURRENT_TIMESTAMP WHERE id = ?2" + ) + .bind(agent_id) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn delete_session(&self, id: &str) -> Result<(), sqlx::Error> { + sqlx::query("DELETE FROM sessions WHERE id = ?1") + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + /// Generic session updater for partial updates. + pub async fn update_session(&self, id: &str, updater: F) -> Result<(), sqlx::Error> + where + F: FnOnce(&mut Session), + { + let mut session = self + .get_session_by_id(id) + .await? + .ok_or_else(|| sqlx::Error::RowNotFound)?; + updater(&mut session); + sqlx::query( + "UPDATE sessions SET title = ?1, agent_id = ?2, status = ?3, linked_task_id = ?4, linked_asset_ids = ?5, metadata = ?6, updated_at = CURRENT_TIMESTAMP WHERE id = ?7" + ) + .bind(&session.title) + .bind(&session.agent_id) + .bind(&session.status) + .bind(&session.linked_task_id) + .bind(&session.linked_asset_ids) + .bind(&session.metadata) + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/settings.rs b/apps/contentforge-desktop/src-tauri/src/db/settings.rs new file mode 100644 index 0000000..e80d2cd --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/settings.rs @@ -0,0 +1,39 @@ +use crate::db::Database; + +impl Database { + pub async fn get_setting(&self, key: &str) -> Result, sqlx::Error> { + let row: Option<(String,)> = sqlx::query_as( + "SELECT value FROM settings WHERE key = ?1" + ) + .bind(key) + .fetch_optional(&self.pool) + .await?; + + Ok(row.map(|r| r.0)) + } + + pub async fn set_setting(&self, key: &str, value: &str) -> Result<(), sqlx::Error> { + sqlx::query( + r#" + INSERT INTO settings (key, value, updated_at) + VALUES (?1, ?2, CURRENT_TIMESTAMP) + ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + updated_at = CURRENT_TIMESTAMP + "#, + ) + .bind(key) + .bind(value) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn get_all_settings(&self) -> Result, sqlx::Error> { + sqlx::query_as::<_, (String, String)>( + "SELECT key, value FROM settings ORDER BY key" + ) + .fetch_all(&self.pool) + .await + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/db/types.rs b/apps/contentforge-desktop/src-tauri/src/db/types.rs new file mode 100644 index 0000000..ae5bdb3 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/db/types.rs @@ -0,0 +1,166 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +// ─────────────────────────── Enums ─────────────────────────── + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)] +#[sqlx(rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum SessionStatus { + Active, + Archived, + Pinned, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)] +#[sqlx(rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum MessageStatus { + Sending, + Streaming, + Completed, + Failed, + Cancelled, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)] +#[sqlx(rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum AssetStatus { + Ingested, + Processing, + Processed, + Ready, + Published, + Failed, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)] +#[sqlx(rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum DownloadStatus { + Pending, + Downloading, + Paused, + Completed, + Failed, + Cancelled, +} + +// ─────────────────────────── Structs ─────────────────────────── + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct Session { + pub id: String, + pub title: String, + pub agent_id: String, + pub status: SessionStatus, + pub linked_task_id: Option, + pub linked_asset_ids: String, // JSON array + pub metadata: Option, // JSON object + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct Message { + pub id: String, + pub session_id: String, + pub role: String, + pub content: String, + pub status: MessageStatus, + pub model: Option, + pub tokens_used: Option, // JSON {prompt, completion, total} + pub tool_calls: Option, // JSON array + pub tool_results: Option, // JSON array + pub selected_asset_ids: Option, // JSON array + pub error: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct Asset { + pub id: String, + pub title: String, + pub asset_type: String, + pub status: AssetStatus, + pub platform: Option, + pub url: Option, + pub file_path: Option, + pub thumbnail_url: Option, + pub description: Option, + pub extracted_text: Option, + pub summary: Option, + pub transcript: Option, + pub translated_text: Option, + pub rewritten_text: Option, + pub duration_sec: Option, + pub analysis: Option, // JSON + pub tags: String, // JSON array + pub pipeline_id: Option, + pub author: Option, + pub published_at: Option, + pub engagement: Option, // JSON + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct AgentSwitchRecord { + pub id: String, + pub session_id: String, + pub from_agent_id: String, + pub to_agent_id: String, + pub reason: Option, + pub triggered_by: String, + pub created_at: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct PipelineRun { + pub id: String, + pub pipeline_id: String, + pub asset_id: Option, + pub status: String, + pub progress: f64, + pub current_step: Option, + pub step_results: Option, // JSON + pub error: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct VttReport { + pub id: String, + pub youtube_url: String, + pub video_id: Option, + pub title: Option, + pub language: Option, + pub content: String, + pub cue_count: i64, + pub duration_sec: Option, + pub created_at: DateTime, + pub status: String, + pub error: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DownloadRecord { + pub id: String, + pub url: String, + pub title: Option, + pub status: DownloadStatus, + pub progress: f64, + pub speed: Option, + pub eta: Option, + pub output_dir: Option, + pub filename: Option, + pub subtitles: Vec, + pub error: Option, + pub queue_position: i64, + pub options: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} diff --git a/apps/contentforge-desktop/src-tauri/src/downloader.rs b/apps/contentforge-desktop/src-tauri/src/downloader.rs new file mode 100644 index 0000000..099b209 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/downloader.rs @@ -0,0 +1,717 @@ +use serde::{Deserialize, Serialize}; +use std::process::Stdio; +use std::time::Duration; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Command; + +/// Standard proxy env vars that Python's urllib (and yt-dlp) recognizes. +const ALLOWED_PROXY_VARS: &[&str] = &[ + "HTTP_PROXY", "http_proxy", + "HTTPS_PROXY", "https_proxy", + "ALL_PROXY", "all_proxy", + "NO_PROXY", "no_proxy", + "FTP_PROXY", "ftp_proxy", +]; + +fn proxy_vars_to_remove() -> Vec { + std::env::vars() + .filter(|(key, _)| { + let lower = key.to_lowercase(); + lower.ends_with("_proxy") && !ALLOWED_PROXY_VARS.contains(&key.as_str()) + }) + .map(|(key, _)| key) + .collect() +} + +async fn run_yt_dlp_blocking( + yt_dlp_path: String, + args: Vec, + timeout_secs: u64, +) -> Result { + let result = tokio::time::timeout( + Duration::from_secs(timeout_secs), + tokio::task::spawn_blocking(move || { + let mut cmd = std::process::Command::new(&yt_dlp_path); + cmd.args(&args); + for key in proxy_vars_to_remove() { + cmd.env_remove(&key); + } + cmd.output() + }), + ) + .await + .map_err(|_| "Request timed out while executing yt-dlp. YouTube may require cookies or the video may be blocked.".to_string())? + .map_err(|e| format!("Blocking task panicked: {}", e))? + .map_err(|e| format!("Failed to execute yt-dlp: {}", e))?; + + Ok(result) +} + +#[derive(Debug, Clone, Serialize)] +#[derive(Default)] +pub struct DownloadOptions { + pub url: String, + pub is_playlist: bool, + pub quality: Option, + pub format: Option, + pub output_dir: Option, + pub sub_langs: Option>, + pub write_subs: bool, + pub write_auto_subs: bool, + pub start_time: Option, + pub end_time: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DownloadProgress { + pub video_id: Option, + pub title: Option, + pub percent: f64, + pub speed: Option, + pub eta: Option, + pub status: String, + pub error: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DownloadLog { + pub level: String, + pub message: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DownloadOutput { + pub title: String, + pub filename: String, + pub subtitles: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct VideoInfoJson { + id: String, + title: String, + #[serde(default)] + _webpage_url: Option, + #[serde(default)] + duration: Option, + #[serde(default)] + thumbnail: Option, + #[serde(default)] + uploader: Option, +} + +// Output types returned by Downloader methods +#[derive(Debug, Serialize)] +pub struct VideoInfo { + pub id: String, + pub title: String, + pub duration: Option, + pub thumbnail: Option, + pub uploader: Option, + pub formats: Vec, +} + +#[derive(Debug, Serialize)] +pub struct VideoFormat { + pub format_id: String, + pub quality: String, + pub resolution: Option, + pub filesize: Option, +} + +#[derive(Debug, Serialize)] +pub struct FormatInfo { + pub format_id: String, + pub ext: String, + pub resolution: Option, + pub fps: Option, + pub filesize: Option, + pub filesize_approx: Option, + pub video_codec: Option, + pub audio_codec: Option, + pub vbr: Option, + pub abr: Option, + pub asr: Option, + pub quality: String, +} + +#[derive(Debug, Serialize)] +pub struct PlaylistVideo { + pub id: String, + pub title: String, + pub duration: Option, + pub thumbnail: Option, + pub uploader: Option, + pub webpage_url: String, +} + +#[derive(Debug, Serialize)] +pub struct PlaylistInfo { + pub id: String, + pub title: String, + pub uploader: Option, + pub description: Option, + pub thumbnail: Option, + pub entries: Vec, + pub webpage_url: String, +} + +pub struct Downloader { + options: DownloadOptions, + _download_id: String, + yt_dlp_path: Option, +} + +impl Downloader { + pub fn new(options: DownloadOptions, download_id: String) -> Self { + Self { + options, + _download_id: download_id, + yt_dlp_path: None, + } + } + + pub fn new_default() -> Self { + Self { + options: DownloadOptions { + url: String::new(), + is_playlist: false, + quality: None, + format: None, + output_dir: None, + sub_langs: None, + write_subs: true, + write_auto_subs: true, + start_time: None, + end_time: None, + }, + _download_id: String::new(), + yt_dlp_path: None, + } + } + + pub fn with_yt_dlp_path(mut self, path: Option) -> Self { + self.yt_dlp_path = path; + self + } + + pub async fn run_yt_dlp(&self, args: Vec, timeout_secs: u64) -> Result { + let yt_dlp_path = self.find_yt_dlp().await?; + run_yt_dlp_blocking(yt_dlp_path, args, timeout_secs).await + } + + pub async fn download(&self, mut on_progress: F, mut on_log: G, cancel_rx: &mut tokio::sync::mpsc::Receiver<()>) -> Result + where + F: FnMut(DownloadProgress), + G: FnMut(DownloadLog), + { + let yt_dlp_path = self.find_yt_dlp().await?; + let output_dir = self + .options + .output_dir + .clone() + .unwrap_or_else(|| self.default_download_dir()); + + tokio::fs::create_dir_all(&output_dir) + .await + .map_err(|e| format!("Failed to create output directory: {}", e))?; + + let mut args = vec![]; + + if let Some(quality) = &self.options.quality { + if quality != "best" { + args.push("-f".to_string()); + args.push(format!("bestvideo[height<={}]+bestaudio/best[height<={}]", quality, quality)); + } else { + args.push("-f".to_string()); + args.push("bestvideo+bestaudio/best".to_string()); + } + } else { + args.push("-f".to_string()); + args.push("bestvideo+bestaudio/best".to_string()); + } + + if let Some(format) = &self.options.format { + args.push("--merge-output-format".to_string()); + args.push(format.clone()); + } + + args.push("-o".to_string()); + args.push(format!("{}/%(title)s.%(ext)s", output_dir)); + + if self.options.write_subs { + args.push("--write-subs".to_string()); + if self.options.write_auto_subs { + args.push("--write-auto-subs".to_string()); + } + if let Some(langs) = &self.options.sub_langs { + args.push("--sub-langs".to_string()); + args.push(langs.join(",")); + } + } + + if self.options.start_time.is_some() || self.options.end_time.is_some() { + let mut section = String::new(); + section.push('*'); + section.push_str(&self.options.start_time.clone().unwrap_or_else(|| "0".to_string())); + section.push('-'); + section.push_str(&self.options.end_time.clone().unwrap_or_else(|| "inf".to_string())); + args.push("--download-sections".to_string()); + args.push(section); + args.push("--force-keyframes-at-cuts".to_string()); + } + + if !self.options.is_playlist { + args.push("--no-playlist".to_string()); + } + + args.push("--newline".to_string()); + args.push("--progress".to_string()); + args.push("--print-json".to_string()); + args.push(self.options.url.clone()); + + let cmd_str = format!("{} {}", yt_dlp_path, args.join(" ")); + on_log(DownloadLog { + level: "info".to_string(), + message: format!("Starting download: {}", cmd_str), + }); + + let mut child = { + let mut cmd = Command::new(&yt_dlp_path); + cmd.args(&args); + for key in proxy_vars_to_remove() { + cmd.env_remove(&key); + } + cmd.stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| format!("Failed to spawn yt-dlp: {}", e))? + }; + + let stdout = child.stdout.take().ok_or("Failed to capture stdout")?; + let stderr = child.stderr.take().ok_or("Failed to capture stderr")?; + + let mut stdout_reader = BufReader::new(stdout).lines(); + let mut stderr_reader = BufReader::new(stderr).lines(); + + let mut last_info: Option = None; + let progress_re = regex::Regex::new(r"\[download\]\s+([\d.]+)%.*?at\s+(\S+)\s+ETA\s+(\S+)") + .map_err(|e| format!("Failed to compile regex: {}", e))?; + + loop { + tokio::select! { + biased; + _ = cancel_rx.recv() => { + let _ = child.kill().await; + on_log(DownloadLog { + level: "info".to_string(), + message: "Download cancelled".to_string(), + }); + return Err("Download cancelled".to_string()); + } + line = stdout_reader.next_line() => { + match line { + Ok(Some(line)) => { + on_log(DownloadLog { + level: "info".to_string(), + message: line.clone(), + }); + + if line.starts_with('{') { + if let Ok(info) = serde_json::from_str::(&line) { + last_info = Some(info.clone()); + on_progress(DownloadProgress { + video_id: Some(info.id.clone()), + title: Some(info.title.clone()), + percent: 0.0, + speed: None, + eta: None, + status: "downloading".to_string(), + error: None, + }); + } + } else if let Some(caps) = progress_re.captures(&line) { + let percent: f64 = caps[1].parse().unwrap_or(0.0); + let speed = caps[2].to_string(); + let eta = caps[3].to_string(); + + on_progress(DownloadProgress { + video_id: last_info.as_ref().map(|i| i.id.clone()), + title: last_info.as_ref().map(|i| i.title.clone()), + percent, + speed: Some(speed), + eta: Some(eta), + status: "downloading".to_string(), + error: None, + }); + } + } + Ok(None) => break, + Err(e) => return Err(format!("Error reading stdout: {}", e)), + } + } + line = stderr_reader.next_line() => { + if let Ok(Some(line)) = line { + on_log(DownloadLog { + level: "error".to_string(), + message: line, + }); + } + } + } + } + + let status = child + .wait() + .await + .map_err(|e| format!("Failed to wait for yt-dlp: {}", e))?; + + if !status.success() { + on_log(DownloadLog { + level: "error".to_string(), + message: format!("yt-dlp exited with status: {:?}", status.code()), + }); + return Err("yt-dlp process failed".to_string()); + } + + on_log(DownloadLog { + level: "info".to_string(), + message: "Download completed successfully".to_string(), + }); + + if let Some(info) = last_info { + let ext = self.options.format.clone().unwrap_or_else(|| "mp4".to_string()); + let filename = format!("{}/{}.{}", output_dir, sanitize_filename(&info.title), ext); + let subtitles = self.find_subtitle_files(&output_dir, &info.title).await; + + Ok(DownloadOutput { + title: info.title, + filename, + subtitles, + }) + } else { + Err("No video information received".to_string()) + } + } + + pub async fn get_info(&self, url: &str) -> Result { + let yt_dlp_path = self.find_yt_dlp().await?; + + let args = vec![ + "--dump-json".to_string(), + "--no-download".to_string(), + "--socket-timeout".to_string(), + "10".to_string(), + "--no-warnings".to_string(), + url.to_string(), + ]; + + let output = run_yt_dlp_blocking(yt_dlp_path, args, 30).await?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!( + "yt-dlp failed: {}", + if stderr.is_empty() { "Unknown error".to_string() } else { stderr.to_string() } + )); + } + + let info: VideoInfoJson = serde_json::from_slice(&output.stdout) + .map_err(|e| format!("Failed to parse video info: {}", e))?; + + Ok(VideoInfo { + id: info.id, + title: info.title, + duration: info.duration.map(|d| d as i64), + thumbnail: info.thumbnail, + uploader: info.uploader, + formats: vec![], + }) + } + + pub async fn get_formats(&self, url: &str) -> Result, String> { + let yt_dlp_path = self.find_yt_dlp().await?; + + let args = vec![ + "--dump-json".to_string(), + "--no-download".to_string(), + "--socket-timeout".to_string(), + "10".to_string(), + "--no-warnings".to_string(), + url.to_string(), + ]; + + let output = run_yt_dlp_blocking(yt_dlp_path, args, 60).await?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!( + "yt-dlp failed: {}", + if stderr.is_empty() { "Unknown error".to_string() } else { stderr.to_string() } + )); + } + + #[derive(Debug, Deserialize)] + struct YtdlpFormat { + format_id: String, + ext: String, + resolution: Option, + fps: Option, + filesize: Option, + filesize_approx: Option, + vcodec: Option, + acodec: Option, + vbr: Option, + abr: Option, + asr: Option, + quality: Option, + format_note: Option, + } + + #[derive(Debug, Deserialize)] + struct YtdlpInfo { + formats: Vec, + } + + let info: YtdlpInfo = serde_json::from_slice(&output.stdout) + .map_err(|e| format!("Failed to parse formats: {}", e))?; + + let formats: Vec = info.formats.into_iter() + .map(|f| FormatInfo { + format_id: f.format_id, + ext: f.ext, + resolution: f.resolution, + fps: f.fps.map(|v| v as i32), + filesize: f.filesize, + filesize_approx: f.filesize_approx, + video_codec: f.vcodec, + audio_codec: f.acodec, + vbr: f.vbr, + abr: f.abr, + asr: f.asr, + quality: f.format_note.unwrap_or_else(|| { + f.quality.map(|q| format!("{}", q)).unwrap_or_default() + }), + }) + .collect(); + + Ok(formats) + } + + pub async fn get_playlist_info(&self, url: &str) -> Result { + let yt_dlp_path = self.find_yt_dlp().await?; + + let args = vec![ + "--dump-single-json".to_string(), + "--flat-playlist".to_string(), + "--socket-timeout".to_string(), + "10".to_string(), + "--no-warnings".to_string(), + url.to_string(), + ]; + + let output = run_yt_dlp_blocking(yt_dlp_path, args, 90).await?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!( + "yt-dlp failed: {}", + if stderr.is_empty() { "Unknown error".to_string() } else { stderr.to_string() } + )); + } + + #[derive(Debug, Deserialize)] + struct YtdlpPlaylist { + id: String, + title: String, + uploader: Option, + description: Option, + thumbnail: Option, + webpage_url: String, + entries: Vec, + } + + #[derive(Debug, Deserialize)] + struct YtdlpEntry { + id: String, + title: String, + duration: Option, + thumbnail: Option, + uploader: Option, + _webpage_url: Option, + _url: Option, + } + + let info: YtdlpPlaylist = serde_json::from_slice(&output.stdout) + .map_err(|e| format!("Failed to parse playlist info: {}", e))?; + + let entries: Vec = info.entries.into_iter() + .map(|e| PlaylistVideo { + id: e.id.clone(), + title: e.title, + duration: e.duration.map(|d| d as i64), + thumbnail: e.thumbnail, + uploader: e.uploader, + webpage_url: e._webpage_url.unwrap_or_else(|| + format!("https://www.youtube.com/watch?v={}", e.id) + ), + }) + .collect(); + + Ok(PlaylistInfo { + id: info.id, + title: info.title, + uploader: info.uploader, + description: info.description, + thumbnail: info.thumbnail, + entries, + webpage_url: info.webpage_url, + }) + } + + async fn find_yt_dlp(&self) -> Result { + if let Some(ref path) = self.yt_dlp_path { + if tokio::fs::metadata(path).await.is_ok() { + return Ok(path.clone()); + } + } + + if let Ok(path) = std::env::var("YT_DLP_BIN") { + if tokio::fs::metadata(&path).await.is_ok() { + return Ok(path); + } + } + + if let Ok(path) = std::env::var("VYTLD_BUNDLED_YT_DLP") { + if tokio::fs::metadata(&path).await.is_ok() { + return Ok(path); + } + } + + let is_windows = std::env::consts::OS == "windows"; + let lookup_cmd = if is_windows { "where" } else { "which" }; + let binaries = if is_windows { + vec!["yt-dlp.exe", "yt-dlp", "youtube-dl.exe", "youtube-dl"] + } else { + vec!["yt-dlp", "youtube-dl"] + }; + + for bin in &binaries { + if let Ok(output) = Command::new(lookup_cmd).arg(bin).output().await { + if output.status.success() { + let path = String::from_utf8_lossy(&output.stdout) + .lines() + .next() + .unwrap_or("") + .trim() + .to_string(); + if !path.is_empty() && tokio::fs::metadata(&path).await.is_ok() { + return Ok(path); + } + } + } + } + + let common_paths = if is_windows { + vec![ + r"C:\Users\%USERNAME%\AppData\Local\Microsoft\WinGet\Links\yt-dlp.exe", + r"C:\ProgramData\chocolatey\bin\yt-dlp.exe", + r"C:\Users\%USERNAME%\.local\bin\yt-dlp.exe", + ] + } else if std::env::consts::OS == "macos" { + vec![ + "/opt/homebrew/bin/yt-dlp", + "/usr/local/bin/yt-dlp", + "/usr/bin/yt-dlp", + "~/.local/bin/yt-dlp", + ] + } else { + vec![ + "/usr/bin/yt-dlp", + "/usr/local/bin/yt-dlp", + "/bin/yt-dlp", + "~/.local/bin/yt-dlp", + ] + }; + + for path in &common_paths { + let expanded = shellexpand::tilde(path).to_string(); + if tokio::fs::metadata(&expanded).await.is_ok() { + return Ok(expanded); + } + } + + Err(self.yt_dlp_install_hint()) + } + + fn yt_dlp_install_hint(&self) -> String { + let os = std::env::consts::OS; + let hint = match os { + "macos" => { + "yt-dlp not found.\n\n\ + Install hints for macOS:\n\ + • Homebrew: brew install yt-dlp\n\ + • pip: pip3 install yt-dlp\n\ + • Manual: download from https://github.com/yt-dlp/yt-dlp/releases\n\n\ + After installing, ensure yt-dlp is in your PATH, or set the path in Settings." + } + "windows" => { + "yt-dlp not found.\n\n\ + Install hints for Windows:\n\ + • WinGet: winget install yt-dlp\n\ + • Chocolatey: choco install yt-dlp\n\ + • pip: pip install yt-dlp\n\ + • Manual: download from https://github.com/yt-dlp/yt-dlp/releases\n\n\ + After installing, ensure yt-dlp is in your PATH, or set the path in Settings." + } + _ => { + "yt-dlp not found.\n\n\ + Install hints for Linux:\n\ + • pip: pip3 install yt-dlp\n\ + • apt: sudo apt install yt-dlp\n\ + • Manual: download from https://github.com/yt-dlp/yt-dlp/releases\n\n\ + After installing, ensure yt-dlp is in your PATH, or set the path in Settings." + } + }; + hint.to_string() + } + + fn default_download_dir(&self) -> String { + let home = dirs::download_dir() + .or_else(|| dirs::home_dir().map(|h| h.join("Downloads"))) + .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()); + + home.join("ContentForge").to_string_lossy().to_string() + } + + async fn find_subtitle_files(&self, dir: &str, title: &str) -> Vec { + let mut subtitles = vec![]; + let sanitized = sanitize_filename(title); + + let patterns = [ + format!("{}/{}*.vtt", dir, sanitized), + format!("{}/{}*.srt", dir, sanitized), + ]; + + for pattern in &patterns { + if let Ok(paths) = glob::glob(pattern) { + for path in paths.flatten() { + subtitles.push(path.to_string_lossy().to_string()); + } + } + } + + subtitles + } +} + +fn sanitize_filename(s: &str) -> String { + let re = regex::Regex::new(r#"[<>:"/\\|?*\x00-\x1f]"#).unwrap(); + let s = re.replace_all(s, "_"); + let s = s.trim(); + if s.len() > 120 { + s[..120].to_string() + } else { + s.to_string() + } +} diff --git a/apps/contentforge-desktop/src-tauri/src/lib.rs b/apps/contentforge-desktop/src-tauri/src/lib.rs index 9226c63..84f9962 100644 --- a/apps/contentforge-desktop/src-tauri/src/lib.rs +++ b/apps/contentforge-desktop/src-tauri/src/lib.rs @@ -1,5 +1,11 @@ +mod agent_cli; +mod agent_runner; +mod audio_extractor; mod commands; -mod database; +mod db; +mod downloader; +mod queue; +mod vtt_analysis; use tauri::Manager; @@ -10,6 +16,111 @@ fn get_platform() -> String { format!("{}-{}", os, arch) } +/// Determine the platform-specific yt-dlp resource directory and executable names +fn bundled_yt_dlp_info() -> (&'static str, &'static str, &'static str) { + let is_windows = std::env::consts::OS == "windows"; + if is_windows { + let arch = std::env::consts::ARCH; + if arch == "aarch64" { + ("windows-arm64", "yt-dlp_arm64.exe", "yt-dlp.exe") + } else { + ("windows-x86", "yt-dlp_x86.exe", "yt-dlp.exe") + } + } else if std::env::consts::OS == "macos" { + ("macos", "yt-dlp_macos", "yt-dlp") + } else { + ("linux", "yt-dlp_linux", "yt-dlp") + } +} + +/// Recursively copy a directory (async) +async fn copy_dir_all( + src: impl AsRef, + dst: impl AsRef, +) -> std::io::Result<()> { + tokio::fs::create_dir_all(&dst).await?; + let mut entries = tokio::fs::read_dir(src).await?; + while let Some(entry) = entries.next_entry().await? { + let ty = entry.file_type().await?; + if ty.is_dir() { + Box::pin(copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))).await?; + } else { + tokio::fs::copy(entry.path(), dst.as_ref().join(entry.file_name())).await?; + } + } + Ok(()) +} + +/// Extract bundled yt-dlp from app resources to app_data_dir on first run. +/// Sets VYTLD_BUNDLED_YT_DLP env var so downloader can find it. +async fn extract_bundled_yt_dlp(app: &tauri::App) -> Option { + let (resource_subdir, _source_exe, target_exe_name) = bundled_yt_dlp_info(); + + let app_data_dir = app.path().app_data_dir().ok()?; + let target_dir = app_data_dir.join("yt-dlp"); + let target_exe = target_dir.join(target_exe_name); + let marker = target_dir.join(".extracted"); + + // Already extracted? + if tokio::fs::metadata(&marker).await.is_ok() { + return Some(target_exe); + } + + // Resolve resource path using Tauri's resource directory + let resource_path = match app + .path() + .resolve(format!("yt-dlp/{}", resource_subdir), tauri::path::BaseDirectory::Resource) + { + Ok(p) => p, + Err(e) => { + log::warn!("Cannot resolve yt-dlp resource path: {}", e); + return None; + } + }; + + // Check if bundled resources exist (may not exist in dev mode before build) + if tokio::fs::metadata(&resource_path).await.is_err() { + log::info!( + "Bundled yt-dlp not found in resources at {:?}, skipping extraction", + resource_path + ); + return None; + } + + log::info!( + "Extracting bundled yt-dlp from {:?} to {:?}", + resource_path, + target_dir + ); + + // Remove old extraction if exists (handles partial/corrupted extractions) + let _ = tokio::fs::remove_dir_all(&target_dir).await; + + if let Err(e) = copy_dir_all(&resource_path, &target_dir).await { + log::error!("Failed to extract bundled yt-dlp: {}", e); + return None; + } + + // Make executable on Unix + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let _ = tokio::fs::set_permissions( + &target_exe, + std::fs::Permissions::from_mode(0o755), + ) + .await; + } + + // Write marker file to indicate successful extraction + if let Err(e) = tokio::fs::write(&marker, "").await { + log::warn!("Failed to write extraction marker: {}", e); + } + + log::info!("Bundled yt-dlp extracted successfully to {:?}", target_exe); + Some(target_exe) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -26,10 +137,17 @@ pub fn run() { )?; } - // Initialize database - let db = tauri::async_runtime::block_on(async { + // Extract bundled yt-dlp from resources on startup + tauri::async_runtime::block_on(async { + if let Some(path) = extract_bundled_yt_dlp(app).await { + std::env::set_var("VYTLD_BUNDLED_YT_DLP", path.to_string_lossy().to_string()); + } + }); + + // Initialize database — always call manage() so commands never fail with "state not managed" + let db: Result = tauri::async_runtime::block_on(async { // Try app data directory first - match database::Database::new(&app.handle()).await { + match db::Database::new(&app.handle()).await { Ok(db) => { if let Err(e) = db.init().await { log::error!("DB init failed (app dir): {}", e); @@ -45,7 +163,7 @@ pub fn run() { let cwd = std::env::current_dir().unwrap_or_default(); let fallback_path = cwd.join("contentforge-dev.db"); log::info!("Trying fallback database at: {:?}", fallback_path); - match database::Database::new_with_path(&fallback_path).await { + match db::Database::new_with_path(&fallback_path).await { Ok(db) => { if let Err(e) = db.init().await { log::error!("DB init failed (fallback): {}", e); @@ -59,29 +177,116 @@ pub fn run() { // Last resort: in-memory database log::warn!("Using in-memory database — data will not persist"); - let db = database::Database::new_in_memory() + let db = db::Database::new_in_memory() .await .expect("In-memory SQLite should always succeed"); db.init().await.expect("In-memory DB init should succeed"); Ok(db) }); - let db = db.expect("Database initialization must not fail"); - app.manage(db); + let db = db.expect("Database initialization must not fail — all fallbacks exhausted"); + + app.manage(db.clone()); + + // Initialize download queue (default: 3 concurrent downloads) + // Must create QueueManager inside block_on so tokio::spawn() has a runtime + let (queue_manager, incomplete) = tauri::async_runtime::block_on(async { + let max_concurrent = db + .get_setting("max_concurrent_downloads") + .await + .unwrap_or(None) + .and_then(|s| s.parse::().ok()) + .unwrap_or(3); + let qm = queue::QueueManager::new(db.clone(), max_concurrent); + + // Resume incomplete downloads from previous session + let incomplete = db.get_incomplete_downloads().await.unwrap_or_default(); + (qm, incomplete) + }); + + // Reset downloading → pending and re-enqueue with saved options + let app_handle = app.handle().clone(); + for record in incomplete { + let options = record.options.and_then(|opts| { + serde_json::from_str::(&opts).ok() + }).map(|req| crate::downloader::DownloadOptions { + url: req.url, + is_playlist: req.is_playlist, + quality: req.quality, + format: req.format, + output_dir: req.output_dir, + sub_langs: req.sub_langs, + write_subs: req.write_subs.unwrap_or(true), + write_auto_subs: req.write_auto_subs.unwrap_or(true), + start_time: req.start_time, + end_time: req.end_time, + }).unwrap_or_else(|| crate::downloader::DownloadOptions { + url: record.url.clone(), + is_playlist: false, + output_dir: record.output_dir.clone(), + ..Default::default() + }); + + tauri::async_runtime::block_on(async { + let _ = db.update_download_status(&record.id, crate::db::DownloadStatus::Pending).await; + queue_manager.enqueue(record.id, options, None, app_handle.clone()).await; + }); + } + + app.manage(queue_manager); Ok(()) }) .invoke_handler(tauri::generate_handler![ + // Chat commands commands::get_chat_sessions, commands::create_chat_session, commands::get_chat_history, commands::chat_send, commands::cancel_chat_stream, + commands::archive_chat_session, + commands::pin_chat_session, + commands::update_chat_session_title, + commands::delete_chat_session, + commands::chat_retry, + commands::delete_chat_message, + commands::confirm_tool_call, + // Agent commands commands::get_agents, commands::switch_agent, + commands::get_quick_actions, + commands::get_skills, + commands::execute_skill, + // Asset commands commands::search_assets, commands::get_asset_detail, + commands::delete_asset, + commands::update_asset_tags, + commands::add_asset_to_session, + commands::get_asset_groups, + // Settings commands commands::get_settings, commands::update_settings, + // Download commands (from vYtDL) + commands::start_download, + commands::cancel_download, + commands::get_downloads, + commands::get_download_by_id, + commands::delete_download, + commands::open_download_folder, + commands::retry_download, + // Video commands (from vYtDL) + commands::get_video_info, + commands::get_video_formats, + commands::get_playlist_info, + // AI / VTT commands (from vYtDL) + commands::summarize_video, + commands::extract_audio, + commands::analyze_vtt, + commands::get_vtt_report, + commands::list_vtt_reports, + commands::delete_vtt_report, + commands::agent_chat_send, + commands::detect_agent_cli, get_platform, ]) .run(tauri::generate_context!()) diff --git a/apps/contentforge-desktop/src-tauri/src/queue.rs b/apps/contentforge-desktop/src-tauri/src/queue.rs new file mode 100644 index 0000000..ea00de0 --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/queue.rs @@ -0,0 +1,273 @@ +use std::collections::{HashMap, VecDeque}; +use tokio::sync::mpsc; +use tauri::{AppHandle, Emitter, Manager}; + +use crate::db::{Database, DownloadStatus}; +use crate::downloader::{DownloadLog, DownloadOptions, DownloadProgress, Downloader}; +use crate::commands::ApiResponse; + +/// Command sent to the queue manager background task. +pub enum QueueCommand { + Enqueue { + id: String, + options: DownloadOptions, + yt_dlp_path: Option, + app: AppHandle, + }, + Cancel { + id: String, + }, + Finished { + id: String, + }, + SetMaxConcurrent { + max: usize, + }, +} + +/// Information about a pending download. +struct PendingDownload { + id: String, + options: DownloadOptions, + yt_dlp_path: Option, +} + +/// Shared queue manager handle. +pub struct QueueManager { + tx: mpsc::Sender, +} + +impl QueueManager { + pub fn new(db: Database, max_concurrent: usize) -> Self { + let (tx, rx) = mpsc::channel::(100); + tokio::spawn(run_queue(rx, db, max_concurrent)); + Self { tx } + } + + pub async fn enqueue( + &self, + id: String, + options: DownloadOptions, + yt_dlp_path: Option, + app: AppHandle, + ) { + let _ = self + .tx + .send(QueueCommand::Enqueue { + id, + options, + yt_dlp_path, + app, + }) + .await; + } + + pub async fn cancel(&self, id: String) { + let _ = self.tx.send(QueueCommand::Cancel { id }).await; + } + + pub async fn notify_finished(&self, id: String) { + let _ = self.tx.send(QueueCommand::Finished { id }).await; + } + + pub async fn set_max_concurrent(&self, max: usize) { + let _ = self.tx.send(QueueCommand::SetMaxConcurrent { max }).await; + } +} + +async fn run_queue( + mut rx: mpsc::Receiver, + db: Database, + mut max_concurrent: usize, +) { + let mut active: HashMap> = HashMap::new(); + let mut pending: VecDeque = VecDeque::new(); + let mut cancel_txs: HashMap> = HashMap::new(); + let mut app_handle: Option = None; + + while let Some(cmd) = rx.recv().await { + match cmd { + QueueCommand::Enqueue { + id, + options, + yt_dlp_path, + app, + } => { + app_handle = Some(app.clone()); + pending.push_back(PendingDownload { + id, + options, + yt_dlp_path, + }); + update_pending_positions(&pending, &db).await; + try_start_pending( + &mut active, + &mut pending, + &mut cancel_txs, + &db, + max_concurrent, + &app, + ) + .await; + } + QueueCommand::Cancel { id } => { + if let Some(tx) = cancel_txs.get(&id) { + let _ = tx.send(()).await; + } + pending.retain(|p| p.id != id); + let _ = db.update_download_status(&id, DownloadStatus::Cancelled).await; + } + QueueCommand::Finished { id } => { + active.remove(&id); + cancel_txs.remove(&id); + update_pending_positions(&pending, &db).await; + if let Some(ref app) = app_handle { + try_start_pending( + &mut active, + &mut pending, + &mut cancel_txs, + &db, + max_concurrent, + app, + ) + .await; + } + } + QueueCommand::SetMaxConcurrent { max } => { + max_concurrent = max.max(1).min(10); + log::info!("Queue max_concurrent updated to {}", max_concurrent); + if let Some(ref app) = app_handle { + try_start_pending( + &mut active, + &mut pending, + &mut cancel_txs, + &db, + max_concurrent, + app, + ) + .await; + } + } + } + } +} + +async fn update_pending_positions( + pending: &VecDeque, + db: &Database, +) { + for (i, item) in pending.iter().enumerate() { + if let Err(e) = db.update_queue_position(&item.id, (i + 1) as i64).await { + log::error!("Failed to update queue position for {}: {}", item.id, e); + } + } +} + +async fn try_start_pending( + active: &mut HashMap>, + pending: &mut VecDeque, + cancel_txs: &mut HashMap>, + db: &Database, + max_concurrent: usize, + app: &AppHandle, +) { + while active.len() < max_concurrent { + let Some(download) = pending.pop_front() else { + break; + }; + + let PendingDownload { + id, + options, + yt_dlp_path, + } = download; + + // Update DB status to downloading and clear queue position + if let Err(e) = db.update_download_status(&id, DownloadStatus::Downloading).await { + log::error!("Failed to update download status: {}", e); + continue; + } + if let Err(e) = db.update_queue_position(&id, 0).await { + log::error!("Failed to reset queue position: {}", e); + } + + // Emit status change event so UI updates immediately + let _ = app.emit(&format!("download:status:{}", id), "downloading"); + + let (cancel_tx, cancel_rx) = mpsc::channel::<()>(1); + cancel_txs.insert(id.clone(), cancel_tx); + + let db_clone = db.clone(); + let app_clone = app.clone(); + + let id_for_task = id.clone(); + let handle = tokio::spawn(async move { + run_download_task(id_for_task, options, yt_dlp_path, db_clone, app_clone, cancel_rx) + .await; + }); + + active.insert(id, handle); + } +} + +async fn run_download_task( + id: String, + options: DownloadOptions, + yt_dlp_path: Option, + db: Database, + app: AppHandle, + mut cancel_rx: mpsc::Receiver<()>, +) { + let downloader = Downloader::new(options.clone(), id.clone()).with_yt_dlp_path(yt_dlp_path); + + let result = downloader + .download( + |progress: DownloadProgress| { + let _ = app.emit(&format!("download:progress:{}", id), progress); + }, + |log: DownloadLog| { + let _ = app.emit(&format!("download:log:{}", id), log); + }, + &mut cancel_rx, + ) + .await; + + match result { + Ok(output) => { + if let Err(e) = db + .update_download_complete( + &id, + Some(output.title.clone()), + Some(output.filename.clone()), + output.subtitles.clone(), + ) + .await + { + log::error!("Failed to update download completion: {}", e); + } + let _ = app.emit( + &format!("download:complete:{}", id), + ApiResponse::ok(output), + ); + } + Err(e) => { + if e == "Download cancelled" { + if let Err(db_err) = db.update_download_status(&id, DownloadStatus::Cancelled).await { + log::error!("Failed to update cancel status: {}", db_err); + } + } else { + if let Err(db_err) = db.update_download_error(&id, &e).await { + log::error!("Failed to update download error: {}", db_err); + } + } + let _ = app.emit( + &format!("download:error:{}", id), + ApiResponse::<()>::err(e), + ); + } + } + + // Notify queue that this download finished + let queue = app.state::(); + queue.notify_finished(id).await; +} diff --git a/apps/contentforge-desktop/src-tauri/src/vtt_analysis.rs b/apps/contentforge-desktop/src-tauri/src/vtt_analysis.rs new file mode 100644 index 0000000..8e3478a --- /dev/null +++ b/apps/contentforge-desktop/src-tauri/src/vtt_analysis.rs @@ -0,0 +1,280 @@ +use chrono::Utc; +use regex::Regex; +use serde::Serialize; +use std::path::PathBuf; +use tauri::{AppHandle, Emitter}; +use tokio::process::Command; +use uuid::Uuid; + +use crate::db::{Database, VttReport}; +use crate::downloader::Downloader; + +#[derive(Debug, Clone, Serialize)] +struct VttStatusEvent { + #[serde(rename = "reportId")] + report_id: String, + status: String, + #[serde(skip_serializing_if = "Option::is_none")] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +struct VttCompleteEvent { + #[serde(rename = "reportId")] + report_id: String, +} + +pub async fn start_analysis( + app: AppHandle, + db: Database, + youtube_url: String, +) -> Result { + let id = Uuid::new_v4().to_string(); + let video_id = extract_video_id(&youtube_url); + + let report = VttReport { + id: id.clone(), + youtube_url: youtube_url.clone(), + video_id: video_id.clone(), + title: None, + language: None, + content: String::new(), + cue_count: 0, + duration_sec: None, + created_at: Utc::now(), + status: "pending".to_string(), + error: None, + }; + + db.create_vtt_report(&report) + .await + .map_err(|e| e.to_string())?; + + emit_status(&app, &id, "pending", None); + + let app_clone = app.clone(); + let db_clone = db.clone(); + tauri::async_runtime::spawn(async move { + run_analysis(app_clone, db_clone, id, youtube_url).await; + }); + + Ok(report.id) +} + +async fn run_analysis(app: AppHandle, db: Database, id: String, youtube_url: String) { + let output_dir = default_output_dir(); + let vtt_dir = PathBuf::from(&output_dir).join("vtt-temp").join(&id); + + let result = async { + tokio::fs::create_dir_all(&vtt_dir).await.map_err(|e| e.to_string())?; + + db.update_vtt_report(&id, None, None, None, None, None, None, Some("processing"), None) + .await + .map_err(|e| e.to_string())?; + emit_status(&app, &id, "processing", None); + + let downloader = Downloader::new_default(); + let video_info = fetch_video_info(&downloader, &youtube_url).await; + let (language, vtt_path) = download_vtt(&downloader, &youtube_url, &vtt_dir).await?; + let markdown = convert_vtt_to_markdown(&vtt_path).await?; + let cue_count = count_cues(&markdown); + let duration_sec = video_info.as_ref().map(|info| info.duration); + + db.update_vtt_report( + &id, + video_info.as_ref().map(|info| info.title.as_str()), + Some(&language), + Some(&markdown), + Some(cue_count), + duration_sec, + extract_video_id(&youtube_url).as_deref(), + Some("done"), + None, + ) + .await + .map_err(|e| e.to_string())?; + + emit_complete(&app, &id); + Ok::<(), String>(()) + } + .await; + + if let Err(error) = result { + let _ = db + .update_vtt_report(&id, None, None, None, None, None, None, Some("failed"), Some(&error)) + .await; + emit_status(&app, &id, "failed", Some(error)); + } + + if let Ok(report) = db.get_vtt_report(&id).await { + if let Some(report) = report { + if report.status == "done" || report.status == "failed" { + let _ = tokio::fs::remove_dir_all(&vtt_dir).await; + } + } + } +} + +struct VideoInfo { + title: String, + duration: f64, +} + +async fn fetch_video_info(downloader: &Downloader, url: &str) -> Option { + match downloader.get_info(url).await { + Ok(info) => Some(VideoInfo { + title: info.title, + duration: info.duration.unwrap_or(0) as f64, + }), + Err(_) => None, + } +} + +async fn download_vtt( + downloader: &Downloader, + url: &str, + output_dir: &PathBuf, +) -> Result<(String, PathBuf), String> { + let langs = ["zh", "en", "ja", "ko", "zh-Hans", "zh-Hant"]; + + for lang in langs { + let out_template = output_dir.join("%(id)s.%(lang)s.vtt"); + let out_template = out_template.to_string_lossy().to_string(); + + let args = vec![ + "--write-subs".to_string(), + "--write-auto-subs".to_string(), + "--sub-langs".to_string(), + lang.to_string(), + "--skip-download".to_string(), + "-o".to_string(), + out_template, + url.to_string(), + ]; + + if downloader.run_yt_dlp(args, 60).await.is_err() { + continue; + } + + let mut entries = tokio::fs::read_dir(output_dir).await.map_err(|e| e.to_string())?; + while let Some(entry) = entries.next_entry().await.map_err(|e| e.to_string())? { + let name = entry.file_name().to_string_lossy().to_string(); + if name.ends_with(".vtt") + && (name.contains(&format!(".{}.", lang)) || name.contains(&format!(".{}-", lang))) + { + return Ok((lang.to_string(), entry.path())); + } + } + } + + Err("No subtitles found".to_string()) +} + +async fn convert_vtt_to_markdown(vtt_path: &PathBuf) -> Result { + let cli_path = find_contentforge_cli().await?; + + let output = Command::new(&cli_path) + .args(["analyze", "--mode", "markdown", &vtt_path.to_string_lossy()]) + .output() + .await + .map_err(|e| format!("Failed to run CLI: {}", e))?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(if stderr.is_empty() { + format!("vtt analyze failed with code {:?}", output.status.code()) + } else { + stderr.to_string() + }) + } +} + +fn count_cues(markdown: &str) -> i64 { + let re = Regex::new(r"\d{2}:\d{2}:\d{2}\s+\S").unwrap(); + re.find_iter(markdown).count() as i64 +} + +fn extract_video_id(url: &str) -> Option { + let re = Regex::new( + r"(?:youtube\.com/(?:watch\?v=|embed/|shorts/)|youtu\.be/)([a-zA-Z0-9_-]{11})", + ) + .unwrap(); + re.captures(url) + .and_then(|caps| caps.get(1)) + .map(|m| m.as_str().to_string()) +} + +async fn find_contentforge_cli() -> Result { + if let Ok(path) = std::env::var("CONTENTFORGE_CLI_PATH") { + if tokio::fs::metadata(&path).await.is_ok() { + return Ok(path); + } + } + + if let Ok(mut cwd) = std::env::current_dir() { + for _ in 0..6 { + let candidate = cwd.join("vYtDL").join("vYtDL"); + if tokio::fs::metadata(&candidate).await.is_ok() { + return Ok(candidate.to_string_lossy().to_string()); + } + if !cwd.pop() { + break; + } + } + } + + let lookup_cmd = if std::env::consts::OS == "windows" { + "where" + } else { + "which" + }; + + if let Ok(output) = Command::new(lookup_cmd).arg("vYtDL").output().await { + if output.status.success() { + let path = String::from_utf8_lossy(&output.stdout) + .lines() + .next() + .unwrap_or("") + .trim() + .to_string(); + if !path.is_empty() && tokio::fs::metadata(&path).await.is_ok() { + return Ok(path); + } + } + } + + Err("vYtDL CLI not found. Set CONTENTFORGE_CLI_PATH or ensure vYtDL is in PATH.".to_string()) +} + +fn default_output_dir() -> String { + std::env::var("CONTENTFORGE_OUTPUT_DIR").unwrap_or_else(|_| { + dirs::download_dir() + .or_else(|| dirs::home_dir().map(|h| h.join("Downloads"))) + .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()) + .join("ContentForge") + .to_string_lossy() + .to_string() + }) +} + +fn emit_status(app: &AppHandle, report_id: &str, status: &str, error: Option) { + let _ = app.emit( + "vtt-report:status", + VttStatusEvent { + report_id: report_id.to_string(), + status: status.to_string(), + error, + }, + ); +} + +fn emit_complete(app: &AppHandle, report_id: &str) { + let _ = app.emit( + "vtt-report:complete", + VttCompleteEvent { + report_id: report_id.to_string(), + }, + ); +} diff --git a/apps/vytdl-desktop/Taskfile.yml b/apps/vytdl-desktop/Taskfile.yml new file mode 100644 index 0000000..2b231c2 --- /dev/null +++ b/apps/vytdl-desktop/Taskfile.yml @@ -0,0 +1,164 @@ +version: "3" + +vars: + PROJECT_ROOT: + sh: pwd + MONOREPO_ROOT: "{{.PROJECT_ROOT}}/../.." + DESKTOP_DIR: "{{.PROJECT_ROOT}}" + TAURI_SRC_DIR: "{{.PROJECT_ROOT}}/src-tauri" + +tasks: + default: + desc: Show available tasks + cmds: + - task --list + + dev: + desc: Start desktop app in development mode + deps: [install] + cmds: + - pnpm tauri:dev + interactive: true + + stop: + desc: Stop running desktop dev processes + cmds: + - | + stopped=0 + # Kill Tauri dev (next-dev + cargo run) + for pattern in "next dev" "tauri" "vytdl-desktop"; do + pids=$(pgrep -f "$pattern" 2>/dev/null || true) + if [ -n "$pids" ]; then + echo "Stopping $pattern (PIDs: $pids)" + echo "$pids" | xargs kill 2>/dev/null || true + stopped=$((stopped + 1)) + fi + done + if [ $stopped -eq 0 ]; then + echo "No running desktop processes found." + else + # Wait briefly for graceful shutdown + sleep 1 + # Force kill any survivors + for pattern in "next dev" "tauri" "vytdl-desktop"; do + pids=$(pgrep -f "$pattern" 2>/dev/null || true) + if [ -n "$pids" ]; then + echo "$pids" | xargs kill -9 2>/dev/null || true + fi + done + echo "All desktop processes stopped." + fi + ignore_error: true + + restart: + desc: Stop running app and restart in dev mode + cmds: + - task: stop + - task: dev + + build: + desc: Build desktop app for production + deps: [install, download-ytdlp] + cmds: + - pnpm tauri:build + + bundle: + desc: Build + show installer/package locations + deps: [build] + cmds: + - echo "Build artifacts:" + - find {{.TAURI_SRC_DIR}}/target/release/bundle -type f 2>/dev/null || echo "No bundle output found" + + check: + desc: Check all required dependencies + cmds: + - | + echo "Dependency Check" + echo "================" + for cmd in pnpm node rustc cargo git; do + if command -v $cmd >/dev/null 2>&1; then + ver=$($cmd --version 2>/dev/null | head -1) + printf "%-12s OK %s\n" "$cmd" "$ver" + else + printf "%-12s MISSING\n" "$cmd" + fi + done + + install: + desc: Install Node dependencies via pnpm + cmds: + - pnpm install --silent + status: + - test -d {{.MONOREPO_ROOT}}/node_modules/.pnpm + + clean: + desc: Clean all build artifacts + cmds: + - rm -rf {{.DESKTOP_DIR}}/.next + - rm -rf {{.DESKTOP_DIR}}/out + - rm -rf {{.DESKTOP_DIR}}/node_modules/.cache + - rm -rf {{.TAURI_SRC_DIR}}/target + - rm -rf {{.MONOREPO_ROOT}}/packages/ui/.turbo + - rm -rf {{.MONOREPO_ROOT}}/packages/ui/.cache + - echo "Clean complete." + + download-ytdlp: + desc: Download yt-dlp binaries for bundling + cmds: + - python3 {{.MONOREPO_ROOT}}/scripts/download-yt-dlp-binaries.py + status: + - test -f {{.TAURI_SRC_DIR}}/resources/yt-dlp/.downloaded + + schedule: + desc: Run scheduled download tasks from schedule.json + cmds: + - | + CONFIG="{{.MONOREPO_ROOT}}/scripts/schedule.json" + if [ ! -f "$CONFIG" ]; then + echo "No schedule.json found. Create one with task schedule:init" + exit 1 + fi + python3 -c " + import json, os, subprocess, sys + config = json.load(open('$CONFIG')) + tasks = [t for t in config.get('tasks', []) if t.get('enabled')] + if not tasks: + print('No enabled tasks.') + sys.exit(0) + yt_dlp = config.get('settings', {}).get('yt_dlp_path', 'yt-dlp') + for t in tasks: + print(f\"Running: {t['name']}\") + out = os.path.expanduser(t.get('output_dir', '~/Downloads/vYtDL/scheduled')) + os.makedirs(out, exist_ok=True) + q = t.get('quality', 'best') + fmt = t.get('format', 'mp4') + cmd = [yt_dlp, '--format', f'bestvideo[height<={q}]+bestaudio/best' if q != 'best' else 'best', + '--merge-output-format', fmt, '--output', os.path.join(out, '%(title)s.%(ext)s'), + '--write-subs', '--sub-langs', 'en,zh', t['url']] + r = subprocess.run(cmd) + print(f\" {'OK' if r.returncode == 0 else 'FAILED'}\") + " + + schedule:init: + desc: Create example schedule.json config + cmds: + - | + cat > {{.MONOREPO_ROOT}}/scripts/schedule.json << 'EOF' + { + "tasks": [ + { + "name": "Example Playlist Backup", + "url": "https://www.youtube.com/playlist?list=...", + "quality": "1080", + "format": "mp4", + "output_dir": "~/Downloads/vYtDL/scheduled", + "enabled": false + } + ], + "settings": { + "yt_dlp_path": "yt-dlp", + "default_output_dir": "~/Downloads/vYtDL/scheduled" + } + } + EOF + echo "Created scripts/schedule.json — edit it and run: task schedule" diff --git a/contentforge/docs/EXTERNAL_REPO_INTEGRATION_PLAN.md b/contentforge/docs/EXTERNAL_REPO_INTEGRATION_PLAN.md new file mode 100644 index 0000000..8a91c5f --- /dev/null +++ b/contentforge/docs/EXTERNAL_REPO_INTEGRATION_PLAN.md @@ -0,0 +1,390 @@ +# ContentForge 外部仓库整合规划 + +> 基于对 7 个 GitHub 仓库的深入分析,本文档提供完整的整合优先级、路径规划和实施建议。 +> 分析时间: 2026-07-25 + +--- + +## 一、仓库整合评估总览 + +| # | 仓库 | 功能定位 | 技术栈 | 整合价值 | 整合策略 | +|---|------|----------|--------|---------|---------| +| 1 | **skill-studio** | Skill 资产管理器(45+ 平台) | Tauri v2 + React + Rust + SQLite | 🔴 **高** | 架构借鉴 + 部分模块移植 | +| 2 | **frameflow** | AI 视频处理 + 时间线编辑 | Electron + Vue 3 + FFmpeg | 🔴 **高** | 算法移植 + Pipeline 借鉴 | +| 3 | **capsummarize** | Chrome 扩展:视频转录 + 34 种 AI 模板 | Chrome Extension + TypeScript | 🔴 **高** | Prompt 资产迁移 + VTT 模块 | +| 4 | **youtube-rag-system (ClipIQ)** | YouTube RAG 问答引擎 | FastAPI + Python + LangChain | 🔴 **高** | Python Sidecar 微服务 | +| 5 | **OpenMontage** | AI 编排视频生产平台 | Python + Remotion + FastAPI | 🔴 **高** | 架构借鉴 + 渲染引擎集成 | +| 6 | **skill-zoo** | Agent Skill 包管理器 | Tauri v2 + React + Rust | 🟡 **中** | 架构参考(UI/IPC/状态管理) | +| 7 | **Video-Note-Extractor** | Python 视频转笔记脚本 | Python + Whisper + Gemini | 🟢 **低** | Prompt 模板参考即可 | + +--- + +## 二、整合优先级矩阵 + +### P0 — 立即整合(1-2 周) + +| 来源 | 可复用资产 | 整合方式 | 工作量 | +|------|-----------|---------|--------| +| capsummarize | 34 种 AI Prompt 模板 | 迁移为 ContentForge Skill JSON | 1-2 天 | +| capsummarize | VTT 解析逻辑 (`vtt.ts`) | 移植到前端 utils | 1 天 | +| youtube-rag-system | 5层 fallback 转录提取 | Python Sidecar FastAPI 服务 | 3-5 天 | +| frameflow | FFmpeg 封装命令 | Rust Tauri 后端命令 | 2-3 天 | + +### P1 — 短期整合(2-4 周) + +| 来源 | 可复用资产 | 整合方式 | 工作量 | +|------|-----------|---------|--------| +| youtube-rag-system | RAG 问答/摘要 Pipeline | 扩展 Sidecar 服务接口 | 1-2 周 | +| frameflow | 场景检测 (PySceneDetect) | 集成为视频分析 Skill | 3-5 天 | +| frameflow | 时间线数据模型 | 视频编辑功能蓝本 | 1 周 | +| OpenMontage | 视频合成引擎 (Remotion) | Node.js 子进程渲染 | 1-2 周 | +| skill-studio | 平台检测与同步逻辑 | Rust 后端移植 | 3-5 天 | +| skill-studio | Diff 组件 | React 组件复用 | 2-3 天 | + +### P2 — 中期整合(1-2 月) + +| 来源 | 可复用资产 | 整合方式 | 工作量 | +|------|-----------|---------|--------| +| OpenMontage | 技能系统三层架构 | 升级 Agent 能力设计 | 2-3 周 | +| OpenMontage | 质量门控与审计追踪 | 内容质量保障层 | 1-2 周 | +| OpenMontage | 预算控制系统 | API 成本治理 | 1 周 | +| skill-zoo | Tauri IPC 命令组织模式 | 重构 Rust 后端 | 1-2 周 | +| skill-zoo | 前端状态管理模式 | Zustand 优化参考 | 3-5 天 | + +### P3 — 参考借鉴(不直接整合) + +| 来源 | 借鉴内容 | 用途 | +|------|---------|------| +| Video-Note-Extractor | 四段式笔记 Prompt 模板 | 笔记生成 Skill 默认 Prompt | +| skill-zoo | Markdown 编辑器/文件树 UI | 前端组件设计参考 | +| frameflow | 视频编辑器 PRD(10万字符) | 产品规划参考 | + +--- + +## 三、技术架构整合方案 + +### 3.1 整体架构图 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ContentForge Desktop App │ +│ (Tauri v2 + Next.js + React + Rust) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Chat UI │ │ Skill Panel │ │ Content View │ │ Video Editor │ │ +│ │ (React) │ │ (React) │ │ (React) │ │ (React) │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ │ +│ ┌──────▼─────────────────▼─────────────────▼─────────────────▼───────┐ │ +│ │ Zustand Store Layer │ │ +│ │ (downloadStore | settingsStore | skillStore | chatStore) │ │ +│ └──────┬─────────────────┬─────────────────┬─────────────────┬───────┘ │ +│ │ │ │ │ │ +│ ┌──────▼──────┐ ┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │ +│ │ Tauri IPC │ │ Tauri IPC │ │ Tauri IPC │ │ HTTP API │ │ +│ │ Commands │ │ Commands │ │ Commands │ │ (Sidecar) │ │ +│ └──────┬──────┘ └───────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +└─────────┼─────────────────┼─────────────────┼─────────────────┼─────────┘ + │ │ │ │ +┌─────────▼─────────────────▼─────────────────▼─────────────────▼─────────┐ +│ Rust Backend (Tauri) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ +│ │ Download │ │ Database │ │ FFmpeg │ │ Skill Registry │ │ +│ │ Manager │ │ (SQLite) │ │ Wrapper │ │ (Rust) │ │ +│ │ (yt-dlp) │ │ │ │ │ │ │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────────┘ │ +└────────────────────────────────────────┬────────────────────────────────┘ + │ + ┌──────────▼──────────┐ + │ Python Sidecar │ + │ (FastAPI Server) │ + ├─────────────────────┤ + │ • RAG Pipeline │ + │ • Transcript Extract│ + │ • LangChain Chains │ + │ • ChromaDB │ + │ • Whisper Transcribe│ + └──────────┬──────────┘ + │ + ┌──────────▼──────────┐ + │ Remotion Render │ + │ (Node.js Child) │ + └─────────────────────┘ +``` + +### 3.2 模块整合详细设计 + +#### A. Capsummarize → ContentForge Skill 系统 + +``` +capsummarize 34 Prompt 模板 ──→ ContentForge Skill JSON 格式 + +before: + Chrome Extension 直接注入 ChatGPT DOM + +after: + 1. Prompt 模板 → src-tauri/skills/capsummarize/ + ├── summary.json # 文本摘要 + ├── key-points.json # 关键点提取 + ├── short-video-script.json + ├── thumbnail-idea.json + └── ... (34 templates) + + 2. VTT 解析 → apps/desktop/src/lib/vtt-parser.ts + ├── YouTube VTT + ├── Zoom VTT + └── Udemy VTT + + 3. UI → apps/desktop/src/components/skill-output-panel.tsx + ├── 输出类型切换器 + └── 风格选择器 +``` + +#### B. youtube-rag-system → Python Sidecar + +``` +ClipIQ FastAPI Backend ──→ ContentForge Sidecar Process + +整合方案: + 1. 打包为独立 Python 可执行文件 (PyInstaller/uv) + 2. Tauri 启动时作为 sidecar 进程启动 + 3. Next.js 前端通过 HTTP API 调用 + +接口映射: + POST /api/analyze → 单视频 RAG 分析 + POST /api/compare → 双视频对比 + POST /api/transcript → 转录提取(带 fallback) + POST /api/qa → 时间戳问答 + +环境变量注入: + - OPENROUTER_API_KEY + - YOUTUBE_DATA_API_KEY + - OLLAMA_HOST +``` + +#### C. frameflow → 视频处理模块 + +``` +FrameFlow FFmpeg/Vue ──→ ContentForge Rust + React + +可移植模块: + 1. FFmpeg 命令封装 + - 降采样: `-vf scale=480:-2` + - 音频提取: `-vn -acodec copy` + - 静音检测: `silencedetect` + - 视频组装: `concat` + + 2. 场景检测 Pipeline + PySceneDetect ──→ Python Sidecar 接口 + POST /api/scene-detect {video_path} + + 3. 时间线数据模型 (TypeScript) + interface Timeline { + tracks: Track[]; + duration: number; + fps: number; + } + interface Track { + id: string; + type: 'video' | 'audio' | 'text'; + items: TimelineItem[]; + } +``` + +#### D. OpenMontage → 视频渲染引擎 + +``` +OpenMontage Remotion ──→ ContentForge Video Export + +整合方案: + 1. 在 Rust 后端添加 Node.js 子进程调用 + 2. 前端传入:视频片段列表 + 字幕 + BGM + 风格模板 + 3. Remotion 渲染输出 MP4/WebM + +渲染 Pipeline: + ContentForge UI ──→ Rust IPC ──→ Node.js Script + ↓ + Remotion Composition + ↓ + FFmpeg 后处理 + ↓ + 输出到下载目录 +``` + +#### E. skill-studio → Skill 管理中心 + +``` +Skill Studio 平台检测 ──→ ContentForge 平台适配器 + +可移植模块: + 1. 45+ 平台目录识别逻辑 (Rust) + 2. Skill 快照/版本管理 (SQLite Schema) + 3. Diff 组件 (React) + 4. 文件系统操作封装 (Rust) + +不移植: + - Ant Design UI(改为 Tailwind) + - Vite 配置(已有 Next.js) + - React Context(已有 Zustand) +``` + +--- + +## 四、实施路线图 + +### Phase 1: 基础能力补齐(第 1-2 周) + +**目标**: 快速获得 Prompt 模板库和转录能力 + +| 天数 | 任务 | 来源仓库 | +|------|------|---------| +| 1-2 | 迁移 34 种 Prompt 模板为 Skill JSON | capsummarize | +| 2-3 | 移植 VTT 解析器到前端 | capsummarize | +| 3-5 | 搭建 Python Sidecar FastAPI 框架 | youtube-rag-system | +| 5-7 | 集成 5层 fallback 转录提取 | youtube-rag-system | +| 7-10 | 封装 FFmpeg Rust 命令 | frameflow | +| 10-14 | 联调测试 + Bug 修复 | - | + +**交付物**: +- `src-tauri/skills/capsummarize/` — 34 个 Skill 模板 +- `apps/desktop/src/lib/vtt-parser.ts` — VTT 解析器 +- `sidecar/` — Python FastAPI Sidecar 服务 +- 视频转录功能可用 + +### Phase 2: 核心功能增强(第 3-6 周) + +**目标**: RAG 问答、场景检测、视频合成 + +| 周次 | 任务 | 来源仓库 | +|------|------|---------| +| 3 | RAG Pipeline 集成(单视频分析) | youtube-rag-system | +| 4 | 场景检测 + 关键帧提取 | frameflow | +| 5 | Remotion 视频渲染集成 | OpenMontage | +| 6 | Skill 管理中心基础版 | skill-studio | + +**交付物**: +- 视频智能问答功能 +- 视频场景自动分割 +- 基础视频合成功能(片段+字幕+BGM) +- Skill 版本管理 UI + +### Phase 3: 高级功能(第 7-10 周) + +**目标**: 双视频对比、质量门控、预算控制 + +| 周次 | 任务 | 来源仓库 | +|------|------|---------| +| 7-8 | 双视频对比分析 | youtube-rag-system | +| 8-9 | 内容质量门控 | OpenMontage | +| 9-10 | API 预算控制 + 成本追踪 | OpenMontage | + +**交付物**: +- 多视频对比视图 +- 内容质量评分 +- API 调用成本仪表盘 + +### Phase 4: 架构优化(第 11-12 周) + +**目标**: 重构和性能优化 + +| 周次 | 任务 | 来源仓库 | +|------|------|---------| +| 11 | Rust 后端模块化重构 | skill-zoo | +| 12 | 前端状态管理优化 | skill-zoo | + +**交付物**: +- 清晰的模块边界 +- 可扩展的 IPC 命令体系 + +--- + +## 五、风险评估与对策 + +| 风险 | 等级 | 对策 | +|------|------|------| +| **AGPL 许可证冲突** (OpenMontage) | 🔴 高 | 仅架构借鉴,不直接引用代码;独立实现关键模块 | +| **Python Sidecar 打包复杂** | 🟡 中 | 使用 `uv` + `PyInstaller` 或 Docker 容器化 | +| **技术栈差异大** (Vue→React, Electron→Tauri) | 🟡 中 | 只移植算法/逻辑,UI 全部重写 | +| **依赖管理膨胀** | 🟡 中 | 按需加载 Python 依赖;Sidecar 独立环境 | +| **API Key 安全** | 🟡 中 | Tauri secure storage + 环境变量注入 | +| **Remotion 商业许可** | 🟡 中 | 开源项目免费;确认使用条款 | +| **项目成熟度** (skill-studio v0.1.0) | 🟢 低 | 仅借鉴已验证的模式,不依赖不稳定 API | +| **Video-Note-Extractor 价值低** | 🟢 低 | 仅参考 Prompt 设计,不引入代码 | + +--- + +## 六、文件结构建议(整合后) + +``` +contentforge/ +├── apps/ +│ └── desktop/ # Next.js + Tauri 桌面应用 +│ ├── src/ +│ │ ├── app/ # Next.js 页面 +│ │ ├── components/ # React 组件 +│ │ ├── lib/ +│ │ │ ├── api-client.ts +│ │ │ ├── vtt-parser.ts # ← from capsummarize +│ │ │ └── sidecar-client.ts # ← Python Sidecar HTTP 客户端 +│ │ ├── store/ # Zustand stores +│ │ └── skills/ # Skill 配置 +│ │ └── capsummarize/ # ← 34 Prompt 模板 +│ └── src-tauri/ +│ ├── src/ +│ │ ├── commands/ # Tauri IPC 命令 +│ │ ├── services/ # Rust 服务层 +│ │ ├── ffmpeg.rs # ← from frameflow +│ │ └── skill_registry.rs # ← from skill-studio +│ └── skills/ +│ └── capsummarize.json +├── sidecar/ # Python Sidecar 服务 +│ ├── main.py # FastAPI 入口 +│ ├── transcript.py # ← from youtube-rag-system +│ ├── pipeline.py # ← RAG Pipeline +│ ├── multi_video_pipeline.py +│ ├── scene_detect.py # ← from frameflow +│ └── requirements.txt +├── render/ # Remotion 渲染引擎 +│ ├── src/ +│ │ ├── compositions/ +│ │ └── templates/ +│ └── package.json +├── docs/ +│ ├── spec/ # SPEC 文档 +│ └── external-analysis/ # 外部仓库分析报告 +│ ├── skill-zoo_ANALYSIS.md +│ ├── skill-studio_ANALYSIS.md +│ ├── capsummarize_ANALYSIS.md +│ ├── frameflow_ANALYSIS.md +│ ├── youtube-rag-system_ANALYSIS.md +│ └── OpenMontage_ANALYSIS.md +└── external-repos/ # 克隆的外部仓库(参考用) + ├── skill-zoo/ + ├── skill-studio/ + ├── capsummarize/ + ├── frameflow/ + ├── youtube-rag-system/ + ├── OpenMontage/ + └── Video-Note-Extractor/ +``` + +--- + +## 七、各仓库详细分析索引 + +每个仓库的完整分析报告位于: + +| 仓库 | 报告路径 | 大小 | +|------|---------|------| +| skill-zoo | `external-repos/skill-zoo_ANALYSIS.md` | 14 KB | +| skill-studio | `external-repos/skill-studio_ANALYSIS.md` | 17 KB | +| Video-Note-Extractor | `external-repos/Video-Note-Extractor_ANALYSIS.md` | 8.7 KB | +| frameflow | `external-repos/frameflow_ANALYSIS.md` | 19 KB | +| capsummarize | `external-repos/capsummarize_ANALYSIS.md` | 19 KB | +| youtube-rag-system | `external-repos/youtube-rag-system_ANALYSIS.md` | 18 KB | +| OpenMontage | `external-repos/OpenMontage_ANALYSIS.md` | 21 KB | + +--- + +*文档版本: v1.0* +*ContentForge 整合规划* diff --git a/contentforge/docs/spec/CLI_SPEC.md b/contentforge/docs/spec/CLI_SPEC.md new file mode 100644 index 0000000..3dd7aa2 --- /dev/null +++ b/contentforge/docs/spec/CLI_SPEC.md @@ -0,0 +1,442 @@ +# ContentForge CLI 模块 SPEC + +> 版本: 0.1.0 +> 模块路径: `cli/` +> 语言: Go 1.24+ +> 框架: Cobra v1.9.1 + +--- + +## 1. 模块定位 + +CLI 是 ContentForge 的命令行入口,提供面向终端用户的交互接口。它不负责核心内容处理逻辑,而是通过 `PythonBridge` 将调用委托给 Python Core Engine 执行。 + +### 1.1 设计原则 + +- **薄壳层**: CLI 仅做参数解析、输入验证、输出格式化 +- **统一出口**: 所有核心功能通过 `PythonBridge` 调用 Python 模块 +- **批量友好**: 支持单 URL、批量文件、stdin 三种输入模式 +- **进度可视**: stderr 输出进度信息,stdout 输出结构化结果 + +--- + +## 2. 命令结构 + +``` +contentforge [global-flags] [command-flags] [args] +``` + +### 2.1 命令总览 + +| 命令 | 用途 | 输入 | 输出 | +|------|------|------|------| +| `scrape` | 从 URL 采集内容 | URL / 批量文件 | JSON | +| `process` | 对已有内容执行 AI 处理 | ContentUnit JSON | JSON | +| `publish` | 导出/发布处理后的内容 | ContentUnit JSON | Markdown/HTML/JSON/小红书 | +| `pipeline` | 管理并执行流水线 | Pipeline ID / URL | JSON | +| `--help`, `-h` | 帮助信息 | - | 文本 | +| `--version`, `-v` | 版本信息 | - | 文本 | + +### 2.2 全局标志 + +| 标志 | 默认值 | 说明 | +|------|--------|------| +| `--config` | `~/.config/contentforge/config.json` | 配置文件路径 | + +--- + +## 3. 子命令详解 + +### 3.1 `scrape` — 内容采集 + +```bash +contentforge scrape [flags] +``` + +**参数:** + +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `url` | string | 是(若无 `--batch`) | 目标 URL | + +**标志:** + +| 标志 | 简写 | 默认值 | 说明 | +|------|------|--------|------| +| `--backend` | - | `"auto"` | 采集后端: `auto` / `jina` / `ytdlp` | +| `--batch` | `-b` | `""` | 批量 URL 文件路径(每行一个 URL) | +| `--output` | `-o` | `""` | 输出目录(为空时输出到 stdout) | +| `--format` | - | `"json"` | 输出格式: `json` | +| `--proxy` | - | `""` | 代理地址 | + +**批量文件格式:** + +```text +# 注释行以 # 开头 +https://twitter.com/user/status/123 +https://youtube.com/watch?v=abc +https://example.com/article +``` + +**执行流程:** + +``` +1. 解析输入 → 单 URL / 批量文件 +2. 初始化 PythonBridge +3. 循环处理每个 URL: + a. 根据 --backend 选择采集器 + b. 调用 Python 模块获取内容 + c. 收集结果(含错误信息) +4. 输出结果 JSON +``` + +**Python 调用映射:** + +| `--backend` | Python 模块 | 类 | 方法 | +|-------------|-------------|-----|------| +| `auto` | `contentforge.ingestion.agent_reach` | `AgentReachIngestor` | `fetch` | +| `jina` | `contentforge.ingestion.web_scraper` | `WebScraper` | `fetch` | +| `ytdlp` | `contentforge.ingestion.transcriber` | `Transcriber` | `transcribe` | + +**错误处理:** + +- 单个 URL 失败不会中断批量处理 +- 失败结果包含 `error` 字段,正常结果包含内容数据 +- stderr 输出每个 URL 的处理状态(✓ / ✗) + +--- + +### 3.2 `process` — AI 处理 + +```bash +contentforge process [flags] +``` + +**参数:** + +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `content-file` | string | 是 | ContentUnit JSON 文件路径,或 `-` 表示 stdin | + +**标志:** + +| 标志 | 默认值 | 说明 | +|------|--------|------| +| `--summarize` | `false` | 生成结构化摘要 | +| `--translate` | `""` | 翻译为目标语言(如 `zh`, `en`, `ja`) | +| `--rewrite` | `""` | 改写风格(如 `professional`, `casual`, `humorous`) | +| `--xiaohongshu` | `false` | 转换为小红书风格文案 | +| `--analyze` | `false` | 内容分析(主题、情感) | +| `--full-analysis` | `false` | 执行完整分析流程(摘要+翻译+分析) | +| `--output` | `""` | 输出文件路径 | +| `--ai-provider` | `"openai"` | AI Provider 名称 | + +**默认行为:** + +若未指定任何处理标志,默认执行 `--summarize`。 + +**处理顺序:** + +1. summarize(若启用) +2. translate(若指定目标语言) +3. rewrite(若指定风格) +4. xiaohongshu(若启用) +5. analyze(若启用) + +**Python 调用映射:** + +| 处理类型 | Python 模块 | 类 | 方法 | +|----------|-------------|-----|------| +| 摘要 | `contentforge.processing.summarizer` | `Summarizer` | `summarize_text` | +| 翻译 | `contentforge.processing.translator` | `Translator` | `translate_text` | +| 改写 | `contentforge.processing.ai_engine` | `AIEngine` | `rewrite` | +| 小红书 | `contentforge.processing.xiaohongshu_converter` | `XiaohongshuConverter` | `convert_text_to_dict` | +| 分析 | `contentforge.processing.analyzer` | `Analyzer` | `analyze_text` | + +--- + +### 3.3 `publish` — 内容发布 + +```bash +contentforge publish [flags] +``` + +**参数:** + +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `content-file` | string | 是 | ContentUnit JSON 文件路径 | + +**标志:** + +| 标志 | 默认值 | 说明 | +|------|--------|------| +| `--format` | `"markdown"` | 输出格式: `markdown` / `text` / `html` / `json` / `xiaohongshu` | +| `--output` | `""` | 输出路径 | +| `--batch` | `false` | 批量模式(输入为目录) | +| `--template` | `""` | 模板文件路径 | +| `--profile` | `""` | 发布 Profile ID | + +**批量模式:** + +- 输入必须是目录 +- 处理目录下所有 `.json` 文件 +- `--output` 必须指定输出目录 +- 每个文件独立渲染,输出为同名的 `.md` / `.html` / `.txt` / `.json` + +**渲染器映射:** + +| 格式 | 渲染函数 | 说明 | +|------|----------|------| +| `markdown` | `renderMarkdown()` | 标准 Markdown,含标题、摘要、要点、正文、来源 | +| `text` | `renderText()` | 纯文本,含标题和摘要 | +| `html` | `renderHTML()` | 简单 HTML 包装(`
` 包裹 Markdown) |
+| `json` | `json.MarshalIndent()` | 原始 JSON 输出 |
+| `xiaohongshu` | `renderXiaohongshu()` | 小红书风格,含标题、正文、标签、互动引导 |
+
+**Markdown 输出结构:**
+
+```markdown
+# {title}
+
+> {description}
+
+## 摘要
+
+{summary}
+
+## 要点
+
+- {key_point_1}
+- {key_point_2}
+
+## 主题
+
+- {topic_1}
+
+## 正文
+
+{extracted_text}
+
+## 翻译
+
+{translated_text}
+
+---
+
+*来源: [platform](url) | 作者: author*
+```
+
+---
+
+### 3.4 `pipeline` — 流水线管理
+
+```bash
+contentforge pipeline  [flags]
+```
+
+**子命令:**
+
+| 子命令 | 用途 | 参数 |
+|--------|------|------|
+| `list` | 列出所有可用流水线 | 无 |
+| `run` | 执行流水线 | `` |
+| `create` | 从 JSON 文件创建自定义流水线 | `` |
+| `status` | 查看流水线运行状态 | `` |
+
+**`pipeline run` 标志:**
+
+| 标志 | 默认值 | 说明 |
+|------|--------|------|
+| `--url` | `""` | 输入 URL |
+| `--input` | `""` | 输入文件(ContentUnit JSON) |
+| `--output` | `""` | 输出目录 |
+
+**执行流程:**
+
+```
+1. 验证输入(--url 或 --input 必须指定一个)
+2. 调用 Python PipelineRunner.run(pipeline_id, input)
+3. 输出执行结果 JSON
+4. 若指定 --output,保存结果到文件
+```
+
+---
+
+## 4. PythonBridge 模块
+
+### 4.1 模块定位
+
+`internal/python_bridge.go` 是 Go 与 Python Core 之间的通信桥梁。通过启动 Python 子进程,以内联脚本方式动态调用 Python 模块。
+
+### 4.2 核心类型
+
+```go
+type PythonBridge struct {
+    venvPath     string        // 虚拟环境路径
+    pythonBinary string        // Python 可执行文件路径
+    timeout      time.Duration // 默认超时: 120s
+    env          []string      // 额外环境变量
+}
+```
+
+### 4.3 构造函数
+
+```go
+func NewPythonBridge() (*PythonBridge, error)
+```
+
+Python 解释器查找顺序:
+1. `${VIRTUAL_ENV}/bin/python3`
+2. `${VIRTUAL_ENV}/bin/python`
+3. 系统 `python3`
+4. 系统 `python`
+
+### 4.4 核心方法
+
+| 方法 | 签名 | 说明 |
+|------|------|------|
+| `Call` | `Call(module, class string, args map[string]interface{}) ([]byte, error)` | 通用调用,返回原始 JSON 字节 |
+| `CallWithOutput` | `CallWithOutput(module, class string, args map[string]interface{}, out interface{}) error` | 调用并自动反序列化到目标结构 |
+| `SetTimeout` | `SetTimeout(d time.Duration)` | 设置超时 |
+| `SetEnv` | `SetEnv(key, value string)` | 设置环境变量 |
+| `HealthCheck` | `HealthCheck() error` | 检测 Python 环境和关键模块 |
+| `GetPythonVersion` | `GetPythonVersion() (string, error)` | 获取 Python 版本 |
+
+### 4.5 调用约定
+
+参数中的特殊字段:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `_method` | string | 实例化后调用的方法名 |
+| `_init_args` | map | 构造器参数(默认空) |
+
+**示例:**
+
+```go
+// 调用 Summarizer.summarize_text(text="...")
+pb.CallWithOutput(
+    "contentforge.processing.summarizer",
+    "Summarizer",
+    map[string]interface{}{
+        "_method": "summarize_text",
+        "text":    "要摘要的内容",
+    },
+    &result,
+)
+```
+
+### 4.6 快捷方法
+
+| 方法 | 说明 |
+|------|------|
+| `CallSummarize(text)` | 调用摘要模块 |
+| `CallXiaohongshu(text)` | 调用小红书转换模块 |
+| `CallIngestion(platform, url)` | 调用采集模块 |
+| `ExecuteMethod(module, method, args)` | 智能推断类名和方法名 |
+
+### 4.7 内联脚本模板
+
+PythonBridge 动态生成并执行以下脚本模板:
+
+```python
+import sys, json, os
+sys.path.insert(0, "")
+
+from  import 
+
+args = json.load(sys.stdin)
+
+method_name = args.pop('_method', None)
+init_args = args.pop('_init_args', {})
+
+instance = (**init_args)
+
+if method_name:
+    method = getattr(instance, method_name)
+    result = method(**args)
+else:
+    result = instance
+
+# 自动序列化
+if hasattr(result, 'to_dict'):
+    output = result.to_dict()
+elif isinstance(result, list) and result and hasattr(result[0], 'to_dict'):
+    output = [r.to_dict() for r in result]
+else:
+    output = result
+
+print(json.dumps(output, ensure_ascii=False, default=str))
+```
+
+---
+
+## 5. 辅助函数
+
+### 5.1 `getStr`
+
+从 `map[string]interface{}` 中安全获取字符串值:
+
+```go
+func getStr(m map[string]interface{}, key string) string
+```
+
+### 5.2 路径解析
+
+| 函数 | 说明 |
+|------|------|
+| `findProjectRoot()` | 向上查找包含 `contentforge/` 的目录 |
+| `contentforgePythonPath()` | 返回 `contentforge/core/python` 绝对路径 |
+| `DefaultVenvPath()` | 返回默认虚拟环境路径(环境变量 → 项目内 `.venv-cf`) |
+
+---
+
+## 6. 错误处理策略
+
+| 场景 | 策略 |
+|------|------|
+| PythonBridge 初始化失败 | 返回错误,CLI 退出码非 0 |
+| 单个 URL 采集失败 | 记录错误,继续处理剩余 URL |
+| AI 处理失败 | 记录错误字段,继续其他处理步骤 |
+| 渲染失败 | 返回错误,不生成输出文件 |
+| 超时 | 杀死子进程,返回 timeout 错误 |
+
+---
+
+## 7. 依赖
+
+```go
+// go.mod
+module github.com/patrick/contentforge
+
+go 1.24
+
+require (
+    github.com/spf13/cobra v1.9.1
+    gopkg.in/yaml.v3 v3.0.1
+)
+```
+
+---
+
+## 8. 扩展指南
+
+### 8.1 添加新命令
+
+1. 在 `cmd/` 下创建新文件(如 `cmd/translate.go`)
+2. 定义 `cobra.Command` 变量和 `runXxx` 函数
+3. 在 `init()` 中注册命令到 `rootCmd`
+4. 通过 `PythonBridge` 调用 Python 核心模块
+
+### 8.2 添加新采集后端
+
+1. 在 `runScrape()` 的 switch 语句中添加新 case
+2. 指定对应的 Python 模块、类和方法
+3. 添加对应的 CLI 标志
+
+### 8.3 添加新发布格式
+
+1. 在 `renderContent()` 的 switch 中添加新 case
+2. 实现对应的 `renderXxx()` 函数
+3. 更新 `--format` 标志的 help 文本
diff --git a/contentforge/docs/spec/FRONTEND_SPEC.md b/contentforge/docs/spec/FRONTEND_SPEC.md
new file mode 100644
index 0000000..70e2ccf
--- /dev/null
+++ b/contentforge/docs/spec/FRONTEND_SPEC.md
@@ -0,0 +1,695 @@
+# ContentForge Frontend 模块 SPEC
+
+> 版本: 0.1.0  
+> 模块路径: `desktop/src/`  
+> 语言: TypeScript 5.x  
+> 框架: Next.js 15 + React 19 + Tailwind CSS 4
+
+---
+
+## 1. 模块定位
+
+Frontend 是 ContentForge Desktop 的 UI 层,基于 Next.js App Router 构建,通过 Tauri IPC 或 HTTP API 与 Rust/Python 后端通信。采用 Zustand 进行状态管理,支持 Chat 对话、Agent 路由、资产管理、下载管理等核心功能。
+
+### 1.1 设计原则
+
+- **同构渲染**: 同一代码库支持 Tauri Desktop 和 Web 部署
+- **API 抽象**: `api-client.ts` 统一封装 IPC/HTTP/WebSocket
+- **Store 分离**: chatStore / agentStore / assetStore / downloadStore 职责清晰
+- **乐观更新**: UI 先行更新,失败时回滚
+- **流式响应**: WebSocket/Tauri Event 驱动实时 UI 更新
+
+---
+
+## 2. 技术栈
+
+| 技术 | 版本 | 用途 |
+|------|------|------|
+| Next.js | 15.x | React 框架、App Router |
+| React | 19.x | UI 库 |
+| TypeScript | 5.x | 类型系统 |
+| Tailwind CSS | 4.x | 原子化 CSS |
+| Zustand | 5.x | 状态管理 |
+| Tauri API | 2.x | 桌面端 IPC |
+| Lucide React | 0.46 | 图标库 |
+
+---
+
+## 3. 项目结构
+
+```
+desktop/src/
+├── app/                        # Next.js App Router 页面
+│   ├── page.tsx                # 首页 / 仪表盘
+│   ├── layout.tsx              # 根布局
+│   ├── globals.css             # 全局样式
+│   ├── settings/               # 设置页面
+│   │   └── page.tsx
+│   ├── download/               # 下载管理页面
+│   │   └── page.tsx
+│   ├── ingestion/              # 内容采集页面
+│   ├── processing/             # 内容处理页面
+│   ├── publishing/             # 发布页面
+│   └── workflows/              # 流水线页面
+│
+├── components/                 # React 组件
+│   ├── layout/                 # 布局组件
+│   │   ├── app-shell.tsx       # 应用外壳
+│   │   ├── app-sidebar.tsx     # 侧边栏导航
+│   │   └── app-header.tsx      # 顶部栏
+│   ├── download/               # 下载相关组件
+│   │   ├── download-form.tsx   # 下载表单
+│   │   └── download-list.tsx   # 下载列表
+│   ├── forms/                  # 通用表单组件
+│   └── ui/                     # 基础 UI 组件
+│
+├── store/                      # Zustand Stores
+│   ├── chatStore.ts            # 聊天状态管理
+│   ├── agentStore.ts           # Agent 状态管理
+│   ├── assetStore.ts           # 资产状态管理
+│   └── downloadStore.ts        # 下载状态管理
+│
+├── lib/                        # 工具库
+│   ├── api-client.ts           # API 抽象层(IPC/HTTP/WebSocket)
+│   ├── ws-client.ts            # WebSocket 客户端
+│   ├── navigation.ts           # 导航配置
+│   └── utils.ts                # 工具函数
+│
+├── types/                      # TypeScript 类型定义
+│   ├── chat.ts                 # 聊天相关类型
+│   ├── agent.ts                # Agent 相关类型
+│   ├── asset.ts                # 资产相关类型
+│   └── download.ts             # 下载相关类型
+│
+└── i18n/                       # 国际化(预留)
+```
+
+---
+
+## 4. API 抽象层(api-client.ts)
+
+### 4.1 环境检测
+
+```typescript
+function isTauri(): boolean {
+  return typeof window !== "undefined" && !!(window as any).__TAURI__;
+}
+```
+
+### 4.2 核心 API
+
+```typescript
+// 统一调用(Desktop: Tauri IPC / Web: HTTP POST)
+export async function apiInvoke(command: string, args?: unknown): Promise
+
+// 统一事件监听(Desktop: Tauri Event / Web: WebSocket)
+export function apiListen(event: string, handler: (payload: unknown) => void): () => void
+
+// 错误类
+export class ApiError extends Error {
+  statusCode: number;
+  data: unknown;
+}
+```
+
+### 4.3 HTTP 模式
+
+```typescript
+async function httpRequest(command: string, args?: unknown): Promise
+```
+
+- 基础 URL: `http://localhost:3000/api/{command}`
+- 超时: 30s
+- 重试: 2 次,指数退避
+- 请求方法: POST
+- Content-Type: `application/json`
+
+### 4.4 WebSocket 客户端
+
+```typescript
+class WebSocketClient {
+  constructor(url: string = "ws://localhost:3000/ws")
+  
+  connect(): void
+  disconnect(): void
+  subscribe(event: string, handler: (payload) => void): () => void
+  send(type: string, payload: unknown): void
+  
+  get isConnected(): boolean
+}
+```
+
+特性:
+- 自动重连(最多 5 次,指数退避)
+- 事件订阅/取消订阅
+- 连接状态广播 (`ws.connected` / `ws.error`)
+
+### 4.5 Store 注入
+
+```typescript
+export function initApiClients(): void {
+  setChatApiClient(apiInvoke, apiListen);
+  setAgentApiClient(apiInvoke);
+  setAssetApiClient(apiInvoke);
+  setDownloadApiClient(apiInvoke, apiListen);
+}
+```
+
+---
+
+## 5. Chat Store(chatStore.ts)
+
+### 5.1 状态定义
+
+```typescript
+interface ChatState {
+  sessions: ChatSession[];                    // 会话列表
+  currentSessionId: string | null;            // 当前会话 ID
+  messagesBySession: Map;  // 消息缓存
+  toolCallsByMessage: Map;    // 工具调用缓存
+  isSending: boolean;                         // 发送中
+  isStreaming: boolean;                       // 流式响应中
+  streamingMessageId: string | null;          // 当前流式消息 ID
+  error: string | null;                       // 错误信息
+  wsConnected: boolean;                       // WebSocket 连接状态
+  isLoadingSessions: boolean;                 // 加载会话中
+  isLoadingHistory: boolean;                  // 加载历史中
+  hasMoreHistory: boolean;                    // 是否还有更多历史
+}
+```
+
+### 5.2 会话管理 Actions
+
+| Action | 说明 | 乐观更新 |
+|--------|------|----------|
+| `loadSessions()` | 加载会话列表 | 否 |
+| `createSession(agentId, title?)` | 创建新会话 | 是(失败回滚) |
+| `switchSession(sessionId)` | 切换当前会话 | 是 |
+| `archiveSession(sessionId)` | 归档会话 | 是(失败恢复) |
+| `pinSession(sessionId)` | 置顶/取消置顶 | 是 |
+| `updateSessionTitle(sessionId, title)` | 更新标题 | 是 |
+| `deleteSession(sessionId)` | 删除会话 | 是(失败回滚) |
+
+### 5.3 消息操作 Actions
+
+| Action | 说明 | 乐观更新 |
+|--------|------|----------|
+| `sendMessage(text, options?)` | 发送消息 | 是(先添加用户消息 + 占位助手消息) |
+| `cancelStream()` | 取消流式响应 | 是 |
+| `retryMessage(messageId)` | 重试失败消息 | 是 |
+| `deleteMessage(messageId)` | 删除消息 | 是 |
+
+### 5.4 流式响应处理
+
+```typescript
+handleStreamChunk(chunk: StreamChunk): void
+handleStreamDone(messageId: string): void
+handleStreamError(messageId: string, error: string): void
+```
+
+**StreamChunk 类型:**
+
+```typescript
+type StreamChunk =
+  | { type: "text"; messageId: string; text: string }
+  | { type: "tool_call"; messageId: string; toolCall: ToolCall }
+  | { type: "tool_result"; messageId: string; toolResult: ToolResult }
+  | { type: "error"; messageId: string; error?: string }
+  | { type: "done"; messageId: string };
+```
+
+### 5.5 工具调用处理
+
+```typescript
+handleToolCallStart(payload: ToolCallStartPayload): void
+handleToolCallProgress(payload: ToolCallProgressPayload): void
+handleToolCallCompleted(payload: ToolCallCompletedPayload): void
+handleToolCallFailed(messageId, callId, error): void
+```
+
+**工具调用状态机:**
+
+```
+pending → running → completed
+                ↘→ failed
+```
+
+### 5.6 Selector Hooks
+
+```typescript
+export function useCurrentMessages(): ChatMessage[]
+export function useCurrentSession(): ChatSession | null
+export function useStreamingMessage(): ChatMessage | null
+export function useCurrentToolCalls(): ToolCall[]
+```
+
+---
+
+## 6. Agent Store(agentStore.ts)
+
+### 6.1 状态定义
+
+```typescript
+interface AgentStoreState extends AgentState {
+  agents: AgentRole[];                        // Agent 注册表
+  switchHistory: AgentSwitchRecord[];         // 切换历史
+  quickActions: AgentQuickAction[];           // 快捷操作
+  skills: SkillDefinition[];                  // Skill 注册表
+  isLoadingAgents: boolean;
+  isLoadingSkills: boolean;
+  error: string | null;
+  routeCache: Map;            // 意图 → Agent ID 缓存
+}
+```
+
+### 6.2 内置 Agent 配置
+
+| ID | 名称 | 能力 | 模型 | 自动切换 |
+|----|------|------|------|----------|
+| `general` | 通用助手 | general, search | gpt-4o-mini | 否 |
+| `content_analyst` | 内容分析师 | analyze, search | gpt-4o | 是 |
+| `summarizer` | 摘要专家 | summarize, search | gpt-4o-mini | 是 |
+| `rewriter` | 改写专家 | rewrite, translate, search | gpt-4o | 是 |
+| `publisher` | 发布助手 | publish, search | gpt-4o-mini | 是 |
+| `pipeline_runner` | 流水线执行器 | pipeline, search | gpt-4o-mini | 是 |
+
+### 6.3 Agent 配置字段
+
+```typescript
+interface AgentRole {
+  id: string;
+  name: string;
+  description: string;
+  systemPrompt: string;
+  capabilities: AgentCapability[];
+  tools: string[];                    // 可用工具列表
+  model: string;
+  temperature: number;
+  maxTokens: number;
+  contextWindow: number;
+  icon: string;                       // Lucide 图标名
+  color: string;                      // 主题色
+  autoSwitch: boolean;                // 是否支持自动切换
+  streaming: boolean;                 // 是否支持流式
+  requiresContext: boolean;           // 是否需要资产上下文
+  order: number;                      // 排序权重
+}
+```
+
+### 6.4 意图路由
+
+```typescript
+routeByIntent(message: string, selectedAssetIds?: string[]): string
+```
+
+路由逻辑:
+1. **缓存检查** — 命中直接返回
+2. **显式提及检查** — 如 "分析师" → content_analyst
+3. **Capability 得分计算** — 正则匹配所有意图模式
+4. **选择最高分 Agent** — 找到支持该 capability 的 Agent
+5. **回退** — 无明确意图时返回当前 Agent
+
+**意图模式示例:**
+
+| Capability | 匹配模式 |
+|-----------|----------|
+| analyze | `/分析.*内容/i`, `/提取.*要点/i`, `/analyze/i` |
+| summarize | `/总结/i`, `/摘要/i`, `/summarize/i`, `/tl;dr/i` |
+| rewrite | `/改写/i`, `/重写/i`, `/润色/i`, `/rewrite/i` |
+| translate | `/翻译/i`, `/translate/i`, `/转成.*文/i` |
+| publish | `/发布/i`, `/导出/i`, `/小红书/i`, `/publish/i` |
+| pipeline | `/运行.*流水线/i`, `/pipeline/i`, `/batch.*process/i` |
+| search | `/搜索/i`, `/查找/i`, `/search/i`, `/find/i` |
+
+### 6.5 Agent Actions
+
+| Action | 说明 |
+|--------|------|
+| `loadAgents()` | 加载 Agent 列表(合并默认 + 远程) |
+| `registerAgent(agent)` | 注册新 Agent |
+| `unregisterAgent(agentId)` | 注销 Agent |
+| `setCurrentAgentId(agentId, reason?)` | 设置当前 Agent |
+| `switchAgent(agentId, triggeredBy, reason?)` | 切换 Agent(通知后端) |
+| `routeByIntent(message)` | 基于意图路由 |
+| `loadQuickActions()` | 加载快捷操作 |
+| `executeQuickAction(actionId, params?)` | 执行快捷操作 |
+| `loadSkills()` | 加载 Skill 列表 |
+| `registerSkill(skill)` | 注册 Skill |
+| `executeSkill(skillId, params)` | 执行 Skill |
+
+### 6.6 Selector Hooks
+
+```typescript
+export function useCurrentAgent(): AgentRole | undefined
+export function useSortedAgents(): AgentRole[]
+export function useCurrentAgentQuickActions(): AgentQuickAction[]
+export function useCurrentAgentSkills(): SkillDefinition[]
+```
+
+---
+
+## 7. 类型定义
+
+### 7.1 Chat 类型(types/chat.ts)
+
+```typescript
+interface ChatSession {
+  id: string;
+  title: string;
+  agentId: string;
+  status: "active" | "archived" | "pinned";
+  linkedTaskId?: string;
+  linkedAssetIds: string[];
+  metadata: Record;
+  createdAt: string;    // ISO 8601
+  updatedAt: string;
+}
+
+interface ChatMessage {
+  id: string;
+  sessionId: string;
+  role: "user" | "assistant" | "system" | "tool";
+  content: string;
+  status: "pending" | "streaming" | "completed" | "failed" | "cancelled";
+  selectedAssetIds?: string[];
+  toolCalls?: ToolCall[];
+  toolResults?: ToolResult[];
+  model?: string;
+  tokensUsed?: Record;
+  error?: string;
+  delta?: string;       // 当前增量文本
+  createdAt: string;
+  updatedAt: string;
+}
+
+interface ToolCall {
+  id: string;
+  name: string;
+  args: Record;
+  status: "pending" | "running" | "completed" | "failed";
+  result?: unknown;
+  error?: string;
+  startedAt?: string;
+  completedAt?: string;
+  durationMs?: number;
+}
+
+interface ToolResult {
+  callId: string;
+  name: string;
+  output: unknown;
+  error?: string;
+  durationMs?: number;
+}
+
+interface SendMessageOptions {
+  agentId?: string;
+  selectedAssetIds?: string[];
+  attachments?: Attachment[];
+  streaming?: boolean;
+}
+```
+
+### 7.2 Agent 类型(types/agent.ts)
+
+```typescript
+type AgentCapability = 
+  | "general" 
+  | "analyze" 
+  | "summarize" 
+  | "rewrite" 
+  | "translate" 
+  | "publish" 
+  | "pipeline" 
+  | "search";
+
+interface AgentState {
+  currentAgentId: string;
+  previousAgentId?: string;
+  switchReason?: string;
+  isSwitching: boolean;
+}
+
+interface AgentSwitchRecord {
+  id: string;
+  sessionId: string;
+  fromAgentId: string;
+  toAgentId: string;
+  reason?: string;
+  triggeredBy: "user" | "auto" | "tool";
+  timestamp: string;
+}
+
+interface AgentQuickAction {
+  id: string;
+  agentId: string;
+  label: string;
+  description: string;
+  promptTemplate: string;
+  icon: string;
+}
+
+interface SkillDefinition {
+  id: string;
+  name: string;
+  description: string;
+  agentId?: string;
+  triggers: string[];
+  parameters: SkillParameter[];
+}
+
+interface SkillExecutionResult {
+  skillId: string;
+  status: "success" | "failed";
+  output: unknown;
+  error?: string;
+  durationMs: number;
+}
+```
+
+### 7.3 Asset 类型(types/asset.ts)
+
+```typescript
+interface ContentAsset {
+  id: string;
+  title: string;
+  assetType: "video" | "article" | "tweet" | "thread" | "audio" | "image" | "note";
+  status: "ingested" | "processing" | "processed" | "editing" | "ready" | "published" | "failed";
+  platform?: string;
+  url?: string;
+  filePath?: string;
+  thumbnailUrl?: string;
+  description?: string;
+  extractedText?: string;
+  summary?: string;
+  transcript?: string;
+  translatedText?: string;
+  rewrittenText?: string;
+  durationSec?: number;
+  analysis?: Record;
+  tags: string[];
+  pipelineId?: string;
+  author?: string;
+  publishedAt?: string;
+  engagement?: Record;
+  createdAt: string;
+  updatedAt: string;
+}
+```
+
+### 7.4 Download 类型(types/download.ts)
+
+```typescript
+interface DownloadRecord {
+  id: string;
+  url: string;
+  title?: string;
+  status: "pending" | "downloading" | "completed" | "failed" | "cancelled";
+  progress: number;           // 0.0 - 100.0
+  speed?: string;
+  eta?: string;
+  outputDir?: string;
+  filename?: string;
+  subtitles: string[];
+  error?: string;
+  queuePosition: number;
+  options?: DownloadOptions;
+  createdAt: string;
+  updatedAt: string;
+}
+
+interface DownloadOptions {
+  url: string;
+  isPlaylist?: boolean;
+  quality?: string;
+  format?: string;
+  outputDir?: string;
+  subLangs?: string[];
+  writeSubs?: boolean;
+  writeAutoSubs?: boolean;
+  startTime?: string;
+  endTime?: string;
+}
+```
+
+---
+
+## 8. 组件体系
+
+### 8.1 布局组件
+
+| 组件 | 路径 | 职责 |
+|------|------|------|
+| `AppShell` | `components/layout/app-shell.tsx` | 应用外壳(侧边栏 + 主内容区) |
+| `AppSidebar` | `components/layout/app-sidebar.tsx` | 侧边栏导航 |
+| `AppHeader` | `components/layout/app-header.tsx` | 顶部工具栏 |
+
+### 8.2 页面路由
+
+| 路径 | 页面 | 功能 |
+|------|------|------|
+| `/` | 首页 | 仪表盘、快捷入口 |
+| `/settings` | 设置 | AI Provider、代理、下载路径 |
+| `/download` | 下载管理 | 下载列表、队列、进度 |
+| `/ingestion` | 内容采集 | URL 输入、批量导入 |
+| `/processing` | 内容处理 | AI 处理操作面板 |
+| `/workflows` | 流水线 | 预设执行、自定义流水线 |
+
+### 8.3 Chat 界面组件(规划)
+
+| 组件 | 职责 |
+|------|------|
+| `ChatContainer` | 聊天窗口容器 |
+| `MessageList` | 消息列表(支持虚拟滚动) |
+| `MessageBubble` | 单条消息气泡 |
+| `StreamingText` | 流式文本渲染 |
+| `ToolCallCard` | 工具调用状态卡片 |
+| `AgentSelector` | Agent 切换下拉框 |
+| `AssetSelector` | 内容资产选择器 |
+| `ChatInput` | 消息输入框 |
+
+---
+
+## 9. Store 初始化
+
+### 9.1 客户端初始化流程
+
+```typescript
+// 应用启动时(layout.tsx 或 page.tsx)
+import { initApiClients } from "@/lib/api-client";
+
+if (typeof window !== "undefined") {
+  initApiClients();  // 注入 apiInvoke / apiListen 到所有 Store
+}
+```
+
+### 9.2 Store 组合使用
+
+```typescript
+// 发送消息时自动关联当前 Agent 和选中资产
+const sendMessage = async (text: string) => {
+  const currentAgentId = useAgentStore.getState().currentAgentId;
+  const selectedAssetIds = useAssetStore.getState().selection.selectedIds;
+  
+  await useChatStore.getState().sendMessage(text, {
+    agentId: currentAgentId,
+    selectedAssetIds,
+  });
+};
+```
+
+---
+
+## 10. 样式体系
+
+### 10.1 Tailwind 配置
+
+```typescript
+// tailwind.config.ts
+export default {
+  content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
+  theme: {
+    extend: {
+      colors: {
+        // Agent 主题色
+        "agent-general": "#6366f1",
+        "agent-analyst": "#0ea5e9",
+        "agent-summarizer": "#8b5cf6",
+        "agent-rewriter": "#ec4899",
+        "agent-publisher": "#10b981",
+        "agent-pipeline": "#f59e0b",
+      },
+    },
+  },
+};
+```
+
+### 10.2 全局样式
+
+```css
+/* globals.css */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+```
+
+---
+
+## 11. 扩展指南
+
+### 11.1 添加新页面
+
+1. 在 `app/` 下创建新目录和 `page.tsx`
+2. 在 `lib/navigation.ts` 添加导航项
+3. 在 `components/layout/app-sidebar.tsx` 注册导航
+
+### 11.2 添加新 Store
+
+1. 创建 `store/myStore.ts`
+2. 使用 `zustand` + `immer` + `devtools` 组合
+3. 在 `lib/api-client.ts` 中注入 API 客户端
+
+### 11.3 添加新类型
+
+1. 在 `types/` 下创建或扩展类型文件
+2. 确保与 Rust 后端和 Python Core 的序列化兼容
+
+---
+
+## 12. 依赖清单
+
+```json
+{
+  "dependencies": {
+    "next": "^15.0.0",
+    "react": "^19.0.0",
+    "react-dom": "^19.0.0",
+    "lucide-react": "^0.460.0",
+    "zustand": "^5.0.0",
+    "@tauri-apps/api": "^2.0.0",
+    "@tauri-apps/plugin-shell": "^2.0.0"
+  },
+  "devDependencies": {
+    "typescript": "^5.0.0",
+    "@types/node": "^20.0.0",
+    "@types/react": "^19.0.0",
+    "@types/react-dom": "^19.0.0",
+    "tailwindcss": "^4.0.0",
+    "postcss": "^8.4.0",
+    "autoprefixer": "^10.4.0",
+    "eslint": "^8.0.0",
+    "eslint-config-next": "^15.0.0"
+  }
+}
+```
+
+---
+
+## 13. 相关 SPEC 文档
+
+| 文档 | 内容 |
+|------|------|
+| [PROJECT_OVERVIEW.md](PROJECT_OVERVIEW.md) | 项目整体架构 |
+| [CLI_SPEC.md](CLI_SPEC.md) | Go CLI 命令与桥接 |
+| [PYTHON_CORE_SPEC.md](PYTHON_CORE_SPEC.md) | Python 核心引擎 |
+| [RUST_BACKEND_SPEC.md](RUST_BACKEND_SPEC.md) | Tauri Rust 后端 |
diff --git a/contentforge/docs/spec/MULTI_AGENT_ARCHITECTURE_PLAN.md b/contentforge/docs/spec/MULTI_AGENT_ARCHITECTURE_PLAN.md
new file mode 100644
index 0000000..23c2a1b
--- /dev/null
+++ b/contentforge/docs/spec/MULTI_AGENT_ARCHITECTURE_PLAN.md
@@ -0,0 +1,673 @@
+# ContentForge 多智能体架构规划
+
+> 版本: 1.0  
+> 日期: 2026-07-24  
+> 状态: 规划草案  
+
+---
+
+## 一、现状分析
+
+### 1.1 当前架构(三层 Agent 并存)
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│  Frontend (Next.js + Zustand)                                   │
+│  ├─ agentStore.ts    → 硬编码 6 个 Agent + 意图路由(前端层)    │
+│  ├─ chatStore.ts     → 会话/消息/流式状态管理                    │
+│  └─ 当前 Agent 聊天 → 通过 Rust → 仅支持 Kimi CLI               │
+├─────────────────────────────────────────────────────────────────┤
+│  Rust Backend (Tauri)                                           │
+│  ├─ commands/agent.rs → 硬编码 AgentRoleOut(与前端重复定义)    │
+│  ├─ agent_cli.rs      → Kimi CLI 二进制检测                      │
+│  ├─ agent_runner.rs   → spawn Kimi 子进程 + stream-json 解析   │
+│  └─ commands/ai.rs    → agent_chat_send(仅支持 agent_id="kimi")│
+├─────────────────────────────────────────────────────────────────┤
+│  Python Core                                                    │
+│  ├─ ai/agent.py       → 轻量 AgentRegistry + 意图路由           │
+│  ├─ ai/agent_registry.py → SQLite 持久化 + SkillRegistry        │
+│  ├─ ai/agent_router.py → LLM 路由 + 协作编排                    │
+│  └─ ai/agent_session.py → ReAct 循环执行                        │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### 1.2 核心问题
+
+| # | 问题 | 影响 | 层级 |
+|---|------|------|------|
+| 1 | **Agent 定义重复** — 前端/ Rust / Python 各有一套硬编码 Agent | 维护困难,三方不一致 | 全栈 |
+| 2 | **Agent 聊天仅限 Kimi CLI** — `agent_chat_send` 硬编码 `agent_id != "kimi"` 拒绝 | 无法接入其他 AI | Rust |
+| 3 | **Rust 与 Python Agent 系统割裂** — Python 的 AgentRouter/Registry 未被 Rust 调用 | 功能冗余,能力浪费 | 跨层 |
+| 4 | **前端意图路由与后端重复** — 前端 `agentStore.routeByIntent()` 和 Python `AgentRouter.route()` 逻辑重复 | 行为不一致 | 前后端 |
+| 5 | **无统一 Agent Provider 抽象** — 无法动态接入新 Agent 平台 | 扩展性差 | 架构 |
+| 6 | **Skill 系统未贯通** — Python SkillRegistry 已存在,但 Rust/前端均未调用 | 功能悬空 | 跨层 |
+| 7 | **Agent 间无法协作** — 各 Agent 独立运行,无上下文共享 | 无法处理复杂任务 | 功能 |
+
+---
+
+## 二、目标架构:统一多智能体平台
+
+### 2.1 架构愿景
+
+```
+                         ┌──────────────┐
+                         │   用户界面    │
+                         └──────┬───────┘
+                                │
+                    ┌───────────┴───────────┐
+                    │    Agent Gateway      │  ← 统一入口
+                    │  (Rust / Tauri)       │
+                    └───────────┬───────────┘
+                                │
+        ┌───────────────────────┼───────────────────────┐
+        │                       │                       │
+   ┌────┴────┐            ┌────┴────┐            ┌────┴────┐
+   │ 内置Agent│            │ 外部Agent│            │ 插件Agent│
+   │ Provider│            │ Provider│            │ Provider│
+   └────┬────┘            └────┬────┘            └────┬────┘
+        │                       │                       │
+   ┌────┴───────────────────────┴───────────────────────┴────┐
+   │              Agent Orchestrator (Python)               │
+   │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │
+   │  │   Router    │  │  Registry   │  │  Session    │   │
+   │  │  意图路由    │  │  注册发现    │  │  执行会话    │   │
+   │  └─────────────┘  └─────────────┘  └─────────────┘   │
+   │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │
+   │  │   Planner   │  │   Memory    │  │  Skill Exec │   │
+   │  │  协作规划    │  │  共享记忆    │  │  技能执行    │   │
+   │  └─────────────┘  └─────────────┘  └─────────────┘   │
+   └────────────────────────────────────────────────────────┘
+                                │
+                    ┌───────────┴───────────┐
+                    │    LLM Providers      │
+                    │  Kimi / OpenAI / etc  │
+                    └───────────────────────┘
+```
+
+### 2.2 设计原则
+
+1. **Single Source of Truth** — Agent 定义、状态、记忆以 Python 层为唯一权威来源
+2. **Provider 模式** — Agent 后端可插拔(Kimi CLI、OpenAI API、Claude、Ollama、本地模型等)
+3. **Rust 为 Gateway** — Tauri 层作为统一的 IPC Gateway,负责认证、限流、事件转发
+4. **前端只关心展示** — 前端不执行路由决策,仅展示后端返回的 Agent/消息/事件
+5. **渐进式迁移** — 现有功能不中断,新能力逐步叠加
+
+---
+
+## 三、核心模块设计
+
+### 3.1 Agent Provider 抽象层
+
+统一接口,支持任意 Agent 后端:
+
+```rust
+// src/agent_providers/mod.rs
+pub trait AgentProvider: Send + Sync {
+    /// Provider 唯一标识
+    fn id(&self) -> &str;
+    
+    /// Provider 显示名称
+    fn name(&self) -> &str;
+    
+    /// 检测是否可用
+    fn is_available(&self) -> bool;
+    
+    /// 列出该 Provider 支持的所有 Agent
+    fn list_agents(&self) -> Vec;
+    
+    /// 发送消息并接收流式响应
+    fn chat(
+        &self,
+        agent_id: &str,
+        session_id: &str,
+        messages: Vec,
+        context: ChatContext,
+    ) -> Result, AgentError>;
+    
+    /// 中断当前会话
+    fn cancel(&self, session_id: &str) -> Result<(), AgentError>;
+}
+
+/// 支持的 Provider 类型
+pub enum ProviderType {
+    KimiCli,        // Kimi CLI 子进程
+    OpenAiApi,      // OpenAI 兼容 API
+    ClaudeApi,      // Anthropic Claude API
+    Ollama,         // Ollama 本地模型
+    PythonBridge,   // Python AgentSession (ReAct)
+    Custom,         // 用户自定义 Provider
+}
+```
+
+#### 3.1.1 内置 Provider 实现
+
+| Provider | 实现位置 | 说明 |
+|----------|----------|------|
+| `KimiCliProvider` | `src/agent_providers/kimi_cli.rs` | 封装现有 `agent_cli.rs` + `agent_runner.rs` |
+| `PythonBridgeProvider` | `src/agent_providers/python_bridge.rs` | 调用 Python `AgentSession` 执行 ReAct |
+| `OpenAiProvider` | `src/agent_providers/openai.rs` | HTTP API 调用,支持流式 SSE |
+| `OllamaProvider` | `src/agent_providers/ollama.rs` | 本地 Ollama 服务 |
+
+#### 3.1.2 Provider 注册与发现
+
+```rust
+// src/agent_providers/registry.rs
+pub struct ProviderRegistry {
+    providers: HashMap>,
+}
+
+impl ProviderRegistry {
+    pub fn discover() -> Self {
+        let mut registry = Self::default();
+        // 自动检测可用 Provider
+        registry.try_register(KimiCliProvider::new());
+        registry.try_register(PythonBridgeProvider::new());
+        registry.try_register(OpenAiProvider::from_env());
+        registry.try_register(OllamaProvider::from_env());
+        registry
+    }
+}
+```
+
+---
+
+### 3.2 Agent Gateway(Rust 层重构)
+
+替换现有的硬编码 Agent 系统:
+
+#### 3.2.1 新 Commands 接口
+
+```rust
+// commands/agent.rs(重构后)
+
+#[tauri::command]
+pub async fn get_agents(
+    provider_registry: State<'_, ProviderRegistry>,
+) -> Result>, String> {
+    // 从所有 Provider 聚合 Agent 列表
+    let mut agents = vec![];
+    for provider in provider_registry.list_available() {
+        agents.extend(provider.list_agents());
+    }
+    Ok(ApiResponse::ok(agents))
+}
+
+#[tauri::command]
+pub async fn chat_send(
+    app: AppHandle,
+    provider_registry: State<'_, ProviderRegistry>,
+    session_manager: State<'_, SessionManager>,
+    request: ChatSendRequest,
+) -> Result, String> {
+    // 1. 通过 Python Bridge 进行意图路由
+    let route = python_bridge::route_intent(&request.message).await?;
+    
+    // 2. 获取目标 Agent 的 Provider
+    let provider = provider_registry
+        .get(&route.provider_id)
+        .ok_or("Provider not found")?;
+    
+    // 3. 启动流式会话
+    let rx = provider.chat(
+        &route.agent_id,
+        &request.session_id,
+        build_message_history(&request.session_id).await?,
+        build_context(&request.selected_asset_ids).await?,
+    )?;
+    
+    // 4. 转发流式事件到前端
+    tokio::spawn(forward_stream_events(app, request.session_id, rx));
+    
+    Ok(ApiResponse::ok(ChatSendResponse { /* ... */ }))
+}
+
+#[tauri::command]
+pub async fn get_providers(
+    registry: State<'_, ProviderRegistry>,
+) -> Result>, String> {
+    Ok(ApiResponse::ok(registry.list_all()))
+}
+```
+
+#### 3.2.2 Session Manager
+
+```rust
+// src/session_manager.rs
+pub struct SessionManager {
+    /// 活跃会话
+    active_sessions: Arc>>,
+    /// 会话历史持久化
+    db: Database,
+}
+
+pub struct SessionHandle {
+    pub session_id: String,
+    pub provider_id: String,
+    pub agent_id: String,
+    pub cancel_tx: mpsc::Sender<()>,
+    pub event_rx: mpsc::Receiver,
+}
+
+impl SessionManager {
+    /// 创建新会话
+    pub async fn create_session(
+        &self,
+        agent_id: &str,
+        provider_id: &str,
+    ) -> Result;
+    
+    /// 发送消息到会话
+    pub async fn send_message(
+        &self,
+        session_id: &str,
+        content: &str,
+        asset_context: Vec,
+    ) -> Result, SessionError>;
+    
+    /// 取消会话
+    pub async fn cancel_session(&self, session_id: &str) -> Result<(), SessionError>;
+    
+    /// 获取会话历史
+    pub async fn get_history(&self, session_id: &str) -> Result, SessionError>;
+}
+```
+
+---
+
+### 3.3 Python Orchestrator 增强
+
+现有 `ai/agent_router.py` 和 `ai/agent_registry.py` 已具备良好基础,需增强:
+
+#### 3.3.1 Agent 能力注册表扩展
+
+```python
+# contentforge/ai/agent_registry.py(增强)
+
+class AgentRegistry:
+    """Agent 注册中心 — 统一权威来源"""
+    
+    def register_provider_agent(
+        self,
+        provider_id: str,        # "kimi", "openai", "python"
+        agent_id: str,           # "general", "content_analyst"
+        config: AgentDefinition,
+    ) -> None:
+        """注册来自外部 Provider 的 Agent"""
+        
+    def get_agent_with_provider(self, agent_id: str) -> Optional[Tuple[str, AgentDefinition]]:
+        """返回 (provider_id, agent_definition)"""
+        
+    def list_by_provider(self, provider_id: str) -> List[AgentDefinition]:
+        """按 Provider 列出 Agent"""
+```
+
+#### 3.3.2 跨 Agent 协作执行器
+
+```python
+# contentforge/ai/collaboration.py(新增)
+
+class CollaborationExecutor:
+    """多 Agent 协作执行器"""
+    
+    def execute_plan(
+        self,
+        plan: CollaborationPlan,
+        shared_context: SharedContext,
+        event_callback: Callable[[CollaborationEvent], None],
+    ) -> CollaborationResult:
+        """
+        执行协作计划,支持:
+        - 顺序执行(depends_on 链)
+        - 并行执行(无依赖的步骤)
+        - 上下文注入(前序步骤输出注入后续步骤)
+        - 错误回退(某步骤失败时的策略)
+        """
+        
+class SharedContext:
+    """跨 Agent 共享上下文"""
+    
+    def __init__(self):
+        self.variables: Dict[str, Any] = {}      # 变量表
+        self.memories: List[MemoryEntry] = []     # 记忆片段
+        self.assets: List[AssetContext] = []      # 关联资产
+        self.session_history: List[Message] = []  # 对话历史
+```
+
+#### 3.3.3 意图路由增强
+
+```python
+# contentforge/ai/agent_router.py(增强)
+
+class AgentRouter:
+    def __init__(self, ...):
+        # 现有能力...
+        self.provider_registry: ProviderRegistry  # 新增
+        
+    def route(self, user_message: str, context: Dict) -> RouteResult:
+        """
+        增强路由逻辑:
+        1. 快速模式匹配(关键词/正则)
+        2. 显式 Agent 提及(@AgentName)
+        3. **Provider 偏好匹配**(根据用户配置的默认 Provider)
+        4. LLM 推理路由(复杂意图)
+        5. **多 Agent 协作检测**(任务是否需要多个 Agent)
+        """
+```
+
+---
+
+### 3.4 前端适配
+
+#### 3.4.1 AgentStore 重构
+
+```typescript
+// store/agentStore.ts(重构后)
+
+interface AgentStoreState {
+  // 不再硬编码 Agent,从后端动态加载
+  agents: AgentInfo[];
+  providers: ProviderInfo[];
+  
+  // 当前选中的 Agent 和 Provider
+  currentAgentId: string | null;
+  currentProviderId: string | null;
+  
+  // Provider 可用性状态
+  providerStatus: Map;
+  
+  // ... 其他状态
+}
+
+interface AgentStoreActions {
+  // 从后端加载所有 Agent(跨 Provider 聚合)
+  loadAgents: () => Promise;
+  
+  // 加载 Provider 列表
+  loadProviders: () => Promise;
+  
+  // 检测 Provider 可用性
+  checkProviderStatus: (providerId: string) => Promise;
+  
+  // 切换 Agent(自动选择 Provider)
+  switchAgent: (agentId: string, reason?: string) => Promise;
+  
+  // 意图路由(调用后端,不再前端本地路由)
+  routeByIntent: (message: string) => Promise;
+  
+  // ... 其他 Actions
+}
+```
+
+#### 3.4.2 Provider 配置 UI
+
+新增 Settings 页面选项:
+
+```typescript
+// app/settings/agent-providers/page.tsx(新增)
+
+interface ProviderConfig {
+  id: string;
+  name: string;
+  type: 'kimi_cli' | 'openai_api' | 'claude_api' | 'ollama' | 'custom';
+  enabled: boolean;
+  config: Record;  // API key, base URL, model, etc.
+  priority: number;  // 优先级(影响自动选择)
+}
+```
+
+---
+
+## 四、数据流重构
+
+### 4.1 当前数据流(问题)
+
+```
+用户输入 → 前端 routeByIntent() → 前端判断 Agent → chat_send() → Rust 硬编码 Kimi
+                                         ↓
+                                    Python AgentRouter(未被调用)
+```
+
+### 4.2 目标数据流
+
+```
+用户输入 → chat_send() → Rust Agent Gateway 
+                              ↓
+                    Python AgentRouter.route()
+                              ↓
+              ┌───────────────┼───────────────┐
+              ↓               ↓               ↓
+         单 Agent        多 Agent 协作      直接 Skill
+              ↓               ↓               ↓
+         Provider.chat()  Collaboration    SkillExecutor
+              ↓               ↓               ↓
+         流式事件 ←────── 流式事件 ─────→ 流式事件
+              ↓               ↓               ↓
+         前端展示 ←────── 前端展示 ─────→ 前端展示
+```
+
+---
+
+## 五、实施路线图
+
+### Phase 1: Provider 抽象层(2-3 周)
+
+**目标**: 建立 Agent Provider 统一接口,支持多后端
+
+| 任务 | 文件 | 说明 |
+|------|------|------|
+| 1.1 | `src/agent_providers/mod.rs` | 定义 `AgentProvider` trait |
+| 1.2 | `src/agent_providers/kimi_cli.rs` | 将现有 `agent_cli.rs` + `agent_runner.rs` 封装为 Provider |
+| 1.3 | `src/agent_providers/python_bridge.rs` | 新增 Provider,调用 Python AgentSession |
+| 1.4 | `src/agent_providers/openai.rs` | 新增 OpenAI API Provider |
+| 1.5 | `src/agent_providers/registry.rs` | Provider 注册与自动发现 |
+| 1.6 | `src/session_manager.rs` | 会话生命周期管理 |
+| 1.7 | 修改 `commands/ai.rs` | `agent_chat_send` 支持任意 Provider |
+| 1.8 | 修改 `commands/agent.rs` | `get_agents` 从 Provider 聚合 |
+
+**验收标准**:
+- `get_providers` 返回所有可用 Provider
+- `agent_chat_send` 可通过参数指定 Provider
+- Kimi CLI 聊天功能不中断
+
+### Phase 2: Python Orchestrator 整合(2 周)
+
+**目标**: 让 Rust Gateway 调用 Python AgentRouter 进行意图路由
+
+| 任务 | 文件 | 说明 |
+|------|------|------|
+| 2.1 | `core/python/contentforge/ai/gateway_bridge.py` | 新增:供 Rust 调用的路由桥接模块 |
+| 2.2 | `src/agent_providers/python_bridge.rs` | 实现 `route_intent()` 调用 Python |
+| 2.3 | 增强 `ai/agent_router.py` | 支持 Provider 偏好路由 |
+| 2.4 | 增强 `ai/agent_registry.py` | 支持 Provider 关联的 Agent 注册 |
+| 2.5 | `db/` 新增表 | `agent_providers`, `agent_sessions` |
+
+**验收标准**:
+- Rust Gateway 调用 Python Router 进行意图识别
+- 路由结果包含目标 Provider + Agent
+- 前端 `routeByIntent` 改为调用后端
+
+### Phase 3: 前端重构(2 周)
+
+**目标**: 前端 AgentStore 从静态定义改为动态加载
+
+| 任务 | 文件 | 说明 |
+|------|------|------|
+| 3.1 | `store/agentStore.ts` | 重构:从后端加载 Agent/Provider |
+| 3.2 | `types/agent.ts` | 扩展类型定义(含 Provider 信息) |
+| 3.3 | `app/settings/agent-providers/` | 新增 Provider 配置页面 |
+| 3.4 | `components/agent/` | 新增 Provider 选择器、Agent 卡片 |
+| 3.5 | 删除前端硬编码 | `DEFAULT_AGENTS`, `INTENT_PATTERNS` |
+
+**验收标准**:
+- 前端 Agent 列表与后端完全一致
+- 新增 Provider 后前端自动显示对应 Agent
+- Provider 配置页面可启用/禁用/配置 Provider
+
+### Phase 4: 多 Agent 协作(3 周)
+
+**目标**: 支持复杂任务的自动分解和多 Agent 协作
+
+| 任务 | 文件 | 说明 |
+|------|------|------|
+| 4.1 | `core/python/contentforge/ai/collaboration.py` | 新增协作执行器 |
+| 4.2 | 增强 `ai/agent_router.py` | `auto_collaborate()` 生产级化 |
+| 4.3 | `src/session_manager.rs` | 支持协作计划执行 |
+| 4.4 | 前端新增协作 UI | 协作进度展示、步骤卡片 |
+| 4.5 | `db/` 新增表 | `collaboration_plans`, `collaboration_steps` |
+
+**验收标准**:
+- "分析这个视频然后改写成小红书风格" 自动触发协作
+- 前端实时显示协作步骤进度
+- 协作结果汇总展示
+
+### Phase 5: Skill 系统贯通(2 周)
+
+**目标**: Skill 系统跨层贯通
+
+| 任务 | 文件 | 说明 |
+|------|------|------|
+| 5.1 | `core/python/contentforge/ai/skill_bridge.py` | Skill → Rust 调用桥接 |
+| 5.2 | `src/commands/skill.rs` | 新增 Skill 执行命令 |
+| 5.3 | `store/agentStore.ts` | `executeSkill` 调用后端 |
+| 5.4 | Skill 加载目录 | 支持 `.agents/skills/` 热加载 |
+
+---
+
+## 六、数据库 Schema 扩展
+
+### 6.1 新增表
+
+```sql
+-- Agent Provider 配置表
+CREATE TABLE agent_providers (
+    id TEXT PRIMARY KEY,           -- "kimi", "openai", "ollama"
+    name TEXT NOT NULL,            -- 显示名称
+    provider_type TEXT NOT NULL,   -- "kimi_cli", "openai_api", "ollama"
+    enabled INTEGER DEFAULT 1,
+    priority INTEGER DEFAULT 0,    -- 自动选择优先级
+    config TEXT,                   -- JSON 配置
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Agent 定义表(替代硬编码)
+CREATE TABLE agent_definitions (
+    id TEXT PRIMARY KEY,
+    provider_id TEXT NOT NULL,
+    name TEXT NOT NULL,
+    description TEXT,
+    system_prompt TEXT,
+    capabilities TEXT,             -- JSON ["analyze", "summarize"]
+    tools TEXT,                    -- JSON ["search_assets"]
+    model TEXT,
+    temperature REAL,
+    max_tokens INTEGER,
+    icon TEXT,
+    color TEXT,
+    auto_switch INTEGER DEFAULT 0,
+    requires_context INTEGER DEFAULT 1,
+    "order" INTEGER DEFAULT 0,
+    FOREIGN KEY (provider_id) REFERENCES agent_providers(id)
+);
+
+-- Agent 会话表
+CREATE TABLE agent_sessions (
+    id TEXT PRIMARY KEY,
+    agent_id TEXT NOT NULL,
+    provider_id TEXT NOT NULL,
+    status TEXT DEFAULT 'active',  -- active | paused | completed
+    context_variables TEXT,        -- JSON
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 协作计划表
+CREATE TABLE collaboration_plans (
+    id TEXT PRIMARY KEY,
+    description TEXT,
+    status TEXT DEFAULT 'pending', -- pending | running | completed | failed
+    session_id TEXT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    completed_at TIMESTAMP
+);
+
+-- 协作步骤表
+CREATE TABLE collaboration_steps (
+    id TEXT PRIMARY KEY,
+    plan_id TEXT NOT NULL,
+    step_index INTEGER,
+    agent_id TEXT NOT NULL,
+    task TEXT,
+    depends_on TEXT,               -- JSON [step_index]
+    status TEXT DEFAULT 'pending', -- pending | running | completed | failed
+    output TEXT,
+    error TEXT,
+    started_at TIMESTAMP,
+    completed_at TIMESTAMP,
+    FOREIGN KEY (plan_id) REFERENCES collaboration_plans(id)
+);
+```
+
+---
+
+## 七、关键决策记录
+
+### ADR-001: 为什么 Provider 抽象放在 Rust 层而不是 Python 层?
+
+**决策**: Agent Provider 的抽象和注册放在 Rust (Tauri) 层,但路由决策委托给 Python。
+
+**理由**:
+1. Rust 层是 Tauri 的 IPC Gateway,所有前端调用必须经过这里
+2. Provider 需要访问系统资源(进程 spawn、文件系统),Rust 更擅长
+3. Python 层专注高阶逻辑(路由、规划、协作),不处理底层 I/O
+4. 保持 Python 层的可替换性(未来可能用其他语言重写 AI 核心)
+
+### ADR-002: 为什么意图路由放在 Python 层?
+
+**决策**: 意图路由和 Agent 编排逻辑保留在 Python 层。
+
+**理由**:
+1. Python 已有完整的 `AgentRouter` 实现(LLM 调用、模式匹配、协作规划)
+2. Python 的 AI 生态更丰富(langchain 等可作为备选)
+3. 路由逻辑与 Agent 定义紧密耦合,放在同一层更内聚
+4. Rust 层作为 Thin Gateway,负责协议转换和事件转发
+
+### ADR-003: Agent 定义的唯一权威来源
+
+**决策**: Agent 定义的权威来源是运行时聚合(Provider 注册 + 数据库),而非静态代码。
+
+**理由**:
+1. 不同 Provider 可能有同名但不同能力的 Agent
+2. 用户可能自定义 Agent
+3. 需要支持动态启用/禁用
+4. 前端不再维护 Agent 定义副本
+
+---
+
+## 八、风险与缓解
+
+| 风险 | 影响 | 缓解措施 |
+|------|------|----------|
+| Kimi CLI 接口变更 | 高 | Provider 封装隔离,接口变更只需改 `kimi_cli.rs` |
+| Python Bridge 性能瓶颈 | 中 | 异步调用 + 流式传输,关键路径用 Rust 直接实现 |
+| 多 Provider 配置冲突 | 中 | 优先级系统 + 显式选择 UI |
+| 前端状态同步复杂 | 中 | Zustand 订阅模式 + WebSocket 实时推送 |
+| 向后兼容性 | 高 | Phase 1-3 保持现有接口可用,Phase 4 后逐步废弃 |
+
+---
+
+## 九、附录:术语表
+
+| 术语 | 定义 |
+|------|------|
+| **Agent Provider** | Agent 后端实现,如 Kimi CLI、OpenAI API、Python ReAct |
+| **Agent Gateway** | Rust Tauri 层的统一入口,负责 Provider 调度 |
+| **Agent Orchestrator** | Python 层的编排器,负责路由、规划、协作 |
+| **Collaboration Plan** | 多 Agent 协作的执行计划,包含步骤和依赖 |
+| **Shared Context** | 跨 Agent 共享的上下文(变量、记忆、资产) |
+| **Stream Event** | 流式事件(token、tool_call、error、done) |
+| **ReAct** | Reasoning + Acting 模式,Agent 的思考-行动循环 |
+
+---
+
+*本文档作为多智能体架构的长期规划,各 Phase 可独立实施,按需启动。*
diff --git a/contentforge/docs/spec/PROJECT_OVERVIEW.md b/contentforge/docs/spec/PROJECT_OVERVIEW.md
new file mode 100644
index 0000000..a54dad0
--- /dev/null
+++ b/contentforge/docs/spec/PROJECT_OVERVIEW.md
@@ -0,0 +1,422 @@
+# ContentForge 项目概览 SPEC
+
+> 版本: 0.1.0  
+> 最后更新: 2025-07-24  
+> 适用范围: ContentForge 全项目架构
+
+---
+
+## 1. 项目定位
+
+ContentForge 是一款跨平台内容获取→处理→发布工具链,支持从任意社交媒体(Twitter/X、YouTube、RSS、网页)采集内容,通过 AI 处理(摘要、改写、翻译、分析)转化为适合任意平台发布的内容格式。
+
+### 1.1 核心价值主张
+
+| 能力 | 描述 |
+|------|------|
+| **采集** | 支持 15+ 平台,通过 agent-reach CLI 统一封装 |
+| **处理** | AI 驱动的摘要、翻译、改写、分析、小红书文案转换 |
+| **流水线** | DAG 执行引擎,支持预设模板和自定义流程 |
+| **发布** | 多格式导出(Markdown、HTML、JSON、小红书格式) |
+| **Chat** | 内置 AI 对话界面,支持 Agent 路由与工具调用 |
+
+### 1.2 目标用户
+
+- 内容创作者(需要跨平台分发内容)
+- 知识工作者(需要将视频/文章转化为笔记)
+- 社交媒体运营(需要批量处理多源内容)
+
+---
+
+## 2. 架构总览
+
+ContentForge 采用 **Hybrid Architecture(混合架构)**,结合多种技术栈以发挥各自优势:
+
+```
+┌─────────────────────────────────────────────────────────────────────────────┐
+│                              ContentForge 架构                                │
+├─────────────────────────────────────────────────────────────────────────────┤
+│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
+│  │   Desktop    │  │     CLI      │  │    Web UI    │  │  Extension   │     │
+│  │  (Tauri v2)  │  │   (Go/Cobra) │  │  (Next.js)   │  │  (Chrome)    │     │
+│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
+│         │                  │                  │                  │           │
+│         └──────────────────┴──────────────────┴──────────────────┘           │
+│                                    │                                         │
+│                         ┌──────────┴──────────┐                             │
+│                         │   API 抽象层         │                             │
+│                         │  (Tauri IPC / HTTP)  │                             │
+│                         └──────────┬──────────┘                             │
+│                                    │                                         │
+│  ┌─────────────────────────────────┼─────────────────────────────────────┐  │
+│  │                                 ▼                                     │  │
+│  │  ┌─────────────────────────────────────────────────────────────┐    │  │
+│  │  │                    Python Core Engine                        │    │  │
+│  │  │  ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌──────┐ │    │  │
+│  │  │  │Ingestion│ │Processing│ │ Pipeline │ │   AI    │ │Chat  │ │    │  │
+│  │  │  │  Domain │ │  Domain  │ │ Engine   │ │ Engine  │ │Engine│ │    │  │
+│  │  │  └─────────┘ └──────────┘ └──────────┘ └─────────┘ └──────┘ │    │  │
+│  │  └─────────────────────────────────────────────────────────────┘    │  │
+│  │                              Rust Backend (Tauri)                    │  │
+│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────────┐   │  │
+│  │  │Downloader│ │  Queue  │ │Database │ │  VTT    │ │ Audio Extract│   │  │
+│  │  │ (yt-dlp) │ │ Manager │ │(SQLite) │ │ Analysis│ │              │   │  │
+│  │  └─────────┘ └─────────┘ └─────────┘ └─────────┘ └──────────────┘   │  │
+│  └─────────────────────────────────────────────────────────────────────┘  │
+└─────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 2.1 技术栈矩阵
+
+| 层级 | 技术选型 | 版本 | 职责 |
+|------|----------|------|------|
+| **CLI** | Go + Cobra | 1.24+ | 命令行交互、批量脚本 |
+| **Desktop Shell** | Tauri v2 (Rust) | 2.10+ | 桌面封装、系统级 API |
+| **Frontend** | Next.js + React 19 + Tailwind CSS | 15.x | UI 渲染、状态管理 |
+| **Core Engine** | Python 3.13 | 3.10+ | AI 处理、采集、流水线 |
+| **Database** | SQLite (via sqlx) | - | 本地数据持久化 |
+| **AI Provider** | OpenAI / Claude / Ollama | - | LLM 推理 |
+| **Video** | yt-dlp + FFmpeg | - | 视频下载与处理 |
+
+### 2.2 模块边界
+
+| 模块 | 代码路径 | 语言 | 运行环境 |
+|------|----------|------|----------|
+| CLI | `cli/` | Go | 本地终端 |
+| Python Core | `core/python/` | Python | venv 子进程 |
+| Desktop Frontend | `desktop/src/` | TypeScript | Tauri WebView |
+| Rust Backend | `desktop/src-tauri/src/` | Rust | Tauri Runtime |
+| Chrome Extension | `extension/` | JavaScript | 浏览器 |
+| Web Server | `web/` | TypeScript | Node.js (可选) |
+
+---
+
+## 3. 数据流架构
+
+### 3.1 核心数据实体
+
+```
+ContentUnit(内容单元)— 贯穿全生命周期的核心实体
+├── id: UUID
+├── source: SourceInfo(平台、URL、作者、互动数据)
+├── type: ContentType(video/article/tweet/thread/audio/image/note)
+├── title, description, extracted_text
+├── summary, key_points, sentiment, topics
+├── translated_text, rewritten_text
+├── status: ContentStatus(生命周期状态)
+├── tags, raw_metadata
+└── created_at, updated_at
+```
+
+### 3.2 生命周期状态机
+
+```
+ingested → processing → processed → editing → ready → published
+    ↑          ↓           ↓
+    └─────── failed ←──────┘
+```
+
+### 3.3 端到端数据流
+
+```
+[URL Input] → [Ingestion Layer] → [ContentUnit] → [Processing Layer]
+                                                  ↓
+[Publish Output] ← [Format Renderer] ← [Pipeline Engine] ← [AI Engine]
+```
+
+---
+
+## 4. 通信机制
+
+### 4.1 Go-Python 桥接
+
+CLI 通过 `PythonBridge` 调用 Python 核心模块:
+
+```go
+// 内部实现:生成内联 Python 脚本,通过子进程 JSON stdin/stdout 通信
+pb.CallWithOutput(
+    "contentforge.ingestion.agent_reach",  // 模块路径
+    "AgentReachIngestor",                  // 类名
+    map[string]interface{}{"_method": "fetch", "url": url},  // 参数
+    &result,
+)
+```
+
+### 4.2 Desktop 前后端通信
+
+| 模式 | 协议 | 用途 |
+|------|------|------|
+| IPC | Tauri `invoke()` | 同步命令(请求-响应) |
+| Event | Tauri `emit()`/`listen()` | 实时推送(下载进度、流式消息) |
+| WebSocket | `ws://` | Web 模式下的流式通信 |
+
+### 4.3 API 抽象层
+
+`api-client.ts` 提供统一接口,自动适配运行环境:
+
+```typescript
+apiInvoke(command: string, args?: unknown): Promise   // 同步调用
+apiListen(event: string, handler: (payload) => void): () => void  // 事件订阅
+```
+
+---
+
+## 5. 配置体系
+
+### 5.1 配置文件位置
+
+```
+~/.config/contentforge/config.yaml   # 主配置(YAML)
+~/.contentforge/                     # 状态目录(下载、数据库)
+```
+
+### 5.2 配置结构
+
+```yaml
+version: "1"
+ai_provider:
+  name: openai
+  api_key: sk-xxx
+  default_model: gpt-4o-mini
+ai_providers:  # 多 Provider 配置
+  - name: claude
+    api_key: sk-ant-xxx
+    default_model: claude-3-5-sonnet-20241022
+  - name: ollama
+    base_url: http://localhost:11434
+    default_model: llama3.1
+platform:
+  agent_reach_binary: agent-reach
+  ytdlp_binary: yt-dlp
+  ffmpeg_path: /usr/local/bin/ffmpeg
+proxy:
+  http: http://localhost:7890
+  https: http://localhost:7890
+publish_profiles:
+  - id: xiaohongshu
+    platform: xiaohongshu
+    max_length: 1000
+    auto_publish: false
+```
+
+### 5.3 环境变量覆盖
+
+| 环境变量 | 覆盖项 |
+|----------|--------|
+| `CF_AI_API_KEY` | AI Provider API Key |
+| `CF_AI_PROVIDER` | 默认 Provider 名称 |
+| `CF_AI_MODEL` | 默认模型 |
+| `CF_HTTP_PROXY` / `CF_HTTPS_PROXY` | 代理 |
+| `CF_YTDLP_BINARY` | yt-dlp 路径 |
+| `CF_FFMPEG_PATH` | FFmpeg 路径 |
+| `CF_STATE_DIR` | 状态目录 |
+| `CF_LOG_LEVEL` | 日志级别 |
+
+---
+
+## 6. 预设流水线
+
+| 预设 ID | 输入 | 输出 | 核心步骤 |
+|---------|------|------|----------|
+| `twitter_to_xiaohongshu` | Twitter URL | 小红书文案 | 采集 → 翻译 → 摘要 → 小红书转换 → 分析 |
+| `youtube_to_notes` | YouTube URL | 结构化笔记 | 采集(字幕) → 翻译 → 摘要 → 分析 → 改写 |
+| `rss_to_digest` | RSS Feed | 摘要报告 | 采集 → 过滤 → 摘要 → 分析 |
+| `web_to_summary` | 网页 URL | Markdown | 采集 → 摘要 → 分析 → 翻译 |
+| `ai_processing` | 已有内容 | 多格式 | 分析 → 摘要 → 改写 → 小红书 → 翻译 |
+
+---
+
+## 7. Agent 体系
+
+### 7.1 内置 Agent 角色
+
+| Agent ID | 名称 | 能力 | 自动切换 |
+|----------|------|------|----------|
+| `general` | 通用助手 | general, search | 否 |
+| `content_analyst` | 内容分析师 | analyze, search | 是 |
+| `summarizer` | 摘要专家 | summarize, search | 是 |
+| `rewriter` | 改写专家 | rewrite, translate, search | 是 |
+| `publisher` | 发布助手 | publish, search | 是 |
+| `pipeline_runner` | 流水线执行器 | pipeline, search | 是 |
+
+### 7.2 意图路由规则
+
+AgentStore 通过正则表达式模式匹配用户输入,自动路由到最合适的 Agent:
+
+```typescript
+// 示例:"分析这个视频" → content_analyst
+// 示例:"总结这篇文章" → summarizer
+// 示例:"转成小红书格式" → rewriter
+// 示例:"运行 twitter_to_xiaohongshu 流水线" → pipeline_runner
+```
+
+---
+
+## 8. 项目目录结构
+
+```
+contentforge/
+├── cli/                          # Go CLI
+│   ├── cmd/                      # Cobra 子命令
+│   │   ├── root.go               # 根命令定义
+│   │   ├── scrape.go             # 采集命令
+│   │   ├── process.go            # 处理命令
+│   │   ├── publish.go            # 发布命令
+│   │   └── pipeline.go           # 流水线命令
+│   ├── internal/
+│   │   ├── models/               # Go 数据模型
+│   │   └── python_bridge.go      # Go-Python 桥接
+│   ├── main.go
+│   └── go.mod
+│
+├── core/                         # Python 核心引擎
+│   ├── python/contentforge/
+│   │   ├── models.py             # 核心数据模型
+│   │   ├── config.py             # 配置管理
+│   │   ├── ingestion/            # 采集域
+│   │   │   ├── agent_reach.py    # agent-reach CLI 封装
+│   │   │   ├── web_scraper.py    # Jina Reader 封装
+│   │   │   ├── transcriber.py    # 视频转录
+│   │   │   └── health_check.py   # 平台健康检查
+│   │   ├── processing/           # 处理域
+│   │   │   ├── ai_engine.py      # AI Engine 抽象层
+│   │   │   ├── summarizer.py     # 摘要生成
+│   │   │   ├── analyzer.py       # 内容分析
+│   │   │   ├── translator.py     # 翻译
+│   │   │   └── xiaohongshu_converter.py  # 小红书转换
+│   │   ├── pipeline/             # 流水线域
+│   │   │   ├── engine.py         # DAG 执行引擎
+│   │   │   ├── presets.py        # 预设流水线
+│   │   │   └── runner.py         # 生命周期管理
+│   │   ├── ai/                   # AI 对话域
+│   │   │   ├── chat_engine.py    # 对话引擎
+│   │   │   ├── agent.py          # Agent 定义
+│   │   │   ├── agent_router.py   # Agent 路由
+│   │   │   ├── agent_registry.py # Agent 注册表
+│   │   │   ├── tools.py          # 工具定义
+│   │   │   ├── context.py        # 上下文管理
+│   │   │   └── session.py        # 会话管理
+│   │   └── cli/                  # Python CLI 桥接
+│   │       └── bridge.py
+│   └── scripts/
+│       ├── presets/              # 预设 JSON 文件
+│       └── cf-env.sh             # 环境变量脚本
+│
+├── desktop/                      # Tauri Desktop 应用
+│   ├── src/
+│   │   ├── app/                  # Next.js 页面路由
+│   │   │   ├── page.tsx          # 首页
+│   │   │   ├── settings/         # 设置页面
+│   │   │   ├── download/         # 下载管理
+│   │   │   └── workflows/        # 流水线页面
+│   │   ├── components/           # React 组件
+│   │   │   ├── layout/           # 布局组件
+│   │   │   ├── download/         # 下载相关组件
+│   │   │   └── forms/            # 表单组件
+│   │   ├── store/                # Zustand Stores
+│   │   │   ├── chatStore.ts      # 聊天状态
+│   │   │   ├── agentStore.ts     # Agent 状态
+│   │   │   ├── assetStore.ts     # 资产状态
+│   │   │   └── downloadStore.ts  # 下载状态
+│   │   ├── lib/
+│   │   │   ├── api-client.ts     # API 抽象层
+│   │   │   └── ws-client.ts      # WebSocket 客户端
+│   │   └── types/                # TypeScript 类型定义
+│   └── src-tauri/
+│       ├── src/                  # Rust 后端源码
+│       │   ├── lib.rs            # 应用入口
+│       │   ├── commands.rs       # Tauri IPC 命令
+│       │   ├── downloader.rs     # yt-dlp 下载器
+│       │   ├── queue.rs          # 下载队列管理
+│       │   ├── db/               # 数据库模块
+│       │   ├── vtt_analysis.rs   # VTT 字幕分析
+│       │   └── audio_extractor.rs# 音频提取
+│       └── Cargo.toml
+│
+├── extension/                    # Chrome 扩展
+│   ├── content/                  # Content Script
+│   └── popup/                    # Popup UI
+│
+├── web/                          # Web UI (可选部署)
+│   └── src/
+│       ├── controllers/
+│       ├── middleware/
+│       └── routes/
+│
+└── docs/                         # 项目文档
+    ├── spec/                     # 模块 SPEC 文档
+    ├── architecture/             # 架构决策记录
+    ├── api/                      # API 文档
+    └── guides/                   # 用户指南
+```
+
+---
+
+## 9. 开发与部署
+
+### 9.1 环境要求
+
+| 组件 | 最低版本 | 安装方式 |
+|------|----------|----------|
+| macOS | 13+ | - |
+| Python | 3.10+ | Homebrew / pyenv |
+| Go | 1.24+ | Homebrew |
+| Rust | 1.77+ | rustup |
+| Node.js | 20+ | nvm / Homebrew |
+| FFmpeg | 6.0+ | Homebrew |
+| yt-dlp | 最新 | pip / Homebrew |
+
+### 9.2 快速启动
+
+```bash
+# 1. 运行环境设置
+bash setup-macos.sh
+
+# 2. 加载环境变量
+source contentforge/core/scripts/cf-env.sh
+
+# 3. 验证 CLI
+contentforge --help
+
+# 4. 启动 Desktop
+cd desktop && pnpm dev
+
+# 5. 启动 Tauri
+cd desktop && pnpm tauri dev
+```
+
+### 9.3 构建命令
+
+```bash
+# CLI 构建
+cd cli && go build -o contentforge
+
+# Desktop 构建
+cd desktop && pnpm tauri build
+
+# Web 构建
+cd desktop && pnpm build
+```
+
+---
+
+## 10. 扩展点
+
+| 扩展类型 | 方式 | 文档 |
+|----------|------|------|
+| 自定义 Pipeline Step | 继承 `StepHandler` 基类 | [PYTHON_CORE_SPEC](PYTHON_CORE_SPEC.md) |
+| 自定义 Agent | 定义 `AgentRole` 配置 | [FRONTEND_SPEC](FRONTEND_SPEC.md) |
+| 自定义 Skill | Markdown + YAML Frontmatter | [PYTHON_CORE_SPEC](PYTHON_CORE_SPEC.md) |
+| 新增 AI Provider | 继承 `AIProvider` 基类 | [PYTHON_CORE_SPEC](PYTHON_CORE_SPEC.md) |
+| 新增发布格式 | 扩展 `renderContent()` | [CLI_SPEC](CLI_SPEC.md) |
+
+---
+
+## 11. 相关文档索引
+
+| 文档 | 路径 | 内容 |
+|------|------|------|
+| CLI SPEC | [CLI_SPEC.md](CLI_SPEC.md) | Go CLI 命令、标志、Go-Python 桥接 |
+| Python Core SPEC | [PYTHON_CORE_SPEC.md](PYTHON_CORE_SPEC.md) | AI Engine、Pipeline、采集、处理域 |
+| Rust Backend SPEC | [RUST_BACKEND_SPEC.md](RUST_BACKEND_SPEC.md) | Tauri IPC、下载器、队列、数据库 |
+| Frontend SPEC | [FRONTEND_SPEC.md](FRONTEND_SPEC.md) | Next.js 组件、Store、API 客户端、类型 |
diff --git a/contentforge/docs/spec/PYTHON_CORE_SPEC.md b/contentforge/docs/spec/PYTHON_CORE_SPEC.md
new file mode 100644
index 0000000..92992d7
--- /dev/null
+++ b/contentforge/docs/spec/PYTHON_CORE_SPEC.md
@@ -0,0 +1,1690 @@
+# ContentForge Python Core SPEC
+
+**任务标识**: `PYTHON_CORE_SPEC`  
+**版本**: v1.0  
+**撰写语言**: 中文(技术术语保持英文)
+
+---
+
+## 目录
+
+1. [概述](#1-概述)
+2. [模块总览](#2-模块总览)
+3. [核心数据模型](#3-核心数据模型)
+4. [配置管理](#4-配置管理)
+5. [AI 模块](#5-ai-模块)
+6. [采集模块](#6-采集模块)
+7. [流水线模块](#7-流水线模块)
+8. [处理模块](#8-处理模块)
+9. [CLI 桥接层](#9-cli-桥接层)
+10. [工具系统](#10-工具系统)
+11. [错误处理与日志](#11-错误处理与日志)
+12. [依赖关系图](#12-依赖关系图)
+
+---
+
+## 1. 概述
+
+ContentForge Python Core 是 ContentForge 项目的内容处理引擎,负责内容采集、AI 处理、流水线编排和发布格式化。它作为 Go CLI / Tauri 后端的子进程被调用,通过 JSON over stdout 进行通信。
+
+### 1.1 设计原则
+
+- **不引入 LangChain**:自研轻量 Agent 框架,基于 ReAct 模式
+- **多 Provider 支持**:OpenAI、Claude、Ollama 统一抽象
+- **Pipeline 驱动**:可编排的 DAG 步骤执行
+- **本地优先**:SQLite 持久化、本地文件访问
+- **Skill 可扩展**:Markdown + YAML Frontmatter 格式的 Skill 定义
+
+### 1.2 技术栈
+
+| 组件 | 技术 |
+|------|------|
+| 语言 | Python 3.10+ |
+| HTTP 客户端 | `requests` |
+| 数据解析 | `PyYAML`(可选) |
+| 数据库 | SQLite3(标准库) |
+| 外部工具 | `yt-dlp`, `ffmpeg/ffprobe`, `agent-reach` |
+
+---
+
+## 2. 模块总览
+
+```
+core/python/contentforge/
+├── __init__.py              # 包入口
+├── models.py                # 核心数据模型
+├── config.py                # 配置管理
+│
+├── ai/                      # AI 模块
+│   ├── __init__.py
+│   ├── agent.py             # Agent 角色定义与意图路由
+│   ├── agent_registry.py    # Agent 注册与生命周期管理
+│   ├── agent_router.py      # 意图路由与协作编排
+│   ├── agent_session.py     # ReAct 会话与工具调用
+│   ├── chat_engine.py       # 对话引擎
+│   ├── content_access.py    # 本地内容访问层
+│   ├── asset_retriever.py   # 智能资产检索
+│   ├── video_inspector.py   # 视频元数据提取
+│   ├── context.py           # Token 预算与上下文管理
+│   ├── session.py           # 会话管理
+│   ├── router.py            # Agent 动态路由
+│   ├── tools.py             # 工具注册与执行
+│   ├── USAGE_EXAMPLES.py    # 使用示例
+│   └── skills/              # Skill 系统
+│       ├── skill_loader.py
+│       ├── skill_executor.py
+│       ├── skill_context.py
+│       └── examples.py
+│
+├── cli/                     # CLI 桥接层
+│   ├── __init__.py
+│   ├── __main__.py
+│   ├── bridge.py
+│   ├── scrape.py
+│   ├── process.py
+│   ├── publish.py
+│   └── pipeline.py
+│
+├── ingestion/               # 采集模块
+│   ├── __init__.py
+│   ├── agent_reach.py       # agent-reach CLI 封装
+│   ├── web_scraper.py       # Jina Reader 网页采集
+│   ├── transcriber.py       # 视频字幕提取
+│   └── health_check.py      # 健康检查
+│
+├── pipeline/                # 流水线模块
+│   ├── __init__.py
+│   ├── engine.py            # 流水线执行引擎
+│   ├── presets.py           # 预设流水线
+│   └── runner.py            # 流水线运行器
+│
+├── processing/              # 处理模块
+│   ├── __init__.py
+│   ├── ai_engine.py         # AI Engine 多 Provider 抽象
+│   ├── analyzer.py          # 内容分析器
+│   ├── summarizer.py        # 摘要生成器
+│   ├── translator.py        # 多语言翻译器
+│   └── xiaohongshu_converter.py  # 小红书文案转换器
+│
+└── publishing/              # 发布域(占位)
+    └── __init__.py
+```
+
+---
+
+## 3. 核心数据模型
+
+**文件**: `contentforge/models.py`
+
+### 3.1 枚举定义
+
+#### `ContentType`
+
+内容类型枚举,标识内容的来源形式。
+
+| 成员 | 值 | 说明 |
+|------|-----|------|
+| `VIDEO` | `"video"` | 视频 |
+| `ARTICLE` | `"article"` | 文章 |
+| `TWEET` | `"tweet"` | 单条推文 |
+| `THREAD` | `"thread"` | 推文线程 |
+| `AUDIO` | `"audio"` | 音频 |
+| `IMAGE` | `"image"` | 图片 |
+| `NOTE` | `"note"` | 笔记 |
+
+#### `ContentStatus`
+
+内容生命周期状态。
+
+| 成员 | 值 |
+|------|-----|
+| `INGESTED` | `"ingested"` |
+| `PROCESSING` | `"processing"` |
+| `PROCESSED` | `"processed"` |
+| `EDITING` | `"editing"` |
+| `READY` | `"ready"` |
+| `PUBLISHED` | `"published"` |
+| `FAILED` | `"failed"` |
+
+#### `PipelineStatus`
+
+流水线执行状态。
+
+| 成员 | 值 |
+|------|-----|
+| `PENDING` | `"pending"` |
+| `RUNNING` | `"running"` |
+| `COMPLETED` | `"completed"` |
+| `FAILED` | `"failed"` |
+| `CANCELLED` | `"cancelled"` |
+| `PARTIAL` | `"partial"` |
+
+### 3.2 数据类
+
+#### `SourceInfo`
+
+```python
+@dataclass
+class SourceInfo:
+    platform: str                    # 来源平台(youtube, twitter, web, rss...)
+    url: str                         # 原始 URL
+    author: Optional[str] = None     # 作者
+    published_at: Optional[datetime] = None
+    engagement: Dict[str, int] = {}  # 互动数据(likes, replies, reposts, views)
+```
+
+**属性**:
+- `likes`, `replies`, `reposts`, `views` — 从 `engagement` 字典读取的便捷属性
+- `to_dict()` — 序列化为字典
+
+#### `ContentUnit`
+
+核心内容单元,贯穿采集 → 处理 → 编辑 → 发布全生命周期。
+
+```python
+@dataclass
+class ContentUnit:
+    id: str
+    source: SourceInfo
+    type: ContentType
+    title: str = ""
+    description: str = ""
+    extracted_text: str = ""          # 提取的原始文本
+    summary: Optional[str] = None     # 摘要
+    key_points: List[str] = []        # 关键要点
+    sentiment: Optional[str] = None   # 情感标签
+    topics: List[str] = []            # 主题标签
+    translated_text: Optional[str] = None
+    rewritten_text: Optional[str] = None
+    status: ContentStatus = ContentStatus.INGESTED
+    pipeline_id: Optional[str] = None
+    tags: List[str] = []
+    file_path: Optional[str] = None   # 关联的本地文件路径
+    raw_metadata: Dict[str, Any] = {} # 原始元数据
+    error: Optional[str] = None
+    created_at: datetime
+    updated_at: datetime
+```
+
+**方法**:
+- `word_count` — 基于 `extracted_text` 的词数统计
+- `to_dict()` / `to_json()` — 序列化
+- `from_dict(cls, data)` — 反序列化
+
+#### `PipelineStep`
+
+```python
+@dataclass
+class PipelineStep:
+    id: str
+    type: str                         # 步骤类型(ingest, summarize, translate...)
+    config: Dict[str, Any] = {}       # 步骤配置
+    input_mapping: Dict[str, str] = {}
+    output_mapping: Dict[str, str] = {}
+    max_retries: int = 3              # 最大重试次数
+    backoff: str = "exponential"      # 退避策略(exponential / linear)
+    delay_ms: int = 1000              # 基础延迟
+    condition: Optional[str] = None   # 条件表达式
+    timeout_ms: int = 30000           # 超时(毫秒)
+```
+
+#### `Pipeline`
+
+```python
+@dataclass
+class Pipeline:
+    id: str
+    name: str
+    description: str = ""
+    steps: List[PipelineStep] = []
+    trigger: str = "manual"           # 触发方式(manual / schedule / webhook)
+    schedule: Optional[str] = None    # Cron 表达式
+    input_config: Dict[str, Any] = {}
+    output_config: Dict[str, Any] = {}
+    enabled: bool = True
+    last_run_at: Optional[datetime] = None
+    run_count: int = 0
+    fail_count: int = 0
+```
+
+#### `PipelineRun`
+
+```python
+@dataclass
+class PipelineRun:
+    id: str
+    pipeline_id: str
+    status: PipelineStatus = PipelineStatus.PENDING
+    started_at: datetime
+    completed_at: Optional[datetime] = None
+    steps: List[Dict] = []            # 每步的执行结果
+    input_unit_ids: List[str] = []
+    output_unit_ids: List[str] = []
+    logs: List[str] = []
+    error: Optional[str] = None
+```
+
+#### `PublishProfile`
+
+```python
+@dataclass
+class PublishProfile:
+    id: str
+    name: str
+    platform: str                     # 目标平台
+    credentials: Dict[str, str] = {}
+    default_format: str = "markdown"
+    default_template: str = ""
+    auto_publish: bool = False
+    max_length: Optional[int] = None
+    image_config: Optional[Dict] = None
+```
+
+---
+
+## 4. 配置管理
+
+**文件**: `contentforge/config.py`
+
+### 4.1 配置模型
+
+#### `AIProviderConfig`
+
+| 字段 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `name` | `str` | `"openai"` | Provider 名称 |
+| `api_key` | `str` | `""` | API Key(序列化时自动掩码) |
+| `base_url` | `str` | `""` | 自定义 Base URL |
+| `default_model` | `str` | `""` | 默认模型 |
+| `timeout` | `int` | `120` | 超时(秒) |
+
+#### `PlatformBackendConfig`
+
+| 字段 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `agent_reach_binary` | `str` | `"agent-reach"` | agent-reach 可执行文件路径 |
+| `ytdlp_binary` | `str` | `"yt-dlp"` | yt-dlp 可执行文件路径 |
+| `ffmpeg_path` | `Optional[str]` | `None` | FFmpeg 路径 |
+| `jina_api_key` | `Optional[str]` | `None` | Jina Reader API Key |
+
+#### `ContentForgeConfig`
+
+完整配置聚合类,包含:
+- `version: str = "1"`
+- `ai_provider: AIProviderConfig` — 默认 AI Provider
+- `ai_providers: List[AIProviderConfig]` — 多 Provider 列表
+- `platform: PlatformBackendConfig` — 平台后端配置
+- `proxy: ProxyConfig` — 代理配置
+- `publish_profiles: List[PublishProfileConfig]` — 发布 Profile 列表
+- `default_pipeline: str = ""` — 默认流水线
+- `log_level: str = "INFO"`
+- `state_dir: str = ""` — 状态目录
+
+### 4.2 ConfigManager
+
+配置管理器,支持 YAML 文件加载和环境变量覆盖。
+
+```python
+class ConfigManager:
+    ENV_PREFIX = "CF_"
+    DEFAULT_CONFIG_PATH = ~/.config/contentforge/config.yaml
+```
+
+**方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `load()` | 加载配置,优先文件,然后环境变量覆盖 |
+| `reload()` | 重新加载配置 |
+| `get()` | 获取当前配置(缓存) |
+| `save(config)` | 保存配置到文件 |
+| `init_default()` | 创建默认配置并保存 |
+
+**环境变量映射**:
+
+| 环境变量 | 覆盖字段 |
+|----------|----------|
+| `CF_AI_API_KEY` | `ai_provider.api_key` |
+| `CF_AI_PROVIDER` | `ai_provider.name` |
+| `CF_AI_MODEL` | `ai_provider.default_model` |
+| `CF_AI_BASE_URL` | `ai_provider.base_url` |
+| `CF_AGENT_REACH_BINARY` | `platform.agent_reach_binary` |
+| `CF_YTDLP_BINARY` | `platform.ytdlp_binary` |
+| `CF_FFMPEG_PATH` | `platform.ffmpeg_path` |
+| `CF_JINA_API_KEY` | `platform.jina_api_key` |
+| `CF_HTTP_PROXY` / `CF_HTTPS_PROXY` | `proxy.http` / `proxy.https` |
+| `CF_LOG_LEVEL` | `log_level` |
+| `CF_STATE_DIR` | `state_dir` |
+
+**全局便捷函数**:
+- `get_config(config_path=None)` — 懒加载全局配置
+- `reload_config()` — 重新加载全局配置
+
+---
+
+## 5. AI 模块
+
+### 5.1 Agent 角色定义
+
+**文件**: `contentforge/ai/agent.py`
+
+#### `AgentCapability` 枚举
+
+| 成员 | 值 |
+|------|-----|
+| `ANALYZE` | `"analyze"` |
+| `SUMMARIZE` | `"summarize"` |
+| `REWRITE` | `"rewrite"` |
+| `TRANSLATE` | `"translate"` |
+| `PUBLISH` | `"publish"` |
+| `PIPELINE` | `"pipeline"` |
+| `SEARCH` | `"search"` |
+| `GENERAL` | `"general"` |
+
+#### `AgentRole`
+
+Agent 角色定义数据类:
+
+| 字段 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `id` | `str` | 必填 | 唯一标识 |
+| `name` | `str` | 必填 | 显示名称 |
+| `description` | `str` | 必填 | 功能描述 |
+| `system_prompt` | `str` | 必填 | 系统提示词 |
+| `capabilities` | `List[AgentCapability]` | 必填 | 能力列表 |
+| `tools` | `List[str]` | 必填 | 可用工具列表 |
+| `model` | `str` | `"gpt-4o-mini"` | 使用的模型 |
+| `temperature` | `float` | `0.7` | 温度参数 |
+| `max_tokens` | `int` | `4000` | 最大 Token |
+| `context_window` | `int` | `128000` | 上下文窗口 |
+| `icon` | `str` | `"bot"` | UI 图标 |
+| `color` | `str` | `"#6366f1"` | UI 颜色 |
+| `auto_switch` | `bool` | `False` | 是否支持自动切换 |
+| `streaming` | `bool` | `True` | 是否支持流式输出 |
+| `requires_context` | `bool` | `True` | 是否需要上下文 |
+| `order` | `int` | `0` | 排序权重 |
+
+#### 内置 Agent 列表
+
+| ID | 名称 | 能力 | 模型 | 自动切换 |
+|-----|------|------|------|----------|
+| `general` | 通用助手 | GENERAL, SEARCH | gpt-4o-mini | 否 |
+| `content_analyst` | 内容分析师 | ANALYZE, SEARCH | gpt-4o | 是 |
+| `summarizer` | 摘要专家 | SUMMARIZE, SEARCH | gpt-4o-mini | 是 |
+| `rewriter` | 改写专家 | REWRITE, TRANSLATE, SEARCH | gpt-4o | 是 |
+| `publisher` | 发布助手 | PUBLISH, SEARCH | gpt-4o-mini | 是 |
+| `pipeline_runner` | 流水线执行器 | PIPELINE, SEARCH | gpt-4o-mini | 是 |
+
+#### `AgentRegistry`
+
+Agent 注册表,管理内置 Agent 的注册、发现和基于意图的路由。
+
+**方法**:
+
+| 方法 | 签名 | 说明 |
+|------|------|------|
+| `register` | `(agent: AgentRole) -> None` | 注册 Agent |
+| `unregister` | `(agent_id: str) -> None` | 注销 Agent |
+| `get_agent` | `(agent_id: str) -> Optional[AgentRole]` | 获取 Agent |
+| `list_agents` | `() -> List[AgentRole]` | 列出所有 Agent(按 order 排序) |
+| `get_by_capability` | `(capability: AgentCapability) -> Optional[AgentRole]` | 按能力查找 |
+| `route_by_intent` | `(message, selected_asset_ids, current_agent_id) -> str` | 基于意图路由 |
+
+**意图路由策略**:
+1. 检查是否显式提及 Agent(`AGENT_MENTIONS` 模式匹配)
+2. 基于 `INTENT_PATTERNS` 进行 capability 评分
+3. 选择最高分的 capability 对应的 Agent
+4. 无明确意图时保持当前 Agent
+
+### 5.2 Agent 注册中心
+
+**文件**: `contentforge/ai/agent_registry.py`
+
+`AgentRegistry`(注意:此模块与 `agent.py` 中的 `AgentRegistry` 是**不同实现**,`agent_registry.py` 提供更完整的功能):
+
+- **单例模式**:全局共享 Agent 定义和状态
+- **SQLite 持久化**:`~/.contentforge/agent_registry.db`
+- **Skill 注册表集成**:`SkillRegistry`
+
+#### 数据模型
+
+**`AgentStatus` 枚举**:
+- `IDLE`, `BUSY`, `PAUSED`, `ERROR`, `TERMINATED`
+
+**`AgentRole` 枚举**:
+- `ORCHESTRATOR`, `ASSISTANT`, `WRITER`, `ANALYST`, `RESEARCHER`, `PUBLISHER`, `CUSTOM`
+
+**`AgentDefinition`**:
+静态配置数据类,包含 `id`, `name`, `role`, `system_prompt`, `model`, `provider`, `skills`, `tools`, `memory_enabled`, `max_history` 等字段。
+
+**`AgentState`**:
+运行时状态数据类,包含 `status`, `current_task`, `memory_snapshot`, `context_variables`, `last_active`, `error_message`, `metrics`。
+
+**`SkillManifest`**:
+Skill 元数据,支持 YAML Frontmatter + Markdown Body 格式。
+
+#### 核心方法
+
+| 方法 | 说明 |
+|------|------|
+| `register(agent_def)` | 注册新 Agent,持久化到 SQLite |
+| `unregister(agent_id)` | 注销 Agent,删除持久化数据 |
+| `get(agent_id)` | 获取 Agent 定义 |
+| `get_state(agent_id)` | 获取运行时状态 |
+| `update_state(agent_id, **kwargs)` | 更新状态字段 |
+| `list_agents(role, status)` | 按角色或状态过滤 |
+| `find_by_skill(skill_name)` | 查找支持指定 Skill 的 Agent |
+| `find_by_name(name)` | 按名称精确匹配 |
+| `search(query)` | 模糊搜索 |
+| `create_custom_agent(...)` | 便捷方法:创建自定义 Agent |
+| `reset_state(agent_id)` | 重置状态(清空记忆) |
+| `to_json()` | 导出所有定义为 JSON |
+
+#### SkillRegistry
+
+Skill 注册中心,管理 Markdown + YAML Frontmatter 格式的 Skill。
+
+**Skill 目录优先级**:
+1. `CONTENTFORGE_SKILL_DIR` 环境变量
+2. `~/.contentforge/skills/`
+3. `contentforge/skills/`(包内目录)
+
+**核心方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `load_all()` | 扫描所有 Skill 目录 |
+| `register(manifest, handler)` | 手动注册 Skill |
+| `get(name)` | 获取 Skill 元数据 |
+| `list_skills(tag)` | 按 tag 过滤 |
+| `search(query)` | 模糊搜索 |
+| `bind_handler(name, handler)` | 绑定执行函数 |
+| `to_prompt_context(skill_names)` | 转换为 LLM prompt 上下文 |
+
+### 5.3 Agent Router
+
+**文件**: `contentforge/ai/agent_router.py`
+
+`AgentRouter` 负责用户意图分析、Agent 调度、多 Agent 协作编排。
+
+#### 路由决策类型
+
+```python
+class RoutingDecision(Enum):
+    DIRECT = "direct"           # 直接路由到指定 Agent
+    DELEGATE = "delegate"       # 委派给子 Agent
+    COLLABORATE = "collaborate" # 多 Agent 协作
+    SKILL = "skill"             # 直接触发 Skill
+    CLARIFY = "clarify"         # 需要澄清
+```
+
+#### `RouteResult`
+
+```python
+@dataclass
+class RouteResult:
+    decision: RoutingDecision
+    target_agent_ids: List[str]
+    skill_name: Optional[str]
+    skill_params: Dict[str, Any]
+    reasoning: str
+    confidence: float
+    user_message: str
+    context: Dict[str, Any]
+```
+
+#### `CollaborationPlan`
+
+多 Agent 协作计划:
+```python
+@dataclass
+class CollaborationPlan:
+    plan_id: str
+    description: str
+    steps: List[Dict]  # {agent_id, task, depends_on, output_key}
+    status: str        # pending | running | completed | failed
+```
+
+#### 核心方法
+
+| 方法 | 说明 |
+|------|------|
+| `route(user_message, context)` | 分析用户消息,返回路由决策 |
+| `route_stream(user_message, context)` | 流式路由,产出决策过程 |
+| `create_collaboration_plan(description, steps)` | 创建协作计划 |
+| `execute_collaboration_plan(plan_id, initial_context)` | 执行协作计划(Generator) |
+| `auto_collaborate(message, context)` | 自动分析需求并生成协作计划 |
+| `suggest_agents(message, top_k)` | 推荐最匹配的 Agent(含相似度分数) |
+| `get_route_history(n)` | 获取最近路由历史 |
+| `cancel_plan(plan_id)` | 取消协作计划 |
+
+**路由流程**:
+1. **快速模式匹配** — 关键词/正则匹配 Skill
+2. **显式 Agent 指定** — 检测 `@AgentName` 格式
+3. **LLM 推理路由** — 使用 Orchestrator Agent 做 LLM 推理
+
+### 5.4 Agent Session
+
+**文件**: `contentforge/ai/agent_session.py`
+
+`AgentSession` 是完整的 ReAct 风格 Agent 运行时。
+
+#### 数据模型
+
+**`MessageRole` 枚举**:
+- `SYSTEM`, `USER`, `ASSISTANT`, `TOOL`, `AGENT`
+
+**`ChatMessage`**:
+统一消息格式,支持 `to_llm_message()` 转换。
+
+**`ToolDefinition`**:
+工具定义,包含 `name`, `description`, `parameters`(JSON Schema), `handler`。
+
+**`ToolCall`** / **`ToolResult`**:
+工具调用实例和执行结果。
+
+**`SessionConfig`**:
+会话配置,包含 `session_id`, `max_turns`, `enable_multi_agent`, `enable_skills`, `stream_response`, `persist_history`。
+
+#### 内置工具
+
+| 工具名 | 说明 | 参数 |
+|--------|------|------|
+| `query_content_units` | 查询 SQLite 内容资产 | `limit`, `type`, `status`, `search`, `tags` |
+| `read_file` | 读取本地文件 | `path`, `max_length` |
+| `list_content_assets` | 列出内容资产统计 | `platform` |
+| `get_video_metadata` | 获取视频元数据 | `content_id` |
+| `execute_skill` | 执行注册 Skill | `skill_name`, `params` |
+| `switch_agent` | 切换 Agent | `agent_name`, `reason` |
+
+#### 核心方法
+
+| 方法 | 说明 |
+|------|------|
+| `send_message(user_message, context)` | 同步发送消息,返回完整响应 |
+| `send_message_stream(user_message, context)` | 流式发送消息,产出增量 token 和事件 |
+| `set_active_agent(agent_id)` | 手动设置当前 Agent |
+| `add_context(key, value)` | 添加上下文变量 |
+| `export_history()` | 导出完整对话历史 |
+| `clear_history()` | 清空对话历史(保留系统消息) |
+| `get_stats()` | 获取会话统计 |
+
+**流式事件类型**:
+- `thinking` — 思考过程
+- `tool_call` — 工具调用
+- `tool_result` — 工具执行结果
+- `token` — 增量文本
+- `agent_switch` — Agent 切换
+- `done` / `error` — 完成/错误
+
+**ReAct 循环**:
+1. 路由决策 → 确定目标 Agent
+2. 构建 LLM 消息(含上下文截断)
+3. 调用 LLM,收集响应
+4. 检测工具调用(JSON/ReAct 格式)
+5. 执行工具,将结果注入对话
+6. 重复直到无工具调用或达到最大迭代次数(默认 5 次)
+
+### 5.5 Chat Engine
+
+**文件**: `contentforge/ai/chat_engine.py`
+
+`ChatEngine` 是对话引擎,职责:
+- 管理对话历史和会话状态
+- 调用 AI Engine 生成响应
+- 支持流式输出
+- 集成工具调用(Function Calling)
+- Agent 路由与切换
+
+**核心方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `chat(session_id, message, agent_id, selected_asset_ids)` | 非流式对话 |
+| `stream_chat(session_id, message, agent_id, selected_asset_ids)` | 流式对话 |
+| `cancel_stream(message_id)` | 取消流式生成 |
+
+**流式事件类型**:
+- `text` — 文本块
+- `tool_call` — 工具调用
+- `tool_result` — 工具结果
+- `agent_switched` — Agent 切换通知
+- `done` — 完成
+- `error` — 错误
+
+### 5.6 Content Access
+
+**文件**: `contentforge/ai/content_access.py`
+
+`ContentAccess` 是 Chat 对话框本地内容访问层统一入口。
+
+#### 数据模型
+
+**`TextSearchResult`**:
+- `asset_id`, `field`, `snippet`, `score`, `matched_terms`
+
+**`ContentQuery`**:
+支持基础过滤、文本搜索、时间范围、分页、排序。
+
+**`ContentAccessResult`**:
+- `success`, `data`, `error`, `total_count`, `execution_time_ms`
+
+#### `DatabaseConnection`
+
+SQLite 连接上下文管理器:
+- 支持连接池和行工厂
+- 自动启用外键约束 (`PRAGMA foreign_keys = ON`)
+- 使用 WAL 模式 (`PRAGMA journal_mode = WAL`)
+- 自动回滚/提交
+
+#### ContentAccess 核心能力
+
+**1. SQLite 数据库查询**:
+
+| 方法 | 说明 |
+|------|------|
+| `get_asset(asset_id)` | 按 ID 获取单个资产 |
+| `get_assets_by_ids(asset_ids)` | 批量获取 |
+| `query_assets(query)` | 通用查询(支持 FTS5 + LIKE 回退) |
+| `save_asset(asset)` | 保存或更新 |
+| `delete_asset(asset_id)` | 删除 |
+
+**FTS5 全文检索**:
+- 虚拟表 `content_assets_fts`
+- 覆盖字段:`id`, `title`, `extracted_text`, `summary`, `transcript`
+- 无 FTS 结果时自动回退到 `LIKE` 模糊匹配
+
+**2. 文件系统读取**:
+
+| 方法 | 说明 |
+|------|------|
+| `read_file(file_path, max_bytes)` | 安全读取本地文件(默认 10MB 限制) |
+| `read_asset_file(asset_id)` | 读取资产关联文件 |
+| `list_asset_files(asset_id)` | 列出资产目录下的文件 |
+
+**安全策略**:
+- 路径规范化(`resolve`)防止目录遍历
+- 文件大小限制
+- 多编码尝试(utf-8, gbk, latin-1)
+
+**3. 文本内容检索**:
+
+| 方法 | 说明 |
+|------|------|
+| `search_text(query, fields, limit)` | 关键词搜索,返回带上下文的片段 |
+| `get_text_content(asset_id, field)` | 获取指定字段文本 |
+| `get_combined_text(asset_id)` | 按优先级拼接文本(title > summary > extracted_text > transcript) |
+
+**4. 视频元数据**:
+- `get_video_metadata(asset_id)` — 委托 `VideoInspector`
+- `list_videos(status, limit)` — 列出视频资产
+
+**5. 工具方法**:
+- `get_stats()` — 获取内容库统计
+- `to_prompt_context(asset_ids, max_length)` — 转换为 LLM prompt 上下文
+
+### 5.7 Asset Retriever
+
+**文件**: `contentforge/ai/asset_retriever.py`
+
+`AssetRetriever` 在 `ContentAccess` 之上提供智能检索策略。
+
+#### 数据模型
+
+**`AssetSearchResult`**:
+- `asset`, `score`, `matched_fields`, `match_reason`, `related_asset_ids`
+
+**`RetrievalContext`**:
+- 多轮检索上下文,包含 `original_query`, `expanded_queries`, `filters_applied`, `previous_results`
+
+**`AssetRelation`**:
+- `source_id`, `target_id`, `relation_type`, `strength`
+
+#### 检索策略
+
+| 策略 | 说明 | 触发条件 |
+|------|------|----------|
+| 精确匹配 | ID / URL 精确匹配 | 查询看起来像 UUID 或 asset ID |
+| 全文检索 | FTS5 / LIKE 检索 | 普通文本查询 |
+| 标签过滤 | 按标签匹配 | 有解析出的关键词 |
+| 关系图谱 | 通过 pipeline/platform/author 发现关联 | 结果不足时自动触发 |
+
+#### 评分维度
+
+| 维度 | 权重逻辑 |
+|------|----------|
+| 文本匹配度 | 关键词出现频率 × 字段权重 |
+| 字段权重 | title(3.0) > tags(2.5) > summary(2.0) > transcript(1.5) > extracted_text(1.0) |
+| 时效性 | 最近 1 天 +0.3,7 天 +0.2,30 天 +0.1 |
+| 质量信号 | ready 状态 +0.1,有摘要 +0.05 |
+
+#### 核心方法
+
+| 方法 | 说明 |
+|------|------|
+| `search(query, ...)` | 智能搜索入口 |
+| `search_similar(asset_id)` | 查找相似内容 |
+| `get_recent_assets(days)` | 获取最近添加的资产 |
+| `get_pipeline_assets(pipeline_id)` | 获取 Pipeline 关联资产 |
+| `get_asset_relations(asset_id)` | 获取关系图谱 |
+| `get_recommended_assets(asset_id)` | 推荐相关资产 |
+| `expand_query(query)` | 查询扩展(同义词) |
+
+### 5.8 Video Inspector
+
+**文件**: `contentforge/ai/video_inspector.py`
+
+`VideoInspector` 负责视频元数据提取。
+
+#### 数据模型
+
+**`VideoStreamInfo`**:
+- `index`, `codec`, `width`, `height`, `fps`, `bitrate`, `pixel_format`, `color_space`
+
+**`AudioStreamInfo`**:
+- `index`, `codec`, `sample_rate`, `channels`, `bitrate`, `language`
+
+**`SubtitleStreamInfo`**:
+- `index`, `codec`, `language`, `title`, `is_forced`, `is_default`
+
+**`VideoMetadata`**:
+- 基础信息:`file_path`, `source_url`, `title`, `duration_sec`, `size_bytes`
+- 流信息:`video_streams`, `audio_streams`, `subtitle_streams`
+- 关键属性:`width`, `height`, `fps`, `video_bitrate`, `audio_bitrate`
+- 缩略图:`thumbnail_path`
+- 章节和标签:`chapters`, `tags`
+- 原始输出:`raw_info`
+
+**便捷属性**:
+- `resolution` — 分辨率字符串(如 "1920x1080")
+- `duration_str` — 人类可读时长(如 "01:23:45")
+- `has_subtitles` — 是否有字幕轨道
+- `has_multiple_audio` — 是否有多个音轨
+
+#### 核心方法
+
+| 方法 | 说明 |
+|------|------|
+| `inspect_file(file_path)` | 提取本地视频完整元数据(ffprobe) |
+| `inspect_file_quick(file_path)` | 快速检查(仅基础信息) |
+| `inspect_url(url)` | 获取在线视频元数据(yt-dlp --dump-json) |
+| `extract_thumbnail(file_path, timestamp)` | 提取缩略图(ffmpeg) |
+| `extract_keyframes(file_path, count)` | 提取关键帧(均匀分布) |
+| `extract_subtitle_text(file_path, stream_index)` | 提取字幕文本 |
+| `get_available_formats(url)` | 获取在线视频可用格式列表 |
+| `check_health()` | 检查依赖健康状态 |
+
+#### 异常类型
+
+| 异常 | 说明 |
+|------|------|
+| `VideoInspectorError` | 通用错误 |
+| `FFmpegNotFoundError` | FFmpeg 未安装 |
+| `YTDLPNotFoundError` | yt-dlp 未安装 |
+| `VideoProbeError` | 视频探测失败 |
+
+### 5.9 Context 管理
+
+**文件**: `contentforge/ai/context.py`
+
+`ContextManager` 负责构建 LLM 上下文消息列表,管理 Token 预算。
+
+#### `TokenBudget`
+
+```python
+@dataclass
+class TokenBudget:
+    max_tokens: int = 128000
+    reserved: Dict[str, int] = {
+        "system": 2000,
+        "tools": 3000,
+        "response": 4000,
+        "buffer": 2000,
+    }
+```
+
+- `available` — 可用 Token 数(max - reserved)
+- `estimate_tokens(text)` — 简化估算(每 3 字符 ≈ 1 token)
+- `allocate_for_assets(assets)` — 为资产分配 Token 预算(优先摘要,超长截断)
+- `truncate_history(messages, max_tokens)` — 从后往前截断历史消息
+
+#### 上下文层级
+
+| 层级 | 内容 | 优先级 |
+|------|------|--------|
+| L1 | System Context(Agent 角色 + 工具列表) | 最高 |
+| L3 | Asset Context(选中资产内容) | 高 |
+| L4 | Tool Context(工具调用结果) | 中 |
+| L2 | Session Context(历史消息) | 动态截断 |
+| — | 当前用户消息 | 必保留 |
+
+### 5.10 Session 管理
+
+**文件**: `contentforge/ai/session.py`
+
+`SessionManager` 管理会话生命周期和消息历史。
+
+**核心方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `create_session(...)` | 创建会话 |
+| `get_session(session_id)` | 获取会话 |
+| `list_sessions(status, agent_id, limit, offset)` | 列出会话 |
+| `archive_session(session_id)` | 归档会话 |
+| `add_message(message)` | 添加消息 |
+| `get_messages(session_id, limit, offset, before_id)` | 获取消息历史(支持分页) |
+| `link_asset(session_id, asset_id)` | 关联资产 |
+| `search_sessions(query)` | 搜索会话 |
+
+**参考数据库 Schema**:
+```sql
+CREATE TABLE chat_sessions (
+    id TEXT PRIMARY KEY,
+    title TEXT NOT NULL DEFAULT '新会话',
+    agent_id TEXT NOT NULL DEFAULT 'general',
+    status TEXT NOT NULL DEFAULT 'active',
+    linked_task_id TEXT,
+    linked_asset_ids TEXT DEFAULT '[]',
+    metadata TEXT DEFAULT '{}',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE chat_messages (
+    id TEXT PRIMARY KEY,
+    session_id TEXT NOT NULL,
+    role TEXT NOT NULL,
+    content TEXT NOT NULL,
+    tool_calls TEXT,
+    tool_results TEXT,
+    selected_asset_ids TEXT DEFAULT '[]',
+    tokens_used TEXT,
+    model TEXT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    FOREIGN KEY (session_id) REFERENCES chat_sessions(id)
+);
+```
+
+### 5.11 Skill 系统
+
+#### 5.11.1 Skill Loader
+
+**文件**: `contentforge/ai/skills/skill_loader.py`
+
+`SkillLoader` 从文件系统加载和索引 Skill。
+
+**Skill 文件格式**(Markdown + YAML Frontmatter):
+
+```markdown
+---
+name: xiaohongshu_publish
+description: 将内容转换为小红书文案并发布
+version: "1.0.0"
+author: contentforge
+category: publishing
+tags: ["social", "xiaohongshu"]
+triggers:
+  - type: keyword
+    patterns: ["小红书", "xhs", "xiaohongshu"]
+  - type: intent
+    patterns: ["publish_to_xiaohongshu"]
+parameters:
+  - name: content
+    type: string
+    required: true
+    description: 要转换的内容
+tools:
+  - name: xiaohongshu_converter
+    description: 转换内容到小红书格式
+    required: true
+---
+
+# 小红书发布 Skill
+
+## 使用说明
+...
+
+```prompt
+你是一个小红书文案专家...
+```
+```
+
+**触发器类型**:
+- `keyword` — 关键词包含匹配
+- `intent` — 意图精确/包含匹配
+- `regex` — 正则表达式匹配
+- `semantic` — 语义匹配(占位)
+
+**核心方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `load_all()` | 加载所有 Skill |
+| `match(text, min_confidence, top_k)` | 自然语言匹配 |
+| `match_exact(name)` | 精确匹配 |
+| `suggest(text)` | 获取匹配建议(UI 展示) |
+| `create_skill_template(name)` | 生成 Skill 模板 |
+| `save_skill_template(name)` | 保存模板到文件 |
+
+#### 5.11.2 Skill Executor
+
+**文件**: `contentforge/ai/skills/skill_executor.py`
+
+`SkillExecutor` 是 Skill 执行引擎,自研轻量 ReAct 风格 Agent 框架。
+
+**行动类型**:
+- `THINK` — 思考/推理
+- `TOOL_CALL` — 调用工具
+- `ANSWER` — 直接回答
+- `CLARIFY` — 请求澄清
+- `SKILL_SWITCH` — 切换 Skill
+
+**执行模式**:
+- `react_mode=True` — ReAct 风格(Thought/Action/Action Input/Observation)
+- `react_mode=False` — Function Calling 风格
+
+**核心方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `execute(skill, user_input, context, args)` | 同步执行 Skill |
+| `stream_execute(skill, user_input, context, args)` | 流式执行 |
+| `stream_with_tools(skill, user_input, context, args)` | 流式执行,产出工具调用事件 |
+| `extract_parameters(skill, user_input, context)` | 从用户输入中提取参数 |
+| `route(user_input, context)` | 路由到合适的 Skill |
+| `auto_execute(user_input, context)` | 自动路由并执行 |
+| `add_tool_result(messages, tool_call, result)` | 将工具结果添加回消息列表 |
+
+#### 5.11.3 Skill Context
+
+**文件**: `contentforge/ai/skills/skill_context.py`
+
+`SkillContext` 统一封装所有本地访问能力。
+
+**组成**:
+- `content: ContentAccess` — SQLite 数据访问
+- `file: FileAccess` — 文件系统访问
+- `tools: ToolRegistry` — 工具注册与调用
+
+**内置工具**:
+
+| 工具名 | 功能 |
+|--------|------|
+| `content_search` | 搜索本地内容 |
+| `content_read` | 读取 ContentUnit |
+| `content_list` | 列出内容 |
+| `file_read` | 读取本地文件 |
+| `file_list` | 列出文件 |
+| `video_metadata` | 获取视频元数据 |
+| `pipeline_list` | 列出 PipelineRun |
+| `pipeline_run` | 执行 Pipeline |
+| `ai_generate` | 调用 AI 生成 |
+| `ai_summarize` | 调用 AI 摘要 |
+
+---
+
+## 6. 采集模块
+
+### 6.1 Agent-Reach 采集器
+
+**文件**: `contentforge/ingestion/agent_reach.py`
+
+`AgentReachIngestor` 封装 `agent-reach` CLI,支持多平台内容获取。
+
+**支持的平台**:
+
+| 方法 | 平台 | 说明 |
+|------|------|------|
+| `fetch_twitter(url)` | Twitter/X | 获取推文或线程 |
+| `fetch_web(url)` | 任意网页 | 获取网页内容 |
+| `fetch_youtube(url)` | YouTube | 获取视频元数据和字幕 |
+| `fetch_rss(feed_url, limit)` | RSS Feed | 获取文章列表 |
+| `fetch(url)` | 自动检测 | 根据 URL 自动分派 |
+
+**自动检测规则**:
+- `twitter.com` / `x.com` → Twitter
+- `youtube.com` / `youtu.be` → YouTube
+- `.rss` / `feed.xml` / `/rss/` / `/feed/` → RSS
+- 其他 → Web
+
+**CLI 调用方式**:
+```bash
+agent-reach fetch --json --platform  
+```
+
+**输出解析**:
+- 尝试解析最后有效的 JSON 行(处理日志+JSON 混合输出)
+- 超时:默认 120 秒
+
+### 6.2 网页采集器
+
+**文件**: `contentforge/ingestion/web_scraper.py`
+
+`JinaWebScraper`(别名 `WebScraper`)使用 Jina AI Reader API 获取网页内容。
+
+**API 端点**:
+- HTTP: `https://r.jina.ai/http://`
+- HTTPS: `https://r.jina.ai/https://`
+
+**特性**:
+- 自动将网页转换为结构化 Markdown
+- 支持批量 URL 获取
+- 自定义 User-Agent 和代理
+- API Token 支持(提高速率限制)
+
+**方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `fetch(url)` | 获取单个网页 |
+| `fetch_batch(urls)` | 批量获取 |
+| `fetch_with_metadata(url)` | 获取原始 Markdown 和元数据 |
+| `health_check()` | 检测服务可用性 |
+
+**响应解析**:
+- 第一行以 `Title: ` 开头 → 提取标题
+- 最后一行以 `Source URL: ` 开头 → 提取源 URL
+
+### 6.3 转录器
+
+**文件**: `contentforge/ingestion/transcriber.py`
+
+`Transcriber` 支持多种后端的视频/音频转录。
+
+**支持的后端**:
+
+| 后端 | 说明 |
+|------|------|
+| `yt-dlp` | 提取 YouTube 字幕/自动字幕(默认) |
+| `agent-reach` | 调用 agent-reach transcribe 子命令 |
+| `ffmpeg-whisper` | 提取音频(需外部 Whisper 服务) |
+
+**yt-dlp 流程**:
+1. `--skip-download --write-subs --write-auto-subs --sub-langs en,zh-Hans,zh-Hant --sub-format vtt`
+2. 查找生成的 VTT 文件
+3. 解析 VTT 为纯文本(移除时间戳和标记行)
+4. 无字幕时回退到视频描述
+
+**VTT 解析**:
+- 移除 `WEBVTT` 标记
+- 移除时间戳行(`00:00:00.000 --> 00:00:01.000`)
+- 保留纯文本内容
+
+### 6.4 健康检查
+
+**文件**: `contentforge/ingestion/health_check.py`
+
+`HealthChecker` 检测各平台可用性。
+
+**检查项**:
+
+| 名称 | 检查方式 | 超时 |
+|------|----------|------|
+| agent-reach | `--version` | 10s |
+| yt-dlp | `--version` | 10s |
+| ffmpeg | `-version` | 10s |
+| jina-reader | `GET https://r.jina.ai/http://example.com` | 15s |
+| internet | `GET https://1.1.1.1` | 10s |
+
+**状态等级**:
+- `ok` — 正常
+- `degraded` — 降级(超时或 HTTP 错误)
+- `fail` — 失败
+
+**方法**:
+- `check_all()` — 运行全部检查
+- `doctor()` — 返回汇总报告
+
+---
+
+## 7. 流水线模块
+
+### 7.1 执行引擎
+
+**文件**: `contentforge/pipeline/engine.py`
+
+`PipelineEngine` 支持 DAG 步骤执行、重试、超时、条件判断。
+
+#### 步骤处理器
+
+所有处理器继承自 `StepHandler` 抽象基类:
+
+```python
+class StepHandler(ABC):
+    @property
+    @abstractmethod
+    def step_type(self) -> str: ...
+    
+    @abstractmethod
+    def execute(self, step, inputs, context) -> List[ContentUnit]: ...
+```
+
+**内置处理器**:
+
+| 处理器 | 类型 | 说明 |
+|--------|------|------|
+| `IngestionHandler` | `ingest` | 采集内容 |
+| `SummarizeHandler` | `summarize` | 生成摘要 |
+| `RewriteHandler` | `rewrite` | 改写风格 |
+| `XiaohongshuHandler` | `xiaohongshu` | 转换为小红书文案 |
+| `TranslateHandler` | `translate` | 翻译 |
+| `AnalyzeHandler` | `analyze` | 分析内容 |
+| `FilterHandler` | `filter` | 过滤内容 |
+| `CustomHandler` | `custom` | 自定义函数(占位) |
+
+#### PipelineEngine 核心方法
+
+| 方法 | 说明 |
+|------|------|
+| `register_handler(handler)` | 注册自定义处理器 |
+| `run(pipeline, inputs, context, fail_fast)` | 执行流水线 |
+
+**执行特性**:
+- 顺序执行 steps
+- 每步支持 `max_retries` 次重试
+- 超时控制(`timeout_ms`)
+- 条件判断(`condition` 表达式,使用 `eval` 安全命名空间)
+- 错误处理:`fail_fast=True` 时遇到错误立即停止;否则继续处理未失败的输入
+- 退避策略:`exponential`(delay × 2^(attempt-1))或 `linear`
+
+**状态判定**:
+- 全部失败 → `FAILED`
+- 部分失败 → `PARTIAL`
+- 全部成功 → `COMPLETED`
+
+### 7.2 预设流水线
+
+**文件**: `contentforge/pipeline/presets.py`
+
+内置 5 个预设流水线:
+
+| 预设名称 | 说明 | 步骤 |
+|----------|------|------|
+| `twitter_to_xiaohongshu` | Twitter 转小红书 | ingest → translate → summarize → xiaohongshu → analyze |
+| `youtube_to_notes` | YouTube 转笔记 | ingest → translate(条件) → summarize → analyze → rewrite |
+| `rss_to_digest` | RSS 转摘要 | ingest → filter → summarize → analyze |
+| `web_to_summary` | 网页转摘要 | ingest → summarize → analyze → translate(条件) |
+| `ai_processing` | 通用 AI 处理 | analyze → summarize → rewrite → xiaohongshu(条件) → translate(条件) |
+
+**PipelinePreset 方法**:
+- `to_pipeline(pipeline_id)` — 转换为可执行的 Pipeline 实例
+
+### 7.3 运行器
+
+**文件**: `contentforge/pipeline/runner.py`
+
+`PipelineRunner` 管理 PipelineRun 生命周期、日志持久化、状态查询。
+
+**存储结构**:
+```
+~/.config/contentforge/runs/
+├── {run_id}.json        # 运行记录
+├── logs/
+│   └── {run_id}.log     # 执行日志
+└── outputs/
+    └── {run_id}.json    # 输出 ContentUnit
+```
+
+**核心方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `run(pipeline, inputs, context, fail_fast)` | 执行流水线 |
+| `run_preset(preset_name, context, **input_params)` | 使用预设执行 |
+| `get_status(run_id)` | 获取运行状态 |
+| `get_logs(run_id)` | 获取日志 |
+| `get_outputs(run_id)` | 获取输出 |
+| `list_runs(pipeline_id, limit)` | 列出历史运行 |
+| `cancel(run_id)` | 取消运行 |
+| `retry(run_id, context)` | 重试失败的运行 |
+| `cleanup(max_age_days)` | 清理旧记录 |
+
+---
+
+## 8. 处理模块
+
+### 8.1 AI Engine
+
+**文件**: `contentforge/processing/ai_engine.py`
+
+`AIEngine` 是多 Provider AI 调用统一入口。
+
+#### 支持的 Provider
+
+| Provider | 类 | 默认 Base URL | 认证方式 |
+|----------|-----|---------------|----------|
+| OpenAI | `OpenAIProvider` | `https://api.openai.com/v1` | `Authorization: Bearer {api_key}` |
+| Claude | `ClaudeProvider` | `https://api.anthropic.com/v1` | `x-api-key: {api_key}` |
+| Ollama | `OllamaProvider` | `http://localhost:11434` | 无 |
+
+**所有 Provider 支持**:
+- `chat(messages, **kwargs)` — 非流式对话
+- `stream(messages, **kwargs)` — 流式对话(Generator)
+
+#### AIConfig
+
+| 字段 | 默认值 | 说明 |
+|------|--------|------|
+| `provider` | `"openai"` | Provider 名称 |
+| `api_key` | `None` | API Key |
+| `base_url` | `None` | 自定义 Base URL |
+| `model` | `"gpt-4o-mini"` | 模型 |
+| `temperature` | `0.7` | 温度 |
+| `max_tokens` | `2000` | 最大 Token |
+| `timeout` | `60` | 超时(秒) |
+| `proxy` | `None` | 代理 |
+
+#### AIEngine 方法
+
+| 方法 | 说明 |
+|------|------|
+| `generate(prompt, system, **kwargs)` | 通用生成 |
+| `generate_structured(prompt, system, **kwargs)` | 生成结构化 JSON(自动解析) |
+| `summarize(text, max_length, **kwargs)` | 生成摘要 |
+| `rewrite(text, style, **kwargs)` | 改写文本 |
+
+**JSON 解析容错**:
+1. 尝试直接解析
+2. 尝试从 ` ```json ` 代码块提取
+3. 尝试从 ` ``` ` 代码块提取
+
+#### 异常类型
+
+| 异常 | 说明 |
+|------|------|
+| `AIEngineError` | 通用错误 |
+| `AIProviderNotFoundError` | Provider 不存在 |
+| `AIAPIError` | API 调用错误 |
+
+### 8.2 分析器
+
+**文件**: `contentforge/processing/analyzer.py`
+
+`Analyzer` 提供主题提取、关键词提取、情感分析和内容质量评估。
+
+**分析模式**:
+
+| 模式 | 说明 |
+|------|------|
+| `ai` | 调用 LLM 深度分析(默认) |
+| `quick` | 基于规则的高速分析 |
+| `both` | 两者结合(AI 负责 topics/entities,快速模式补充 keywords) |
+
+**AI 分析输出**:
+- `topics` — 3-5 个主题
+- `keywords` — 10-15 个关键词
+- `entities` — 命名实体
+- `sentiment` — 情感标签(positive/neutral/negative)+ 置信度 + 解释
+- `audience` — 目标受众
+- `quality_score` — 质量评分(1-10)
+
+**快速分析规则**:
+- 分词:保留中文和英文,去除标点
+- 关键词:频率统计,去除停用词
+- 情感:基于正负词典计数
+- 主题:基于关键词共现映射到预定义主题
+- 实体:匹配大写组合(英文)和引号内容
+
+### 8.3 摘要器
+
+**文件**: `contentforge/processing/summarizer.py`
+
+`Summarizer` 调用 AI Engine 生成结构化摘要。
+
+**支持风格**:
+
+| 风格 | 说明 |
+|------|------|
+| `structured` | 结构化(What I Learned + Key Patterns + One-Sentence Summary) |
+| `concise` | 简洁(<150 词) |
+| `detailed` | 详细(全面分析) |
+| `bullets` | 要点列表(7-10 项) |
+| `executive` | 执行摘要(100-200 词,面向决策者) |
+
+**结构化摘要解析**:
+- 提取 `### What I Learned` 下的列表项
+- 提取 `### Key Patterns` 下的列表项
+- 提取 `### One-Sentence Summary`
+- 提取 `### Confidence Assessment`
+
+### 8.4 翻译器
+
+**文件**: `contentforge/processing/translator.py`
+
+`Translator` 支持多语言翻译。
+
+**支持语言**(部分):
+- `zh` — 中文(简体)
+- `en` — English
+- `ja` — 日本語
+- `ko` — 한국어
+- `de`, `fr`, `es`, `it`, `pt`, `ru`, `ar`, `hi`, `th`, `vi` ...
+
+**翻译模式**:
+
+| 模式 | 说明 |
+|------|------|
+| `full` | 完整翻译(默认) |
+| `summary` | 摘要式翻译 |
+| `concise` | 精简翻译 |
+
+**特性**:
+- 自动语言检测(基于字符比例:中文 >30%,日文 >20%,韩文 >20%,默认英文)
+- 长文本自动分段(每段 8000 字符)
+- 翻译缓存(基于文本前 500 字符的 hash)
+- 上下文感知翻译(`translate_with_context`)
+
+### 8.5 小红书转换器
+
+**文件**: `contentforge/processing/xiaohongshu_converter.py`
+
+`XiaohongshuConverter` 将内容转换为小红书风格文案。
+
+**转换模式**:
+
+| 模式 | 说明 |
+|------|------|
+| AI 模式 | 调用 LLM 高质量改写(推荐,需 engine) |
+| 模板模式 | 基于规则快速转换(无需 AI) |
+
+**小红书风格规则**:
+1. 标题:emoji 开头,制造悬念或共鸣(20 字以内)
+2. 开场:第一人称建立信任("我发现""亲测")
+3. 正文:短段落、大量 emoji、关键信息加粗
+4. 标签:文末 3-5 个 `#话题`
+5. 互动:结尾引导点赞收藏
+
+**后处理**:
+- 调整 emoji 密度(low/medium/high)
+- 字数控制(自动截断到 max_length)
+- 质量评分(length, emoji, hashtag, interaction, structure 五维度)
+
+---
+
+## 9. CLI 桥接层
+
+### 9.1 主入口
+
+**文件**: `contentforge/cli/__init__.py`
+
+Python CLI 作为 Go 后端的子进程被调用:
+
+```bash
+python -m contentforge.cli  [args]
+```
+
+**子命令**:
+
+| 命令 | 说明 | 数据来源 |
+|------|------|----------|
+| `scrape` | 采集内容 | `stdin` JSON payload |
+| `process` | AI 处理 | `stdin` JSON payload |
+| `publish` | 发布/导出 | `stdin` JSON payload |
+| `pipeline` | 流水线操作 | `stdin` JSON payload |
+
+**通信协议**:
+- 输入:`stdin` 中的 JSON payload
+- 输出:`stdout` 中的 JSON 结果
+- 日志:`stderr`
+
+### 9.2 Bridge 模块
+
+**文件**: `contentforge/cli/bridge.py`
+
+提供 argparse 风格的 CLI 接口,支持以下命令:
+
+| 命令 | 参数 | 说明 |
+|------|------|------|
+| `scrape` | `--url`, `--type`, `--backend` | 采集内容 |
+| `process` | `--input`, `--mode`, `--target-lang`, `--tone`, `--max-length` | AI 处理 |
+| `publish` | `--input`, `--format` | 发布/导出 |
+| `pipeline_list` | 无 | 列出预设 |
+| `pipeline_run` | `--preset`, `--url`, `--feed-url`, `--input` | 执行流水线 |
+| `pipeline_create` | `--preset`, `--new-name`, `--output` | 基于预设创建 |
+| `pipeline_status` | `--run-id` | 查询状态 |
+
+### 9.3 各子命令处理器
+
+**文件**: `contentforge/cli/scrape.py`, `process.py`, `publish.py`, `pipeline.py`
+
+**统一输入格式**:
+```json
+{
+  "action": "summarize",
+  "input_data": "...",
+  "url": "...",
+  "...": "..."
+}
+```
+
+**统一输出格式**:
+```json
+{
+  "success": true,
+  "data": { ... }
+}
+```
+或
+```json
+{
+  "success": false,
+  "error": "错误信息"
+}
+```
+
+---
+
+## 10. 工具系统
+
+**文件**: `contentforge/ai/tools.py`
+
+### 10.1 工具定义
+
+**`ToolParameter`**:
+- `name`, `type`, `description`, `required`, `enum`, `default`
+
+**`ToolDefinition`**:
+- `name`, `description`, `parameters`, `handler`, `requires_confirmation`, `async_handler`, `category`, `icon`
+- `to_openai_schema()` — 转换为 OpenAI Function Calling Schema
+- `to_claude_schema()` — 转换为 Claude Tool Use Schema
+
+**`ToolExecutionResult`**:
+- `success`, `output`, `error`, `duration_ms`, `metadata`
+
+### 10.2 工具执行器
+
+`ToolExecutor` 管理所有可用工具。
+
+**内置工具列表**:
+
+| 工具名 | 类别 | 说明 |
+|--------|------|------|
+| `scrape` | ingestion | 从 URL 采集内容 |
+| `analyze` | processing | 分析内容 |
+| `summarize` | processing | 生成摘要 |
+| `rewrite` | processing | 改写风格 |
+| `translate` | processing | 翻译内容 |
+| `xiaohongshu_convert` | processing | 转小红书文案 |
+| `run_pipeline` | pipeline | 执行预设流水线 |
+| `search_assets` | asset | 搜索内容资产库 |
+| `get_asset_detail` | asset | 获取资产详情 |
+| `publish` | publishing | 导出内容 |
+
+**核心方法**:
+
+| 方法 | 说明 |
+|------|------|
+| `register(tool)` | 注册工具 |
+| `execute(name, args)` | 执行工具 |
+| `describe_tools(tool_names)` | 生成工具描述文本 |
+| `get_schemas_for_llm(tool_names)` | 获取 LLM 可用的 Schema 列表 |
+
+---
+
+## 11. 错误处理与日志
+
+### 11.1 日志配置
+
+所有模块使用标准 `logging` 库:
+
+```python
+logger = logging.getLogger(__name__)
+```
+
+**CLI 日志格式**:
+```
+%(asctime)s [%(levelname)s] %(name)s: %(message)s
+```
+
+### 11.2 异常体系
+
+| 模块 | 异常类 | 说明 |
+|------|--------|------|
+| `ai_engine` | `AIEngineError` | AI Engine 通用错误 |
+| `ai_engine` | `AIProviderNotFoundError` | Provider 不存在 |
+| `ai_engine` | `AIAPIError` | API 调用错误 |
+| `analyzer` | `AnalyzerError` | 分析器错误 |
+| `summarizer` | `SummarizerError` | 摘要器错误 |
+| `translator` | `TranslatorError` | 翻译器错误 |
+| `xiaohongshu_converter` | `XiaohongshuError` | 小红书转换错误 |
+| `pipeline/engine` | `PipelineEngineError` | 流水线引擎错误 |
+| `pipeline/presets` | `PresetError` | 预设错误 |
+| `pipeline/runner` | `PipelineRunnerError` | 运行器错误 |
+| `content_access` | `ContentAccessError` | 内容访问通用错误 |
+| `content_access` | `AssetNotFoundError` | 资产不存在 |
+| `content_access` | `DatabaseConnectionError` | 数据库连接失败 |
+| `content_access` | `FileAccessError` | 文件系统访问失败 |
+| `video_inspector` | `VideoInspectorError` | 视频检查器通用错误 |
+| `video_inspector` | `FFmpegNotFoundError` | FFmpeg 未安装 |
+| `video_inspector` | `YTDLPNotFoundError` | yt-dlp 未安装 |
+| `video_inspector` | `VideoProbeError` | 视频探测失败 |
+| `skill_context` | `ToolNotFoundError` | 工具未找到 |
+| `skill_context` | `ToolExecutionError` | 工具执行错误 |
+
+---
+
+## 12. 依赖关系图
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                        CLI 桥接层                            │
+│              (scrape / process / publish / pipeline)         │
+└─────────────┬───────────────────────────────┬───────────────┘
+              │                               │
+    ┌─────────▼─────────┐         ┌───────────▼──────────┐
+    │    采集模块        │         │      处理模块         │
+    │  (agent_reach)    │         │   (ai_engine)        │
+    │  (web_scraper)    │◄────────┤   (analyzer)         │
+    │  (transcriber)    │         │   (summarizer)       │
+    └─────────┬─────────┘         │   (translator)       │
+              │                   │   (xiaohongshu)      │
+              │                   └───────────┬──────────┘
+              │                               │
+    ┌─────────▼───────────────────────────────▼────────────┐
+    │              核心数据模型 (models.py)                 │
+    │           ContentUnit / Pipeline / PipelineRun       │
+    └─────────┬───────────────────────────────┬────────────┘
+              │                               │
+    ┌─────────▼─────────┐         ┌───────────▼────────────┐
+    │    流水线模块      │         │       AI 模块          │
+    │  (engine)         │         │  (agent_registry)      │
+    │  (presets)        │         │  (agent_router)        │
+    │  (runner)         │         │  (agent_session)       │
+    └───────────────────┘         │  (content_access)      │
+                                  │  (asset_retriever)     │
+                                  │  (video_inspector)     │
+                                  │  (skill_*)             │
+                                  └───────────┬────────────┘
+                                              │
+                                  ┌───────────▼────────────┐
+                                  │      配置管理          │
+                                  │    (config.py)         │
+                                  └────────────────────────┘
+```
+
+---
+
+## 附录 A:数据库 Schema 参考
+
+### content_assets 表
+
+```sql
+CREATE TABLE IF NOT EXISTS content_assets (
+    id TEXT PRIMARY KEY,
+    type TEXT NOT NULL,
+    title TEXT,
+    description TEXT,
+    source_url TEXT,
+    source_platform TEXT,
+    file_path TEXT,
+    extracted_text TEXT,
+    summary TEXT,
+    transcript TEXT,
+    language TEXT,
+    duration_sec REAL,
+    status TEXT DEFAULT 'ingested',
+    metadata TEXT DEFAULT '{}',
+    tags TEXT DEFAULT '[]',
+    pipeline_id TEXT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+```
+
+### 索引
+
+```sql
+CREATE INDEX IF NOT EXISTS idx_assets_type ON content_assets(type);
+CREATE INDEX IF NOT EXISTS idx_assets_status ON content_assets(status);
+CREATE INDEX IF NOT EXISTS idx_assets_platform ON content_assets(source_platform);
+CREATE INDEX IF NOT EXISTS idx_assets_created ON content_assets(created_at);
+CREATE INDEX IF NOT EXISTS idx_assets_pipeline ON content_assets(pipeline_id);
+```
+
+### FTS5 虚拟表
+
+```sql
+CREATE VIRTUAL TABLE IF NOT EXISTS content_assets_fts USING fts5(
+    id, title, extracted_text, summary, transcript,
+    content='content_assets', content_rowid='rowid'
+);
+```
+
+### agent_registry 表
+
+```sql
+CREATE TABLE IF NOT EXISTS agent_definitions (
+    id TEXT PRIMARY KEY,
+    name TEXT NOT NULL,
+    role TEXT NOT NULL,
+    description TEXT,
+    system_prompt TEXT,
+    model TEXT,
+    provider TEXT,
+    temperature REAL,
+    max_tokens INTEGER,
+    skills TEXT,
+    tools TEXT,
+    memory_enabled INTEGER,
+    max_history INTEGER,
+    metadata TEXT,
+    created_at TEXT
+);
+
+CREATE TABLE IF NOT EXISTS agent_states (
+    agent_id TEXT PRIMARY KEY,
+    status TEXT NOT NULL,
+    current_task TEXT,
+    memory_snapshot TEXT,
+    context_variables TEXT,
+    last_active TEXT,
+    error_message TEXT,
+    metrics TEXT,
+    FOREIGN KEY (agent_id) REFERENCES agent_definitions(id)
+);
+```
+
+---
+
+**文档结束**
diff --git a/contentforge/docs/spec/RUST_BACKEND_SPEC.md b/contentforge/docs/spec/RUST_BACKEND_SPEC.md
new file mode 100644
index 0000000..4c7be4c
--- /dev/null
+++ b/contentforge/docs/spec/RUST_BACKEND_SPEC.md
@@ -0,0 +1,595 @@
+# ContentForge Rust Backend 模块 SPEC
+
+> 版本: 0.1.0  
+> 模块路径: `desktop/src-tauri/src/`  
+> 语言: Rust 1.77+  
+> 框架: Tauri v2.10.3
+
+---
+
+## 1. 模块定位
+
+Rust Backend 是 ContentForge Desktop 的 Tauri 后端层,负责:
+- 提供 Tauri IPC Commands 供前端调用
+- 管理 SQLite 数据库(会话、消息、资产、下载记录)
+- 执行 yt-dlp 视频下载与队列管理
+- 处理 VTT 字幕分析与音频提取
+- 管理 AI Agent 运行环境
+- 启动时自动恢复未完成的下载任务
+
+### 1.1 设计原则
+
+- **State Managed**: 通过 Tauri `manage()` 注入共享状态(Database, QueueManager)
+- **Async-First**: 使用 `tokio` 处理 I/O 密集型操作
+- **Event-Driven**: 通过 Tauri Event 向前端推送实时进度
+- **Graceful Degradation**: 数据库初始化失败时回退到内存数据库
+
+---
+
+## 2. 应用入口(lib.rs)
+
+### 2.1 启动流程
+
+```
+1. 初始化 Tauri Builder
+   ├── 注册插件: shell, dialog, fs, store, log
+   └── setup() 回调:
+       ├── 提取 bundled yt-dlp(首次运行)
+       ├── 初始化数据库(三级回退)
+       │   ├── app_data_dir → SQLite
+       │   ├── cwd/contentforge-dev.db → SQLite
+       │   └── :memory: → 内存 SQLite
+       ├── 初始化 QueueManager
+       │   ├── 读取 max_concurrent 设置(默认 3)
+       │   └── 恢复未完成的下载任务
+       └── 注册所有 IPC Commands
+```
+
+### 2.2 Bundled yt-dlp 提取
+
+```rust
+async fn extract_bundled_yt_dlp(app: &tauri::App) -> Option
+```
+
+- 从 Tauri Resource 目录提取平台对应的 yt-dlp 二进制
+- 支持平台: macOS (Intel/Apple Silicon), Linux, Windows (x86/arm64)
+- 使用 marker 文件避免重复提取
+- Unix 系统设置可执行权限 (`0o755`)
+
+### 2.3 数据库初始化回退策略
+
+| 优先级 | 路径 | 说明 |
+|--------|------|------|
+| 1 | `app_data_dir/contentforge.db` | 标准应用数据目录 |
+| 2 | `cwd/contentforge-dev.db` | 开发回退 |
+| 3 | `:memory:` | 内存数据库(数据不持久化) |
+
+---
+
+## 3. 数据库模块(db/)
+
+### 3.1 数据库连接
+
+```rust
+pub struct Database {
+    pub(crate) pool: Pool,
+}
+
+impl Database {
+    pub async fn new_with_path(db_path: &Path) -> Result
+    pub async fn new(app: &tauri::AppHandle) -> Result
+    pub async fn new_in_memory() -> Result
+    pub async fn init(&self) -> Result<(), sqlx::Error>
+}
+```
+
+### 3.2 Schema 定义
+
+#### sessions 表
+
+```sql
+CREATE TABLE IF NOT EXISTS sessions (
+    id TEXT PRIMARY KEY,
+    title TEXT NOT NULL,
+    agent_id TEXT NOT NULL DEFAULT 'general',
+    status TEXT NOT NULL DEFAULT 'active',
+    linked_task_id TEXT,
+    linked_asset_ids TEXT DEFAULT '[]',
+    metadata TEXT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+```
+
+#### messages 表
+
+```sql
+CREATE TABLE IF NOT EXISTS messages (
+    id TEXT PRIMARY KEY,
+    session_id TEXT NOT NULL,
+    role TEXT NOT NULL,           -- user | assistant | system | tool
+    content TEXT NOT NULL DEFAULT '',
+    status TEXT NOT NULL DEFAULT 'completed',
+    model TEXT,
+    tokens_used TEXT,
+    tool_calls TEXT,              -- JSON 数组
+    tool_results TEXT,            -- JSON 数组
+    selected_asset_ids TEXT,
+    error TEXT,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
+);
+```
+
+#### assets 表
+
+```sql
+CREATE TABLE IF NOT EXISTS assets (
+    id TEXT PRIMARY KEY,
+    title TEXT NOT NULL,
+    asset_type TEXT NOT NULL,     -- video | article | tweet | ...
+    status TEXT NOT NULL DEFAULT 'ingested',
+    platform TEXT,
+    url TEXT,
+    file_path TEXT,
+    thumbnail_url TEXT,
+    description TEXT,
+    extracted_text TEXT,
+    summary TEXT,
+    transcript TEXT,
+    translated_text TEXT,
+    rewritten_text TEXT,
+    duration_sec REAL,
+    analysis TEXT,                -- JSON
+    tags TEXT DEFAULT '[]',
+    pipeline_id TEXT,
+    author TEXT,
+    published_at TEXT,
+    engagement TEXT,              -- JSON
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+```
+
+#### downloads 表
+
+```sql
+CREATE TABLE IF NOT EXISTS downloads (
+    id TEXT PRIMARY KEY,
+    url TEXT NOT NULL,
+    title TEXT,
+    status TEXT NOT NULL DEFAULT 'pending',
+    progress REAL DEFAULT 0.0,
+    speed TEXT,
+    eta TEXT,
+    output_dir TEXT,
+    filename TEXT,
+    subtitles TEXT DEFAULT '[]',
+    error TEXT,
+    queue_position INTEGER DEFAULT 0,
+    options TEXT,                 -- JSON: StartDownloadRequest 序列化
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+```
+
+#### vtt_reports 表
+
+```sql
+CREATE TABLE IF NOT EXISTS vtt_reports (
+    id TEXT PRIMARY KEY,
+    youtube_url TEXT NOT NULL,
+    video_id TEXT,
+    title TEXT,
+    language TEXT,
+    content TEXT NOT NULL DEFAULT '',
+    cue_count INTEGER DEFAULT 0,
+    duration_sec REAL,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    status TEXT NOT NULL DEFAULT 'pending',
+    error TEXT
+);
+```
+
+#### 其他表
+
+| 表名 | 用途 |
+|------|------|
+| `settings` | 键值对设置存储 |
+| `agent_switches` | Agent 切换历史记录 |
+| `pipeline_runs` | 流水线执行记录 |
+
+### 3.3 索引
+
+```sql
+CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
+CREATE INDEX IF NOT EXISTS idx_assets_type ON assets(asset_type);
+CREATE INDEX IF NOT EXISTS idx_assets_status ON assets(status);
+CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
+```
+
+---
+
+## 4. 下载器模块(downloader.rs)
+
+### 4.1 核心类型
+
+```rust
+#[derive(Debug, Clone, Serialize, Default)]
+pub struct DownloadOptions {
+    pub url: String,
+    pub is_playlist: bool,
+    pub quality: Option,        // e.g., "720", "1080", "best"
+    pub format: Option,         // e.g., "mp4", "mkv"
+    pub output_dir: Option,
+    pub sub_langs: Option>,
+    pub write_subs: bool,
+    pub write_auto_subs: bool,
+    pub start_time: Option,     // HH:MM:SS
+    pub end_time: Option,       // HH:MM:SS
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct DownloadProgress {
+    pub video_id: Option,
+    pub title: Option,
+    pub percent: f64,                   // 0.0 - 100.0
+    pub speed: Option,
+    pub eta: Option,
+    pub status: String,                 // pending | downloading | completed | failed | cancelled
+    pub error: Option,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct DownloadLog {
+    pub level: String,                  // info | error | warn
+    pub message: String,
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub struct DownloadOutput {
+    pub title: String,
+    pub filename: String,
+    pub subtitles: Vec,
+}
+```
+
+### 4.2 Downloader 结构
+
+```rust
+pub struct Downloader {
+    options: DownloadOptions,
+    _download_id: String,
+    yt_dlp_path: Option,
+}
+
+impl Downloader {
+    pub fn new(options: DownloadOptions, download_id: String) -> Self
+    pub fn with_yt_dlp_path(mut self, path: Option) -> Self
+    
+    // 核心下载方法
+    pub async fn download(
+        &self,
+        mut on_progress: F,
+        mut on_log: G,
+        cancel_rx: &mut tokio::sync::mpsc::Receiver<()>,
+    ) -> Result
+    where F: FnMut(DownloadProgress), G: FnMut(DownloadLog)
+    
+    // 视频信息查询
+    pub async fn get_info(&self, url: &str) -> Result
+    pub async fn get_formats(&self, url: &str) -> Result, String>
+    pub async fn get_playlist_info(&self, url: &str) -> Result
+}
+```
+
+### 4.3 yt-dlp 参数构建
+
+| DownloadOptions 字段 | yt-dlp 参数 |
+|---------------------|-------------|
+| `quality` | `-f bestvideo[height<={quality}]+bestaudio/best[height<={quality}]` |
+| `format` | `--merge-output-format {format}` |
+| `output_dir` | `-o {output_dir}/%(title)s.%(ext)s` |
+| `write_subs` | `--write-subs` |
+| `write_auto_subs` | `--write-auto-subs` |
+| `sub_langs` | `--sub-langs {langs}` |
+| `start_time` / `end_time` | `--download-sections *{start}-{end} --force-keyframes-at-cuts` |
+| `!is_playlist` | `--no-playlist` |
+
+### 4.4 进度解析
+
+使用正则表达式解析 yt-dlp 进度输出:
+
+```rust
+regex::Regex::new(r"\[download\]\s+([\d.]+)%.*?at\s+(\S+)\s+ETA\s+(\S+)")
+```
+
+匹配组:
+1. 百分比 (`67.3`)
+2. 速度 (`1.5MiB/s`)
+3. ETA (`00:02:15`)
+
+### 4.5 yt-dlp 查找策略
+
+查找顺序:
+1. `self.yt_dlp_path`(显式指定)
+2. `YT_DLP_BIN` 环境变量
+3. `VYTLD_BUNDLED_YT_DLP` 环境变量(Tauri 启动时设置)
+4. 系统 PATH (`which yt-dlp` / `where yt-dlp`)
+5. 常见安装路径(Homebrew、Chocolatey、WinGet、pip)
+
+### 4.6 代理清理
+
+启动 yt-dlp 前自动移除非标准代理环境变量:
+
+```rust
+const ALLOWED_PROXY_VARS: &[&str] = &[
+    "HTTP_PROXY", "http_proxy",
+    "HTTPS_PROXY", "https_proxy",
+    "ALL_PROXY", "all_proxy",
+    "NO_PROXY", "no_proxy",
+    "FTP_PROXY", "ftp_proxy",
+];
+```
+
+---
+
+## 5. 队列管理器(queue.rs)
+
+### 5.1 架构
+
+```
+QueueManager (前端句柄)
+    └── mpsc::Sender
+        └── run_queue() (后台 tokio task)
+            ├── pending: VecDeque
+            ├── active: HashMap
+            └── cancel_txs: HashMap>
+```
+
+### 5.2 QueueCommand 枚举
+
+```rust
+pub enum QueueCommand {
+    Enqueue { id, options, yt_dlp_path, app },
+    Cancel { id },
+    Finished { id },
+    SetMaxConcurrent { max: usize },
+}
+```
+
+### 5.3 QueueManager API
+
+```rust
+impl QueueManager {
+    pub fn new(db: Database, max_concurrent: usize) -> Self
+    pub async fn enqueue(&self, id, options, yt_dlp_path, app)
+    pub async fn cancel(&self, id)
+    pub async fn notify_finished(&self, id)
+    pub async fn set_max_concurrent(&self, max)
+}
+```
+
+### 5.4 调度逻辑
+
+```rust
+async fn try_start_pending(active, pending, cancel_txs, db, max_concurrent, app)
+```
+
+- 当 `active.len() < max_concurrent` 时,从 pending 队列取出任务启动
+- 更新数据库状态为 `downloading`,清除 queue_position
+- 发射 Tauri Event: `download:status:{id}`
+- 创建独立的 `tokio::spawn` 任务执行下载
+- 每个任务拥有独立的 cancel channel
+
+### 5.5 下载任务生命周期
+
+```
+pending → downloading → completed
+                  ↘→ failed
+                  ↘→ cancelled
+```
+
+### 5.6 并发控制
+
+- 默认并发数: 3
+- 最大值: 10(通过 `SetMaxConcurrent` 限制)
+- 最小值: 1
+
+---
+
+## 6. IPC Commands
+
+### 6.1 Chat Commands
+
+| Command | 参数 | 返回 |
+|---------|------|------|
+| `get_chat_sessions` | - | `{ sessions: ChatSession[] }` |
+| `create_chat_session` | `{ sessionId, agentId, title }` | `{ session: ChatSession }` |
+| `get_chat_history` | `{ sessionId, cursor? }` | `{ messages, hasMore, nextCursor? }` |
+| `chat_send` | `{ sessionId, message, agentId?, selectedAssetIds?, streaming? }` | `{ messageId, status }` |
+| `cancel_chat_stream` | `{ messageId }` | - |
+| `archive_chat_session` | `{ sessionId }` | - |
+| `pin_chat_session` | `{ sessionId }` | - |
+| `update_chat_session_title` | `{ sessionId, title }` | - |
+| `delete_chat_session` | `{ sessionId }` | - |
+| `chat_retry` | `{ sessionId, messageId, message, selectedAssetIds? }` | - |
+| `delete_chat_message` | `{ sessionId, messageId }` | - |
+| `confirm_tool_call` | `{ messageId, callId, approved }` | - |
+
+### 6.2 Agent Commands
+
+| Command | 参数 | 返回 |
+|---------|------|------|
+| `get_agents` | - | `{ agents: AgentRole[] }` |
+| `switch_agent` | `{ fromAgentId, toAgentId, triggeredBy, reason? }` | - |
+| `get_quick_actions` | - | `{ actions: AgentQuickAction[] }` |
+| `get_skills` | - | `{ skills: SkillDefinition[] }` |
+| `execute_skill` | `{ skillId, params }` | `SkillExecutionResult` |
+
+### 6.3 Asset Commands
+
+| Command | 参数 | 返回 |
+|---------|------|------|
+| `search_assets` | `{ query?, type?, status?, tags?, limit?, offset? }` | `{ assets, total }` |
+| `get_asset_detail` | `{ assetId }` | `AssetDetail` |
+| `delete_asset` | `{ assetId }` | - |
+| `update_asset_tags` | `{ assetId, tags }` | - |
+| `add_asset_to_session` | `{ assetId, sessionId }` | - |
+| `get_asset_groups` | - | `{ groups }` |
+
+### 6.4 Download Commands(来自 vYtDL)
+
+| Command | 参数 | 返回 |
+|---------|------|------|
+| `start_download` | `StartDownloadRequest` | `{ downloadId }` |
+| `cancel_download` | `{ downloadId }` | - |
+| `get_downloads` | `{ status?, limit?, offset? }` | `{ downloads }` |
+| `get_download_by_id` | `{ downloadId }` | `DownloadRecord` |
+| `delete_download` | `{ downloadId }` | - |
+| `open_download_folder` | `{ downloadId? }` | - |
+| `retry_download` | `{ downloadId }` | - |
+
+### 6.5 Video Commands(来自 vYtDL)
+
+| Command | 参数 | 返回 |
+|---------|------|------|
+| `get_video_info` | `{ url }` | `VideoInfo` |
+| `get_video_formats` | `{ url }` | `{ formats: FormatInfo[] }` |
+| `get_playlist_info` | `{ url }` | `PlaylistInfo` |
+
+### 6.6 AI / VTT Commands(来自 vYtDL)
+
+| Command | 参数 | 返回 |
+|---------|------|------|
+| `summarize_video` | `{ youtubeUrl, language? }` | `{ reportId }` |
+| `extract_audio` | `{ youtubeUrl, outputFormat? }` | `{ filePath }` |
+| `analyze_vtt` | `{ youtubeUrl, language? }` | `{ reportId }` |
+| `get_vtt_report` | `{ reportId }` | `VTTReport` |
+| `list_vtt_reports` | `{ limit?, offset? }` | `{ reports }` |
+| `delete_vtt_report` | `{ reportId }` | - |
+| `agent_chat_send` | `{ sessionId, message, agentId? }` | StreamEvent |
+| `detect_agent_cli` | - | `{ detected: bool, path?: string }` |
+
+---
+
+## 7. 事件系统
+
+### 7.1 Download Events
+
+| Event 名称 | Payload | 触发时机 |
+|-----------|---------|----------|
+| `download:status:{id}` | `"downloading"` | 状态变更 |
+| `download:progress:{id}` | `DownloadProgress` | 进度更新 |
+| `download:log:{id}` | `DownloadLog` | 日志输出 |
+| `download:complete:{id}` | `ApiResponse` | 下载完成 |
+| `download:error:{id}` | `ApiResponse<()>` | 下载失败 |
+
+### 7.2 Chat Events
+
+| Event 名称 | Payload | 触发时机 |
+|-----------|---------|----------|
+| `message.delta` | `{ messageId, delta }` | 流式消息增量 |
+| `message.completed` | `{ messageId }` | 消息完成 |
+| `message.failed` | `{ messageId, error }` | 消息失败 |
+| `tool.call.start` | `ToolCallStartPayload` | 工具调用开始 |
+| `tool.call.progress` | `ToolCallProgressPayload` | 工具调用进度 |
+| `tool.call.completed` | `ToolCallCompletedPayload` | 工具调用完成 |
+| `tool.call.failed` | `{ messageId, callId, error }` | 工具调用失败 |
+| `agent.switched` | `AgentSwitchedPayload` | Agent 切换 |
+
+---
+
+## 8. 自动恢复机制
+
+### 8.1 启动恢复流程
+
+```rust
+// lib.rs setup()
+let incomplete = db.get_incomplete_downloads().await?;
+for record in incomplete {
+    // 1. 解析保存的 options JSON
+    let options = serde_json::from_str::(&record.options)
+        .map(|req| DownloadOptions { ... })
+        .unwrap_or_else(|| DownloadOptions { url: record.url, ... });
+    
+    // 2. 重置状态为 pending
+    db.update_download_status(&record.id, DownloadStatus::Pending).await?;
+    
+    // 3. 重新入队
+    queue_manager.enqueue(record.id, options, None, app_handle.clone()).await;
+}
+```
+
+### 8.2 恢复状态映射
+
+| 上次状态 | 恢复后状态 |
+|----------|-----------|
+| `downloading` | `pending`(重置后重新入队) |
+| `pending` | `pending`(已在队列中) |
+
+---
+
+## 9. 依赖清单
+
+```toml
+[dependencies]
+serde_json = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+log = "0.4"
+tauri = { version = "2.10.3", features = [] }
+tauri-plugin-log = "2"
+tauri-plugin-shell = "2"
+tauri-plugin-dialog = "2"
+tauri-plugin-fs = "2"
+tauri-plugin-store = "2"
+tokio = { version = "1", features = ["full"] }
+uuid = { version = "1", features = ["v4", "serde"] }
+chrono = { version = "0.4", features = ["serde"] }
+regex = "1"
+sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "chrono", "macros"] }
+dirs = "5"
+glob = "0.3"
+opener = "0.6"
+shellexpand = "3"
+```
+
+---
+
+## 10. 文件清单
+
+```
+desktop/src-tauri/src/
+├── main.rs              # 入口(仅调用 lib::run())
+├── lib.rs               # 应用初始化、setup、IPC 注册
+│
+├── commands/            # IPC Commands 子模块
+│   ├── mod.rs           # 命令聚合、ApiResponse 类型
+│   ├── chat.rs          # Chat 相关命令
+│   ├── agent.rs         # Agent 相关命令
+│   ├── asset.rs         # Asset 相关命令
+│   ├── settings.rs      # 设置命令
+│   ├── download.rs      # 下载命令
+│   ├── video.rs         # 视频信息命令
+│   └── ai.rs            # AI / VTT 命令
+│
+├── db/                  # 数据库模块
+│   ├── mod.rs           # Database 结构、初始化
+│   ├── types.rs         # 数据库类型定义
+│   ├── sessions.rs      # 会话 CRUD
+│   ├── messages.rs      # 消息 CRUD
+│   ├── assets.rs        # 资产 CRUD
+│   ├── downloads.rs     # 下载记录 CRUD
+│   ├── settings.rs      # 设置 CRUD
+│   ├── agent_switches.rs# Agent 切换历史
+│   └── pipeline_runs.rs # 流水线执行记录
+│
+├── downloader.rs        # yt-dlp 下载器封装
+├── queue.rs             # 下载队列管理器
+├── vtt_analysis.rs      # VTT 字幕分析
+├── audio_extractor.rs   # 音频提取
+├── agent_cli.rs         # AI Agent CLI 封装
+├── agent_runner.rs      # Agent 运行环境
+└── pipeline.rs          # Rust 端流水线执行
+```
diff --git a/contentforge/docs/spec/VIZPLAINER_INTEGRATION.md b/contentforge/docs/spec/VIZPLAINER_INTEGRATION.md
new file mode 100644
index 0000000..2c09887
--- /dev/null
+++ b/contentforge/docs/spec/VIZPLAINER_INTEGRATION.md
@@ -0,0 +1,314 @@
+# Vizplainer 模式分析 & ContentForge 整合方案
+
+> 来源: https://www.vizplainer.com/  
+> 分析日期: 2026-07-24
+
+---
+
+## 一、Vizplainer 是什么
+
+Vizplainer 是一个 **AI Explainer Video Generator** —— 自动生成"解说型视频"的工具。
+
+### 1.1 核心模式
+
+| 环节 | 说明 | Vizplainer 实现 |
+|------|------|----------------|
+| **输入** | 财报/新闻/数据报告 | Alphabet Q3 2025 Earnings Report |
+| **信息挖掘** | AI 深度阅读 + 社区情感聚合 | Reddit, Hacker News, X 评论追踪 |
+| **结构化叙事** | 提炼 3 个关键要点 + 总结 | "Three specific things..." |
+| **观点对比** | 多方视角呈现 | Bull vs Bear 观点对比 |
+| **输出** | 视频解说(脚本 + 字幕 + 配音) | 自动生成视频 |
+
+### 1.2 内容结构拆解(以 Alphabet 分析为例)
+
+```
+开场 Hook
+  └── "The Google 'death spiral' narrative is officially dead."
+      └── 震撼数据: "first-ever $100 billion quarter"
+
+要点 1: AI Infrastructure
+  ├── 核心数据: Gemini 7B tokens/minute
+  ├── 社区观点: HN 讨论 "experimental AI → enterprise utility"
+  └── 结论: "enterprise migration to GCP is actually happening"
+
+要点 2: Spending Controversy
+  ├── 核心数据: $91-93B CapEx guidance
+  ├── 社区观点: Reddit r/investing 多空分歧
+  └── 结论: "capital-efficient monster, 34% operating margin"
+
+要点 3: Regulatory Fine
+  ├── 核心数据: $3.5B EU antitrust fine
+  ├── 社区观点: "cost of doing business"
+  └── 结论: "can absorb multi-billion hit, Net Income +33%"
+
+总结 Takeaway
+  ├── Search 未死: double-digit growth
+  ├── Gemini: 650M MAU
+  └── 结论: "insurmountable moat"
+
+互动结尾
+  └── "Is $93B sustainable, or is this a bubble?"
+```
+
+### 1.3 独特价值
+
+1. **社区情感聚合** — 不只报告数据,还聚合 Reddit/HN/X 的社区讨论
+2. **多空视角平衡** — 每个要点都呈现正反双方观点
+3. **数据驱动的叙事** — 每个观点都有具体数字支撑
+4. **视频原生输出** — 不是文章,是可直接消费的视频内容
+
+---
+
+## 二、与 ContentForge 的契合度分析
+
+### 2.1 能力映射
+
+| Vizplainer 能力 | ContentForge 现有模块 | 匹配度 | 差距 |
+|----------------|----------------------|--------|------|
+| 内容采集(财报/新闻) | `ingestion/` — WebScraper, AgentReach | ✅ | 需增加财报专用解析 |
+| 社区情感聚合 | **未实现** | ❌ | 需要新增 Reddit/HN/X API 采集 |
+| AI 深度分析 | `processing/ai_engine.py` | ✅ | 已有 AIEngine,需增加分析 Prompt |
+| 结构化叙事 | `processing/summarizer.py` | ⚠️ | 摘要≠叙事,需新增 Narrative Builder |
+| 观点对比 | **未实现** | ❌ | 需新增 Perspective Comparator |
+| 视频生成 | `processing/video_generator.py` | ❌ | 完全缺失 |
+| 字幕生成 | `processing/transcriber.py` | ⚠️ | 有转录,无字幕生成 |
+| 发布到社交平台 | `publishing/` | ✅ | 小红书/JSON/Markdown 已支持 |
+
+### 2.2 整合价值
+
+Vizplainer 模式可以极大增强 ContentForge 的以下场景:
+
+| 场景 | 说明 |
+|------|------|
+| **财报速览** | 下载财报 PDF → AI 分析 → 生成 3 分钟解说视频 |
+| **热点追踪** | 抓取 Twitter/X 热点 → 社区情感分析 → 生成观点对比视频 |
+| **竞品分析** | 采集竞品动态 → 多方视角对比 → 生成分析视频 |
+| **Research 转内容** | 阅读论文/报告 → 提炼要点 → 生成科普视频 |
+
+---
+
+## 三、ContentForge 整合方案
+
+### 3.1 新增 Pipeline: `vizplainer`
+
+```yaml
+# pipeline/presets.py 新增
+PipelinePreset(
+    id="vizplainer",
+    name="Vizplainer 视频生成",
+    description="采集内容 → 社区情感 → AI 分析 → 叙事构建 → 视频生成",
+    steps=[
+        # Step 1: 内容采集
+        {
+            "handler": "IngestionHandler",
+            "config": {"mode": "deep_read", "extract_tables": True}
+        },
+        # Step 2: 社区情感采集(新增)
+        {
+            "handler": "CommunitySentimentHandler",
+            "config": {
+                "sources": ["reddit", "hackernews", "x"],
+                "query_template": "{company_name} {topic}",
+                "max_posts_per_source": 50
+            }
+        },
+        # Step 3: AI 深度分析
+        {
+            "handler": "AnalysisHandler",
+            "config": {"mode": "deep", "output_format": "structured_json"}
+        },
+        # Step 4: 叙事构建(新增)
+        {
+            "handler": "NarrativeBuilderHandler",
+            "config": {
+                "style": "vizplainer",  # 3-point + takeaway 结构
+                "tone": "professional_but_conversational",
+                "include_perspectives": True  # 多空视角
+            }
+        },
+        # Step 5: 视频生成(新增)
+        {
+            "handler": "VideoGenerationHandler",
+            "config": {
+                "format": "vertical_1080p",  # 9:16 短视频
+                "duration_target": "3m",
+                "style": "subtitle_overlay",  # 字幕叠加风格
+                "voice": "auto_select"
+            }
+        },
+        # Step 6: 发布
+        {
+            "handler": "PublishHandler",
+            "config": {"formats": ["mp4", "xhs", "twitter"]}  # 视频 + 文案
+        }
+    ]
+)
+```
+
+### 3.2 新增核心模块
+
+#### 3.2.1 Community Sentiment Collector
+
+```python
+# contentforge/ingestion/community_sentiment.py(新增)
+
+class CommunitySentimentCollector:
+    """社区情感采集器 — 聚合 Reddit, Hacker News, X(Twitter) 的讨论情感"""
+    
+    SOURCES = {
+        "reddit": RedditAPI(),
+        "hackernews": HNAlgoliaAPI(),
+        "x": XAPI(),  # 或第三方服务
+    }
+    
+    def collect(self, query: str, sources: List[str], max_posts: int = 50) -> SentimentReport:
+        """
+        采集并分析社区情感
+        
+        Returns:
+            SentimentReport {
+                overall_sentiment: "positive" | "mixed" | "negative",
+                key_threads: List[ThreadSummary],  # 热门讨论串
+                bull_points: List[str],  # 看多观点
+                bear_points: List[str],  # 看空观点
+                notable_quotes: List[Quote],  # 经典引用
+                engagement_metrics: Dict,  # 互动数据
+            }
+        """
+```
+
+#### 3.2.2 Narrative Builder
+
+```python
+# contentforge/processing/narrative_builder.py(新增)
+
+class NarrativeBuilder:
+    """叙事构建器 — 将分析结果转化为结构化解说脚本"""
+    
+    TEMPLATES = {
+        "vizplainer": VizplainerTemplate(),  # 3-point + takeaway
+        "executive_summary": ExecutiveSummaryTemplate(),  # 执行摘要
+        "deep_dive": DeepDiveTemplate(),  # 深度分析
+    }
+    
+    def build(self, analysis: AnalysisResult, template: str = "vizplainer") -> NarrativeScript:
+        """
+        构建叙事脚本
+        
+        Returns:
+            NarrativeScript {
+                hook: str,  # 开场 Hook
+                key_points: List[KeyPoint],  # 核心要点(3个)
+                perspectives: List[Perspective],  # 多方观点
+                takeaway: str,  # 总结
+                call_to_action: str,  # 互动结尾
+                scenes: List[Scene],  # 分镜脚本(时间轴)
+            }
+        """
+```
+
+#### 3.2.3 Video Generator
+
+```python
+# contentforge/processing/video_generator.py(新增)
+
+class VideoGenerator:
+    """视频生成器 — 将叙事脚本转化为视频"""
+    
+    def generate(self, script: NarrativeScript, config: VideoConfig) -> VideoOutput:
+        """
+        生成视频
+        
+        实现方案(可选):
+        A. 集成 Manim(数学动画)— 适合数据可视化
+        B. 集成 Remotion(React 视频)— 适合字幕叠加风格
+        C. 集成 moviepy(Python 视频编辑)— 轻量级方案
+        D. 调用外部 API(如 HeyGen, D-ID, Synthesia)— SaaS 方案
+        
+        推荐: 方案 C (moviepy) + 方案 D (SaaS) 作为备选
+        """
+```
+
+### 3.3 前端 UI 扩展
+
+#### 新增页面: `app/workflows/vizplainer/page.tsx`
+
+```
+Vizplainer 工作流页面:
+┌─────────────────────────────────────────┐
+│  📊 Vizplainer 视频生成                  │
+├─────────────────────────────────────────┤
+│  输入 URL / 上传文件                      │
+│  [https://...] [📁 上传 PDF]             │
+├─────────────────────────────────────────┤
+│  配置选项                                │
+│  ☑ 采集社区情感 (Reddit/HN/X)           │
+│  ☑ 包含多空观点对比                      │
+│  ☑ 生成视频                             │
+│  视频风格: [字幕叠加 ▼] [3分钟 ▼]       │
+├─────────────────────────────────────────┤
+│  [🚀 开始生成]                          │
+├─────────────────────────────────────────┤
+│  生成进度                                │
+│  [████████░░░░░░] 步骤 3/6: 叙事构建    │
+├─────────────────────────────────────────┤
+│  预览 & 发布                             │
+│  [▶ 预览视频] [📋 复制脚本] [📤 发布]  │
+└─────────────────────────────────────────┘
+```
+
+---
+
+## 四、实施优先级
+
+| 优先级 | 模块 | 工作量 | 依赖 |
+|--------|------|--------|------|
+| P0 | Narrative Builder(叙事构建) | 2-3 天 | AIEngine |
+| P0 | Vizplainer Pipeline 预设 | 1 天 | Narrative Builder |
+| P1 | Community Sentiment(Reddit/HN) | 3-5 天 | 需 API key |
+| P1 | 前端 Vizplainer 工作流页面 | 2-3 天 | Pipeline API |
+| P2 | Video Generator(moviepy 基础版) | 5-7 天 | 叙事脚本 |
+| P2 | 字幕自动生成 | 2-3 天 | Video Generator |
+| P3 | X/Twitter 情感采集 | 2-3 天 | 需 API 权限 |
+| P3 | 高级视频风格(Manim/Remotion) | 7-10 天 | 视频基础设施 |
+
+---
+
+## 五、与现有 SPEC 的关联
+
+| 本文档模块 | 对应 SPEC 文件 | 关联章节 |
+|-----------|---------------|---------|
+| Pipeline 预设 | `PYTHON_CORE_SPEC.md` | 5. 流水线模块 |
+| AI 深度分析 | `PYTHON_CORE_SPEC.md` | 4. AI 模块 — AIEngine |
+| Community Sentiment | `PYTHON_CORE_SPEC.md` | 3. 采集模块(扩展) |
+| Video Generator | `PYTHON_CORE_SPEC.md` | 新增章节(待补充) |
+| Narrative Builder | `PYTHON_CORE_SPEC.md` | 4. 处理模块(扩展) |
+| 前端工作流页面 | `FRONTEND_SPEC.md` | 8. 页面组件(扩展) |
+| Pipeline 执行 | `RUST_BACKEND_SPEC.md` | 6. 核心服务 — Pipeline Framework |
+
+---
+
+## 六、参考实现
+
+### 6.1 类似开源项目
+
+| 项目 | 链接 | 参考价值 |
+|------|------|---------|
+| **NewsWithJokes** | AI 新闻解说 | 叙事风格参考 |
+| **Manim** | https://github.com/3b1b/manim | 数学/数据动画 |
+| **Remotion** | https://www.remotion.dev/ | React 视频生成 |
+| **moviepy** | https://github.com/Zulko/moviepy | Python 视频编辑 |
+
+### 6.2 Vizplainer 技术栈推测
+
+基于内容特征分析,Vizplainer 可能使用:
+
+- **文本生成**: GPT-4 / Claude(叙事脚本)
+- **社区采集**: Reddit API + HN Algolia API + X API
+- **语音合成**: ElevenLabs / OpenAI TTS
+- **视频合成**: 可能是 Remotion / 自定义 FFmpeg 管线
+- **字幕**: Whisper(转录)+ 自定义字幕渲染
+
+---
+
+*本文档作为 ContentForge 扩展 Vizplainer 模式的可行性分析和实施蓝图。*
diff --git a/contentforge/docs/vizplainer-slides/design.md b/contentforge/docs/vizplainer-slides/design.md
new file mode 100644
index 0000000..0e6be31
--- /dev/null
+++ b/contentforge/docs/vizplainer-slides/design.md
@@ -0,0 +1,100 @@
+# Design Document
+
+## Visual Direction
+
+- **Style**: Dark-mode financial tech, inspired by Bloomberg Terminal and modern fintech dashboards
+- **Mood**: Professional, data-driven, confident — matching the "death spiral is dead" narrative tone
+- **Density**: Medium-high, each page focuses on one key insight with supporting data points
+
+## Color System
+
+```yaml
+colors:
+  primary: "#4285F4"        # Google Blue — brand anchor
+  secondary: "#34A853"      # Google Green — growth/positive
+  accent: "#EA4335"         # Google Red — alerts/bear views
+  highlight: "#FBBC05"      # Google Yellow — highlights/callouts
+  background: "#0A0E1A"     # Deep navy black — main background
+  surface: "#141B2D"        # Elevated surface — cards/sections
+  surfaceLight: "#1E2746"   # Lighter surface — secondary cards
+  text: "#E8ECF1"           # Primary text — almost white
+  textMuted: "#8B95A5"      # Secondary text — muted gray
+  border: "#2A3550"         # Borders and dividers
+```
+
+## Typography
+
+```yaml
+textStyles:
+  heroTitle:
+    fontSize: 56
+    color: "$text"
+    fontFamily: "MiSans"
+    letterSpacing: -1
+  title:
+    fontSize: 40
+    color: "$text"
+    fontFamily: "MiSans"
+    letterSpacing: -0.5
+  subtitle:
+    fontSize: 24
+    color: "$textMuted"
+    fontFamily: "MiSans"
+    lineHeight: 1.4
+  body:
+    fontSize: 18
+    color: "$text"
+    fontFamily: "MiSans"
+    lineHeight: 1.6
+  caption:
+    fontSize: 14
+    color: "$textMuted"
+    fontFamily: "MiSans"
+    lineHeight: 1.4
+  dataNumber:
+    fontSize: 48
+    color: "$primary"
+    fontFamily: "MiSans"
+    letterSpacing: -1
+  label:
+    fontSize: 12
+    color: "$textMuted"
+    fontFamily: "MiSans"
+    letterSpacing: 1
+```
+
+## Layout Principles
+
+- **Page size**: 1280 x 720 (16:9)
+- **Margins**: 60px left/right, 50px top/bottom
+- **Grid**: Content area = 1160 x 620
+- **Cards**: Rounded rectangles (roundRect, adjustments: [8000]), surface color, subtle border
+- **Data emphasis**: Large numbers with primary/accent color, supporting text muted
+- **Icons**: Font Awesome solid icons in accent colors for visual anchors
+- **Dividers**: Thin horizontal lines (1px, $border color) between sections
+
+## Page-Specific Design Notes
+
+### Cover (Page 1)
+- Full-bleed dark background with subtle gradient overlay
+- Large hero title centered, with Google brand colors subtly integrated
+- Subtitle below with muted text
+- Bottom: "Community-Powered Analysis" badge
+
+### Chapter Pages (Pages 3, 5, 9)
+- Large chapter number in primary color (01, 02, 03)
+- Chapter title in heroTitle style
+- Subtitle below with brief context
+- Decorative accent line (4px height, primary color, short width)
+
+### Content Pages (Pages 4, 6, 7, 8, 10, 11)
+- Top: Page title with left accent bar (4px wide, primary color)
+- Body: Card-based layout for data points
+- Key numbers displayed prominently (dataNumber style)
+- Supporting text in body style
+- Source attribution in caption style at bottom
+
+### Final Page (Page 12)
+- Centered question in heroTitle style
+- Subtle call-to-action below
+- Clean, minimal design with generous whitespace
diff --git a/contentforge/docs/vizplainer-slides/outline.md b/contentforge/docs/vizplainer-slides/outline.md
new file mode 100644
index 0000000..65abf69
--- /dev/null
+++ b/contentforge/docs/vizplainer-slides/outline.md
@@ -0,0 +1,49 @@
+# Presentation Outline
+
+## Page 1 [cover]
+- **Title**: Alphabet Q3 2025 Earnings Deep Dive
+- **Content**: The "Death Spiral" Narrative Is Officially Dead — A Community-Powered Analysis
+
+## Page 2 [table_of_contents]
+- **Title**: Executive Overview
+- **Content**: 1. Financial Snapshot; 2. Three Core Insights; 3. Community Sentiment; 4. Bottom Line
+
+## Page 3 [chapter]
+- **Title**: 01 Financial Snapshot
+- **Content**: First-ever $100B quarter and what the headline numbers tell us
+
+## Page 4 [content]
+- **Title**: Q3 2025 Key Metrics
+- **Content**: From vizplainer.com analysis. Alphabet posted $102B revenue (+16% YoY), the first $100B quarter in history. Net Income grew 33% despite a $3.5B EU antitrust fine. Operating margin hit 34% excluding the fine. Google Cloud revenue surged 34% to $15B+. These numbers collectively refute the "Google is dying" narrative.
+
+## Page 5 [chapter]
+- **Title**: 02 Three Core Insights
+- **Content**: What the community is obsessing over — beyond the headline numbers
+
+## Page 6 [content]
+- **Title**: Insight 1: AI Infrastructure at Scale
+- **Content**: From vizplainer.com analysis. Gemini now processes 7 billion tokens per minute. Hacker News discussions validate the shift from "experimental AI" to "actual enterprise utility". Google Cloud revenue jumped 34% to $15B+, proving the enterprise migration to GCP for generative AI is happening, not just forecasted. This is the "valuable found" for anyone tracking the tech stack.
+
+## Page 7 [content]
+- **Title**: Insight 2: The $93B CapEx Arms Race
+- **Content**: From vizplainer.com analysis. Alphabet expects 2025 CapEx of $91-93B. Reddit r/investing sentiment is mixed — some call it a terrifying "arms race" to keep up with OpenAI and Meta. The counter-argument: Google has become a capital-efficient monster. Even with massive spend and excluding the $3.5B EU fine, operating margins sit at nearly 34%.
+
+## Page 8 [content]
+- **Title**: Insight 3: The $3.5B Fine as "Cost of Business"
+- **Content**: From vizplainer.com analysis. The European Commission's $3.5B antitrust fine would have tanked the stock two years ago. Today, discussion threads treat it as "cost of doing business." The ability to absorb a multi-billion dollar hit and still grow Net Income by 33% demonstrates Alphabet's financial resilience and market dominance.
+
+## Page 9 [chapter]
+- **Title**: 03 Community Sentiment
+- **Content**: What Reddit, Hacker News, and X are saying
+
+## Page 10 [content]
+- **Title**: Bull vs Bear: The Community Divide
+- **Content**: From vizplainer.com analysis. Bulls argue: 650M Gemini MAU, search revenue up double digits, AI-first transition without breaking the money printer, "insurmountable moat" via infrastructure. Bears counter: $93B CapEx unsustainable, regulatory overhang, competition from OpenAI and Meta, potential search disruption. The key question: Is this infrastructure spend buying a moat or inflating a bubble?
+
+## Page 11 [content]
+- **Title**: The Bottom Line
+- **Content**: From vizplainer.com analysis. The fear that search would vanish overnight due to chatbots hasn't materialized. "Search & Other" revenue is up double digits. 650M monthly active users on the Gemini App. Alphabet is successfully managing the transition from traditional search to an AI-first model. For builders: the infrastructure is scaling fast. For investors: the CapEx is hefty, but it looks like it's buying an insurmountable moat.
+
+## Page 12 [final]
+- **Title**: The Big Question
+- **Content**: Is $93 billion in infrastructure spending sustainable, or is this a bubble? — Let us know what you think. Analysis powered by community insights from Reddit, Hacker News, and X.
diff --git a/contentforge/docs/vizplainer-slides/pages/bullbear.page b/contentforge/docs/vizplainer-slides/pages/bullbear.page
new file mode 100644
index 0000000..e206699
--- /dev/null
+++ b/contentforge/docs/vizplainer-slides/pages/bullbear.page
@@ -0,0 +1,171 @@
+pageType: content
+background:
+  type: solid
+  color: "$background"
+elements:
+  - elementId: title-bar
+    elementType: shape
+    shapeName: rect
+    bounds: [60, 40, 4, 40]
+    fill:
+      type: solid
+      color: "$primary"
+  - elementId: page-title
+    elementType: text
+    bounds: [80, 35, 800, 50]
+    content:
+      style: "$title"
+      fontSize: 32
+      align: [left, middle]
+      wrap: false
+      text: |
+        

Bull vs Bear: The Community Divide

+ - elementId: bull-card-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 110, 555, 400] + fill: + type: solid + color: "#34A85310" + border: + style: solid + width: 1 + color: "#34A85340" + - elementId: bull-header-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 110, 555, 50] + fill: + type: solid + color: "#34A85330" + - elementId: bull-icon + elementType: icon + bounds: [80, 122, 24, 24] + iconName: "fas:arrow-trend-up" + fill: + type: solid + color: "$secondary" + - elementId: bull-title + elementType: text + bounds: [115, 115, 200, 36] + content: + fontSize: 22 + color: "$secondary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

BULL CASE

+ - elementId: bull-content + elementType: text + bounds: [80, 180, 520, 310] + content: + fontSize: 17 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.7 + align: [left, top] + text: | +

650M Gemini MAU — massive user adoption

+

Search revenue up double digits — chatbots didn't kill search

+

34% operating margin — capital efficiency maintained

+

Cloud +34% — enterprise AI migration real

+

$102B quarter — first in history

+

"Insurmountable moat"

+

via infrastructure scale

+ - elementId: bear-card-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [645, 110, 575, 400] + fill: + type: solid + color: "#EA433510" + border: + style: solid + width: 1 + color: "#EA433540" + - elementId: bear-header-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [645, 110, 575, 50] + fill: + type: solid + color: "#EA433530" + - elementId: bear-icon + elementType: icon + bounds: [665, 122, 24, 24] + iconName: "fas:arrow-trend-down" + fill: + type: solid + color: "$accent" + - elementId: bear-title + elementType: text + bounds: [700, 115, 200, 36] + content: + fontSize: 22 + color: "$accent" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

BEAR CASE

+ - elementId: bear-content + elementType: text + bounds: [665, 180, 540, 310] + content: + fontSize: 17 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.7 + align: [left, top] + text: | +

$93B CapEx — unsustainable spending?

+

"Arms race" — just keeping up with OpenAI & Meta

+

Regulatory overhang — EU fines keep coming

+

Search disruption risk — long-term AI threat

+

Competition intensifying — LLM market crowded

+

"Bubble?"

+

infrastructure spending may not ROI

+ - elementId: question-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 540, 1160, 100] + fill: + type: solid + color: "$surfaceLight" + border: + style: solid + width: 1 + color: "$border" + - elementId: question-icon + elementType: icon + bounds: [90, 568, 28, 28] + iconName: "fas:circle-question" + fill: + type: solid + color: "$highlight" + - elementId: question-text + elementType: text + bounds: [130, 555, 1060, 70] + content: + fontSize: 20 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, middle] + text: | +

The Key Question: Is $93B in infrastructure spending buying a moat or inflating a bubble?

+ - elementId: source + elementType: text + bounds: [60, 650, 700, 20] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Source: Reddit r/investing, Hacker News, X discussions · Vizplainer

diff --git a/contentforge/docs/vizplainer-slides/pages/ch1.page b/contentforge/docs/vizplainer-slides/pages/ch1.page new file mode 100644 index 0000000..7613289 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/ch1.page @@ -0,0 +1,40 @@ +pageType: chapter +background: + type: solid + color: "$background" +elements: + - elementId: ch-num + elementType: text + bounds: [60, 200, 300, 100] + content: + fontSize: 96 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "01" + - elementId: accent-bar + elementType: shape + shapeName: rect + bounds: [60, 320, 80, 4] + fill: + type: solid + color: "$primary" + - elementId: ch-title + elementType: text + bounds: [60, 350, 800, 60] + content: + style: "$title" + align: [left, middle] + wrap: false + text: | +

Financial Snapshot

+ - elementId: ch-subtitle + elementType: text + bounds: [60, 420, 900, 40] + content: + style: "$subtitle" + align: [left, middle] + wrap: false + text: | +

First-ever $100B quarter and what the headline numbers tell us

diff --git a/contentforge/docs/vizplainer-slides/pages/ch2.page b/contentforge/docs/vizplainer-slides/pages/ch2.page new file mode 100644 index 0000000..b0361fb --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/ch2.page @@ -0,0 +1,40 @@ +pageType: chapter +background: + type: solid + color: "$background" +elements: + - elementId: ch-num + elementType: text + bounds: [60, 200, 300, 100] + content: + fontSize: 96 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "02" + - elementId: accent-bar + elementType: shape + shapeName: rect + bounds: [60, 320, 80, 4] + fill: + type: solid + color: "$primary" + - elementId: ch-title + elementType: text + bounds: [60, 350, 800, 60] + content: + style: "$title" + align: [left, middle] + wrap: false + text: | +

Three Core Insights

+ - elementId: ch-subtitle + elementType: text + bounds: [60, 420, 900, 40] + content: + style: "$subtitle" + align: [left, middle] + wrap: false + text: | +

What the community is obsessing over — beyond the headline numbers

diff --git a/contentforge/docs/vizplainer-slides/pages/ch3.page b/contentforge/docs/vizplainer-slides/pages/ch3.page new file mode 100644 index 0000000..4892d2b --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/ch3.page @@ -0,0 +1,40 @@ +pageType: chapter +background: + type: solid + color: "$background" +elements: + - elementId: ch-num + elementType: text + bounds: [60, 200, 300, 100] + content: + fontSize: 96 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "03" + - elementId: accent-bar + elementType: shape + shapeName: rect + bounds: [60, 320, 80, 4] + fill: + type: solid + color: "$primary" + - elementId: ch-title + elementType: text + bounds: [60, 350, 800, 60] + content: + style: "$title" + align: [left, middle] + wrap: false + text: | +

Community Sentiment

+ - elementId: ch-subtitle + elementType: text + bounds: [60, 420, 900, 40] + content: + style: "$subtitle" + align: [left, middle] + wrap: false + text: | +

What Reddit, Hacker News, and X are saying

diff --git a/contentforge/docs/vizplainer-slides/pages/cover.page b/contentforge/docs/vizplainer-slides/pages/cover.page new file mode 100644 index 0000000..1bd2d98 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/cover.page @@ -0,0 +1,81 @@ +pageType: cover +background: + type: gradient + gradientType: linear + angle: 135 + stops: + - {position: 0, color: "#0A0E1A"} + - {position: 0.5, color: "#0F1530"} + - {position: 1, color: "#141B2D"} +elements: + - elementId: bg-glow + elementType: shape + shapeName: ellipse + bounds: [440, 120, 400, 400] + fill: + type: gradient + gradientType: radial + stops: + - {position: 0, color: "#4285F420"} + - {position: 1, color: "#4285F400"} + - elementId: logo-icon + elementType: icon + bounds: [616, 140, 48, 48] + iconName: "fab:google" + fill: + type: solid + color: "$primary" + - elementId: main-title + elementType: text + bounds: [140, 220, 1000, 80] + content: + style: "$heroTitle" + align: [center, middle] + wrap: false + text: | +

Alphabet Q3 2025

+ - elementId: subtitle + elementType: text + bounds: [240, 310, 800, 40] + content: + style: "$subtitle" + align: [center, middle] + wrap: false + text: | +

Earnings Deep Dive

+ - elementId: tagline + elementType: text + bounds: [190, 380, 900, 60] + content: + fontSize: 22 + color: "$highlight" + fontFamily: "MiSans" + align: [center, middle] + lineHeight: 1.4 + text: | +

The "Death Spiral" Narrative Is Officially Dead

+ - elementId: divider + elementType: shape + shapeName: rect + bounds: [540, 470, 200, 2] + fill: + type: solid + color: "$border" + - elementId: badge-text + elementType: text + bounds: [340, 500, 600, 30] + content: + style: "$caption" + align: [center, middle] + wrap: false + text: | +

A Community-Powered Analysis · Reddit · Hacker News · X

+ - elementId: date-text + elementType: text + bounds: [440, 550, 400, 24] + content: + style: "$caption" + align: [center, middle] + wrap: false + text: | +

July 2026 · Based on Vizplainer Research

diff --git a/contentforge/docs/vizplainer-slides/pages/final.page b/contentforge/docs/vizplainer-slides/pages/final.page new file mode 100644 index 0000000..aaea7cd --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/final.page @@ -0,0 +1,94 @@ +pageType: final +background: + type: gradient + gradientType: linear + angle: 135 + stops: + - {position: 0, color: "#0A0E1A"} + - {position: 0.5, color: "#0F1530"} + - {position: 1, color: "#141B2D"} +elements: + - elementId: bg-glow + elementType: shape + shapeName: ellipse + bounds: [440, 100, 400, 400] + fill: + type: gradient + gradientType: radial + stops: + - {position: 0, color: "#4285F415"} + - {position: 1, color: "#4285F400"} + - elementId: question-mark + elementType: text + bounds: [340, 160, 600, 80] + content: + fontSize: 72 + color: "$primary" + fontFamily: "MiSans" + align: [center, middle] + wrap: false + text: "?" + - elementId: main-question + elementType: text + bounds: [140, 260, 1000, 100] + content: + fontSize: 36 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.3 + align: [center, middle] + text: | +

Is $93 billion in infrastructure spending

+

sustainable, or is this a bubble?

+ - elementId: divider + elementType: shape + shapeName: rect + bounds: [540, 390, 200, 2] + fill: + type: solid + color: "$border" + - elementId: cta-text + elementType: text + bounds: [290, 420, 700, 60] + content: + fontSize: 20 + color: "$textMuted" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [center, middle] + text: | +

Let us know what you think — catch you in the next update.

+ - elementId: badge-bg + elementType: shape + shapeName: roundRect + adjustments: [50000] + bounds: [440, 520, 400, 36] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: badge-text + elementType: text + bounds: [440, 520, 400, 36] + content: + fontSize: 14 + color: "$textMuted" + fontFamily: "MiSans" + align: [center, middle] + wrap: false + text: | +

Powered by Vizplainer · Reddit · HN · X

+ - elementId: bottom-text + elementType: text + bounds: [390, 600, 500, 24] + content: + fontSize: 12 + color: "$textMuted" + fontFamily: "MiSans" + align: [center, middle] + wrap: false + text: | +

Alphabet Q3 2025 Earnings Deep Dive · July 2026

diff --git a/contentforge/docs/vizplainer-slides/pages/insight1.page b/contentforge/docs/vizplainer-slides/pages/insight1.page new file mode 100644 index 0000000..29d2225 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/insight1.page @@ -0,0 +1,192 @@ +pageType: content +background: + type: solid + color: "$background" +elements: + - elementId: title-bar + elementType: shape + shapeName: rect + bounds: [60, 40, 4, 40] + fill: + type: solid + color: "$primary" + - elementId: page-title + elementType: text + bounds: [80, 35, 800, 50] + content: + style: "$title" + fontSize: 32 + align: [left, middle] + wrap: false + text: | +

Insight 1: AI Infrastructure at Scale

+ - elementId: insight-badge + elementType: shape + shapeName: roundRect + adjustments: [50000] + bounds: [80, 105, 180, 28] + fill: + type: solid + color: "#4285F430" + border: + style: solid + width: 1 + color: "$primary" + - elementId: insight-badge-text + elementType: text + bounds: [80, 105, 180, 28] + content: + fontSize: 12 + color: "$primary" + fontFamily: "MiSans" + align: [center, middle] + wrap: false + text: "VALUABLE FIND" + - elementId: main-stat-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 160, 520, 180] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: main-stat-icon + elementType: icon + bounds: [85, 180, 36, 36] + iconName: "fas:microchip" + fill: + type: solid + color: "$primary" + - elementId: main-stat-number + elementType: text + bounds: [85, 220, 470, 70] + content: + fontSize: 52 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

7 Billion

+ - elementId: main-stat-label + elementType: text + bounds: [85, 295, 470, 28] + content: + fontSize: 18 + color: "$textMuted" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "Tokens per minute processed by Gemini" + - elementId: side-card-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [620, 160, 600, 180] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: side-card-icon + elementType: icon + bounds: [645, 180, 32, 32] + iconName: "fas:cloud" + fill: + type: solid + color: "$highlight" + - elementId: side-card-title + elementType: text + bounds: [645, 220, 550, 30] + content: + fontSize: 20 + color: "$text" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

Google Cloud +34% → $15B+

+ - elementId: side-card-body + elementType: text + bounds: [645, 260, 550, 60] + content: + style: "$caption" + align: [left, top] + text: | +

Enterprise migration to GCP for generative AI is actually happening, not just forecasted

+ - elementId: hn-quote-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 370, 1160, 140] + fill: + type: solid + color: "#141B2D" + border: + style: solid + width: 1 + color: "$border" + - elementId: quote-icon + elementType: icon + bounds: [85, 395, 24, 24] + iconName: "fas:quote-left" + fill: + type: solid + color: "$textMuted" + - elementId: quote-text + elementType: text + bounds: [125, 390, 1060, 80] + content: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, top] + text: | +

"This volume validates the shift from 'experimental AI' to actual enterprise utility."

+

— Hacker News community discussion

+ - elementId: takeaway-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 540, 1160, 100] + fill: + type: solid + color: "#4285F415" + border: + style: solid + width: 1 + color: "#4285F440" + - elementId: takeaway-icon + elementType: icon + bounds: [85, 568, 28, 28] + iconName: "fas:lightbulb" + fill: + type: solid + color: "$highlight" + - elementId: takeaway-text + elementType: text + bounds: [125, 555, 1070, 70] + content: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, middle] + text: | +

Key Takeaway: Google's AI infrastructure is no longer experimental — it's a revenue-generating enterprise utility at massive scale.

+ - elementId: source + elementType: text + bounds: [60, 650, 600, 20] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Source: Alphabet Q3 2025 Earnings · Vizplainer Analysis

diff --git a/contentforge/docs/vizplainer-slides/pages/insight2.page b/contentforge/docs/vizplainer-slides/pages/insight2.page new file mode 100644 index 0000000..92ae989 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/insight2.page @@ -0,0 +1,204 @@ +pageType: content +background: + type: solid + color: "$background" +elements: + - elementId: title-bar + elementType: shape + shapeName: rect + bounds: [60, 40, 4, 40] + fill: + type: solid + color: "$accent" + - elementId: page-title + elementType: text + bounds: [80, 35, 900, 50] + content: + style: "$title" + fontSize: 32 + align: [left, middle] + wrap: false + text: | +

Insight 2: The $93B CapEx Arms Race

+ - elementId: insight-badge + elementType: shape + shapeName: roundRect + adjustments: [50000] + bounds: [80, 105, 160, 28] + fill: + type: solid + color: "#EA433520" + border: + style: solid + width: 1 + color: "$accent" + - elementId: insight-badge-text + elementType: text + bounds: [80, 105, 160, 28] + content: + fontSize: 12 + color: "$accent" + fontFamily: "MiSans" + align: [center, middle] + wrap: false + text: "CONTROVERSIAL" + - elementId: big-number-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 160, 400, 200] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: big-number + elementType: text + bounds: [80, 175, 360, 90] + content: + fontSize: 64 + color: "$accent" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

$93B

+ - elementId: big-number-label + elementType: text + bounds: [80, 275, 360, 30] + content: + fontSize: 18 + color: "$textMuted" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "2025 CapEx Guidance" + - elementId: big-number-range + elementType: text + bounds: [80, 310, 360, 24] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Range: $91B — $93B

+ - elementId: context-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [500, 160, 720, 200] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: context-title + elementType: text + bounds: [525, 175, 300, 30] + content: + fontSize: 16 + color: "$textMuted" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

THE DEBATE

+ - elementId: bear-view + elementType: text + bounds: [525, 215, 320, 120] + content: + fontSize: 16 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, top] + text: | +

▲ Bear View

+

"Terrifying arms race"

+

Spending just to keep up with OpenAI and Meta

+ - elementId: bull-view + elementType: text + bounds: [870, 215, 320, 120] + content: + fontSize: 16 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, top] + text: | +

▲ Bull View

+

"Capital-efficient monster"

+

34% operating margin even with massive spend

+ - elementId: reddit-quote-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 390, 1160, 120] + fill: + type: solid + color: "#141B2D" + border: + style: solid + width: 1 + color: "$border" + - elementId: reddit-icon + elementType: icon + bounds: [85, 415, 24, 24] + iconName: "fab:reddit" + fill: + type: solid + color: "#FF4500" + - elementId: reddit-quote + elementType: text + bounds: [125, 410, 1060, 80] + content: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, top] + text: | +

"Sentiment on r/investing is mixed. Some are calling this a terrifying 'arms race' spend... but the counter-argument gaining traction is that Google has turned into a capital-efficient monster."

+ - elementId: takeaway-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 540, 1160, 100] + fill: + type: solid + color: "#EA433515" + border: + style: solid + width: 1 + color: "#EA433540" + - elementId: takeaway-icon + elementType: icon + bounds: [85, 568, 28, 28] + iconName: "fas:scale-balanced" + fill: + type: solid + color: "$accent" + - elementId: takeaway-text + elementType: text + bounds: [125, 555, 1070, 70] + content: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, middle] + text: | +

Key Question: Is this massive CapEx buying an insurmountable moat, or inflating an AI bubble?

+ - elementId: source + elementType: text + bounds: [60, 650, 600, 20] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Source: Alphabet Q3 2025 Guidance · Vizplainer Analysis

diff --git a/contentforge/docs/vizplainer-slides/pages/insight3.page b/contentforge/docs/vizplainer-slides/pages/insight3.page new file mode 100644 index 0000000..11a4883 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/insight3.page @@ -0,0 +1,210 @@ +pageType: content +background: + type: solid + color: "$background" +elements: + - elementId: title-bar + elementType: shape + shapeName: rect + bounds: [60, 40, 4, 40] + fill: + type: solid + color: "$highlight" + - elementId: page-title + elementType: text + bounds: [80, 35, 900, 50] + content: + style: "$title" + fontSize: 32 + align: [left, middle] + wrap: false + text: | +

Insight 3: The $3.5B Fine as "Cost of Business"

+ - elementId: insight-badge + elementType: shape + shapeName: roundRect + adjustments: [50000] + bounds: [80, 105, 180, 28] + fill: + type: solid + color: "#FBBC0520" + border: + style: solid + width: 1 + color: "$highlight" + - elementId: insight-badge-text + elementType: text + bounds: [80, 105, 180, 28] + content: + fontSize: 12 + color: "$highlight" + fontFamily: "MiSans" + align: [center, middle] + wrap: false + text: "RESILIENCE TEST" + - elementId: fine-card-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 160, 500, 200] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: fine-icon + elementType: icon + bounds: [85, 180, 36, 36] + iconName: "fas:gavel" + fill: + type: solid + color: "$accent" + - elementId: fine-amount + elementType: text + bounds: [85, 225, 450, 70] + content: + fontSize: 52 + color: "$accent" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

$3.5B

+ - elementId: fine-label + elementType: text + bounds: [85, 305, 450, 30] + content: + fontSize: 18 + color: "$textMuted" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "EU Antitrust Fine (European Commission)" + - elementId: impact-card-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [600, 160, 620, 200] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: impact-title + elementType: text + bounds: [625, 175, 300, 30] + content: + fontSize: 16 + color: "$textMuted" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

MARKET IMPACT

+ - elementId: impact-then + elementType: text + bounds: [625, 215, 270, 120] + content: + fontSize: 16 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, top] + text: | +

Two Years Ago

+

News like this would have tanked the stock

+ - elementId: impact-arrow + elementType: shape + shapeName: straightConnector1 + bounds: [910, 260, 60, 0] + arrow: [null, "arrow"] + border: + style: solid + width: 2 + color: "$secondary" + - elementId: impact-now + elementType: text + bounds: [980, 215, 220, 120] + content: + fontSize: 16 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, top] + text: | +

Today

+

Treated as "cost of doing business"

+ - elementId: quote-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 390, 1160, 120] + fill: + type: solid + color: "#141B2D" + border: + style: solid + width: 1 + color: "$border" + - elementId: quote-icon + elementType: icon + bounds: [85, 415, 24, 24] + iconName: "fas:quote-left" + fill: + type: solid + color: "$textMuted" + - elementId: quote-text + elementType: text + bounds: [125, 410, 1060, 80] + content: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, top] + text: | +

"The fact that they can absorb a multi-billion dollar hit and still grow Net Income by 33% is frankly ridiculous."

+

— Community analysis consensus

+ - elementId: takeaway-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 540, 1160, 100] + fill: + type: solid + color: "#FBBC0515" + border: + style: solid + width: 1 + color: "#FBBC0540" + - elementId: takeaway-icon + elementType: icon + bounds: [85, 568, 28, 28] + iconName: "fas:shield-halved" + fill: + type: solid + color: "$highlight" + - elementId: takeaway-text + elementType: text + bounds: [125, 555, 1070, 70] + content: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [left, middle] + text: | +

Key Takeaway: Alphabet's financial resilience has reached a point where multi-billion regulatory fines are no longer market-moving events.

+ - elementId: source + elementType: text + bounds: [60, 650, 600, 20] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Source: European Commission · Vizplainer Analysis

diff --git a/contentforge/docs/vizplainer-slides/pages/metrics.page b/contentforge/docs/vizplainer-slides/pages/metrics.page new file mode 100644 index 0000000..b45a826 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/metrics.page @@ -0,0 +1,216 @@ +pageType: content +background: + type: solid + color: "$background" +elements: + - elementId: title-bar + elementType: shape + shapeName: rect + bounds: [60, 40, 4, 40] + fill: + type: solid + color: "$primary" + - elementId: page-title + elementType: text + bounds: [80, 35, 600, 50] + content: + style: "$title" + fontSize: 32 + align: [left, middle] + wrap: false + text: | +

Q3 2025 Key Metrics

+ - elementId: card1-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 110, 360, 240] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: card1-label + elementType: text + bounds: [80, 125, 200, 20] + content: + style: "$label" + align: [left, middle] + wrap: false + text: "TOTAL REVENUE" + - elementId: card1-number + elementType: text + bounds: [80, 155, 320, 70] + content: + style: "$dataNumber" + align: [left, middle] + wrap: false + text: | +

$102B

+ - elementId: card1-change + elementType: text + bounds: [80, 230, 320, 30] + content: + fontSize: 18 + color: "$secondary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

+16% YoY · First $100B quarter

+ - elementId: card1-desc + elementType: text + bounds: [80, 270, 320, 60] + content: + style: "$caption" + align: [left, top] + text: | +

The largest quarterly revenue in Alphabet's history

+ - elementId: card2-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [460, 110, 360, 240] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: card2-label + elementType: text + bounds: [480, 125, 200, 20] + content: + style: "$label" + align: [left, middle] + wrap: false + text: "NET INCOME" + - elementId: card2-number + elementType: text + bounds: [480, 155, 320, 70] + content: + style: "$dataNumber" + color: "$secondary" + align: [left, middle] + wrap: false + text: | +

+33%

+ - elementId: card2-change + elementType: text + bounds: [480, 230, 320, 30] + content: + fontSize: 18 + color: "$secondary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

Despite $3.5B EU fine

+ - elementId: card2-desc + elementType: text + bounds: [480, 270, 320, 60] + content: + style: "$caption" + align: [left, top] + text: | +

Profit growth even after absorbing a massive regulatory hit

+ - elementId: card3-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [860, 110, 360, 240] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: card3-label + elementType: text + bounds: [880, 125, 200, 20] + content: + style: "$label" + align: [left, middle] + wrap: false + text: "GOOGLE CLOUD" + - elementId: card3-number + elementType: text + bounds: [880, 155, 320, 70] + content: + style: "$dataNumber" + color: "$highlight" + align: [left, middle] + wrap: false + text: | +

+34%

+ - elementId: card3-change + elementType: text + bounds: [880, 230, 320, 30] + content: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

$15B+ Revenue · AI migration driving growth

+ - elementId: card3-desc + elementType: text + bounds: [880, 270, 320, 60] + content: + style: "$caption" + align: [left, top] + text: | +

Enterprise migration to GCP is actually happening

+ - elementId: bottom-card-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 380, 1160, 200] + fill: + type: solid + color: "$surfaceLight" + border: + style: solid + width: 1 + color: "$border" + - elementId: bottom-icon + elementType: icon + bounds: [90, 410, 32, 32] + iconName: "fas:chart-line" + fill: + type: solid + color: "$primary" + - elementId: bottom-title + elementType: text + bounds: [135, 405, 500, 36] + content: + fontSize: 22 + color: "$text" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

Operating Margin

+ - elementId: bottom-body + elementType: text + bounds: [90, 450, 1100, 110] + content: + style: "$body" + align: [left, top] + text: | +

Operating margins hit nearly 34% when excluding the $3.5B EU antitrust fine. Even with massive AI infrastructure investments, Alphabet maintains capital efficiency that rivals its pre-AI era.

+

These numbers collectively refute the "Google is dying" narrative.

+ - elementId: source + elementType: text + bounds: [60, 650, 600, 20] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Source: Alphabet Q3 2025 Earnings Report · Analysis by Vizplainer

diff --git a/contentforge/docs/vizplainer-slides/pages/takeaway.page b/contentforge/docs/vizplainer-slides/pages/takeaway.page new file mode 100644 index 0000000..5788e13 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/takeaway.page @@ -0,0 +1,173 @@ +pageType: content +background: + type: solid + color: "$background" +elements: + - elementId: title-bar + elementType: shape + shapeName: rect + bounds: [60, 40, 4, 40] + fill: + type: solid + color: "$primary" + - elementId: page-title + elementType: text + bounds: [80, 35, 600, 50] + content: + style: "$title" + fontSize: 32 + align: [left, middle] + wrap: false + text: | +

The Bottom Line

+ - elementId: conclusion-card-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 110, 1160, 180] + fill: + type: gradient + gradientType: linear + angle: 90 + stops: + - {position: 0, color: "#4285F420"} + - {position: 1, color: "#141B2D"} + border: + style: solid + width: 1 + color: "#4285F440" + - elementId: conclusion-icon + elementType: icon + bounds: [90, 140, 40, 40] + iconName: "fas:bullseye" + fill: + type: solid + color: "$primary" + - elementId: conclusion-text + elementType: text + bounds: [150, 130, 1040, 140] + content: + fontSize: 20 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.6 + align: [left, top] + text: | +

The fear that search would vanish overnight due to chatbots hasn't materialized.

+

"Search & Other" revenue is up double digits

+

650 million monthly active users on the Gemini App

+

Successfully managing the transition from traditional search to an AI-first model

+ - elementId: for-builders-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 320, 555, 180] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: for-builders-icon + elementType: icon + bounds: [85, 345, 28, 28] + iconName: "fas:code" + fill: + type: solid + color: "$primary" + - elementId: for-builders-title + elementType: text + bounds: [125, 340, 300, 36] + content: + fontSize: 20 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

For Builders

+ - elementId: for-builders-body + elementType: text + bounds: [85, 385, 510, 100] + content: + fontSize: 16 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.6 + align: [left, top] + text: | +

The infrastructure is scaling fast. Gemini's 7B tokens/minute and GCP's 34% growth signal a mature AI platform ready for enterprise workloads.

+ - elementId: for-investors-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [645, 320, 575, 180] + fill: + type: solid + color: "$surface" + border: + style: solid + width: 1 + color: "$border" + - elementId: for-investors-icon + elementType: icon + bounds: [670, 345, 28, 28] + iconName: "fas:chart-pie" + fill: + type: solid + color: "$secondary" + - elementId: for-investors-title + elementType: text + bounds: [710, 340, 300, 36] + content: + fontSize: 20 + color: "$secondary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

For Investors

+ - elementId: for-investors-body + elementType: text + bounds: [670, 385, 530, 100] + content: + fontSize: 16 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.6 + align: [left, top] + text: | +

The $93B CapEx bill is hefty, but right now, it looks like it's buying an insurmountable moat. Margins prove capital efficiency.

+ - elementId: final-quote-bg + elementType: shape + shapeName: roundRect + adjustments: [8000] + bounds: [60, 530, 1160, 100] + fill: + type: solid + color: "#141B2D" + border: + style: solid + width: 1 + color: "$border" + - elementId: final-quote + elementType: text + bounds: [90, 545, 1100, 70] + content: + fontSize: 20 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.5 + align: [center, middle] + text: | +

"The Google 'death spiral' narrative is officially dead."

+ - elementId: source + elementType: text + bounds: [60, 650, 600, 20] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Source: Alphabet Q3 2025 Earnings · Vizplainer Analysis

diff --git a/contentforge/docs/vizplainer-slides/pages/toc.page b/contentforge/docs/vizplainer-slides/pages/toc.page new file mode 100644 index 0000000..6fcc138 --- /dev/null +++ b/contentforge/docs/vizplainer-slides/pages/toc.page @@ -0,0 +1,162 @@ +pageType: table_of_contents +background: + type: solid + color: "$background" +elements: + - elementId: toc-title + elementType: text + bounds: [60, 50, 500, 50] + content: + style: "$title" + align: [left, middle] + wrap: false + text: | +

Executive Overview

+ - elementId: toc-line + elementType: shape + shapeName: rect + bounds: [60, 110, 60, 4] + fill: + type: solid + color: "$primary" + - elementId: item1-num + elementType: text + bounds: [60, 160, 80, 60] + content: + fontSize: 48 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "01" + - elementId: item1-title + elementType: text + bounds: [150, 160, 400, 36] + content: + fontSize: 24 + color: "$text" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

Financial Snapshot

+ - elementId: item1-desc + elementType: text + bounds: [150, 200, 500, 30] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

First-ever $100B quarter and headline numbers

+ - elementId: divider1 + elementType: shape + shapeName: rect + bounds: [60, 260, 1160, 1] + fill: + type: solid + color: "$border" + - elementId: item2-num + elementType: text + bounds: [60, 290, 80, 60] + content: + fontSize: 48 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "02" + - elementId: item2-title + elementType: text + bounds: [150, 290, 400, 36] + content: + fontSize: 24 + color: "$text" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

Three Core Insights

+ - elementId: item2-desc + elementType: text + bounds: [150, 330, 600, 30] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

What the community is obsessing over beyond the headlines

+ - elementId: divider2 + elementType: shape + shapeName: rect + bounds: [60, 390, 1160, 1] + fill: + type: solid + color: "$border" + - elementId: item3-num + elementType: text + bounds: [60, 420, 80, 60] + content: + fontSize: 48 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "03" + - elementId: item3-title + elementType: text + bounds: [150, 420, 400, 36] + content: + fontSize: 24 + color: "$text" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

Community Sentiment

+ - elementId: item3-desc + elementType: text + bounds: [150, 460, 600, 30] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

What Reddit, Hacker News, and X are saying

+ - elementId: divider3 + elementType: shape + shapeName: rect + bounds: [60, 520, 1160, 1] + fill: + type: solid + color: "$border" + - elementId: item4-num + elementType: text + bounds: [60, 550, 80, 60] + content: + fontSize: 48 + color: "$primary" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: "04" + - elementId: item4-title + elementType: text + bounds: [150, 550, 400, 36] + content: + fontSize: 24 + color: "$text" + fontFamily: "MiSans" + align: [left, middle] + wrap: false + text: | +

The Bottom Line

+ - elementId: item4-desc + elementType: text + bounds: [150, 590, 600, 30] + content: + style: "$caption" + align: [left, middle] + wrap: false + text: | +

Takeaways for builders and investors

diff --git a/contentforge/docs/vizplainer-slides/slides.pptd b/contentforge/docs/vizplainer-slides/slides.pptd new file mode 100644 index 0000000..e22a5fd --- /dev/null +++ b/contentforge/docs/vizplainer-slides/slides.pptd @@ -0,0 +1,63 @@ +title: "Alphabet Q3 2025 Earnings Deep Dive" +size: [1280, 720] +theme: + colors: + primary: "#4285F4" + secondary: "#34A853" + accent: "#EA4335" + highlight: "#FBBC05" + background: "#0A0E1A" + surface: "#141B2D" + surfaceLight: "#1E2746" + text: "#E8ECF1" + textMuted: "#8B95A5" + border: "#2A3550" + textStyles: + heroTitle: + fontSize: 56 + color: "$text" + fontFamily: "MiSans" + letterSpacing: -1 + title: + fontSize: 40 + color: "$text" + fontFamily: "MiSans" + letterSpacing: -0.5 + subtitle: + fontSize: 24 + color: "$textMuted" + fontFamily: "MiSans" + lineHeight: 1.4 + body: + fontSize: 18 + color: "$text" + fontFamily: "MiSans" + lineHeight: 1.6 + caption: + fontSize: 14 + color: "$textMuted" + fontFamily: "MiSans" + lineHeight: 1.4 + dataNumber: + fontSize: 48 + color: "$primary" + fontFamily: "MiSans" + letterSpacing: -1 + label: + fontSize: 12 + color: "$textMuted" + fontFamily: "MiSans" + letterSpacing: 1 +pages: + - pages/cover.page + - pages/toc.page + - pages/ch1.page + - pages/metrics.page + - pages/ch2.page + - pages/insight1.page + - pages/insight2.page + - pages/insight3.page + - pages/ch3.page + - pages/bullbear.page + - pages/takeaway.page + - pages/final.page diff --git a/contentforge/docs/vizplainer-slides/slides.pptx b/contentforge/docs/vizplainer-slides/slides.pptx new file mode 100644 index 0000000000000000000000000000000000000000..4b6be4f3968bcba403385f65669cf899533787f8 GIT binary patch literal 74628 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≫gK$n{BwyeY4dg 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 IjIvSx19uE-PXGV_ literal 0 HcmV?d00001 diff --git a/contentforge/external-repos/OpenMontage b/contentforge/external-repos/OpenMontage new file mode 160000 index 0000000..c36e412 --- /dev/null +++ b/contentforge/external-repos/OpenMontage @@ -0,0 +1 @@ +Subproject commit c36e41223e819441748817105635ac4036d41b10 diff --git a/contentforge/external-repos/OpenMontage_ANALYSIS.md b/contentforge/external-repos/OpenMontage_ANALYSIS.md new file mode 100644 index 0000000..efd5e24 --- /dev/null +++ b/contentforge/external-repos/OpenMontage_ANALYSIS.md @@ -0,0 +1,397 @@ +## OpenMontage 仓库分析 + +> 分析时间:2026-07-25 +> 仓库路径:`/Users/patrick/innate/projects/innate-keepthem/contentforge/external-repos/OpenMontage/` +> 分析师:AI 技术分析师 + +--- + +### 1. 项目概述 + +**OpenMontage** 是一个开源的 AI 编排视频生产平台,自称"首个开源的 Agentic 视频生产系统"。它通过 AI Agent 驱动完整的视频制作流水线,从创意构思到最终渲染输出,覆盖研究、脚本、场景规划、资产生成、编辑和合成等全流程。 + +项目的核心理念是 **Agent-First 架构**:AI Agent 本身即为编排器,Python 代码仅提供工具执行和状态持久化能力。所有创意决策、编排逻辑、审查标准和质量标准都存储在人类可读的指令文件中(YAML 管道清单 + Markdown 技能文件)。 + +项目已在 GitHub Trending 获得较高关注,采用 AGPLv3 许可证开源。 + +**关键数据:** +- 仓库文件总数:约 2,050 个文件 +- 生产管道:12 条(涵盖动画解释器、纪录片蒙太奇、电影预告片、屏幕演示、播客再利用等) +- 生产工具:100+(视频生成、图像生成、TTS、音乐、音频混合、字幕、增强、分析等) +- Agent 技能文件:700+(管道导演技能、创意技术技能、核心工具技能、元审查技能) +- 视频生成提供商集成:15+(Kling、Runway、Veo、Grok、WAN 2.1、Hunyuan 等) +- 图像生成提供商集成:11+(FLUX、Imagen、GPT Image、Grok 等) +- TTS 提供商集成:5+(ElevenLabs、Google、OpenAI、Piper 等) + +--- + +### 2. 功能分析 + +#### 2.1 核心功能模块 + +| 模块 | 功能描述 | 工具数量 | +|------|----------|----------| +| **视频生成** | 支持云 API(Kling、Runway、Veo、Grok 等)和本地 GPU(WAN 2.1、Hunyuan、CogVideo、LTX 等) | 15+ | +| **图像生成** | FLUX、Imagen、GPT Image、Recraft、Grok 等 + 免费图库(Pexels、Pixabay、Unsplash) | 11+ | +| **语音合成 (TTS)** | ElevenLabs、Google TTS(700+ 声音)、OpenAI TTS、Piper(本地免费) | 5+ | +| **音乐/音效** | Suno AI(歌曲生成)、ElevenLabs Music/SFX、免费音乐库 | 4+ | +| **视频编辑** | 视频拼接、修剪、合成、字幕烧录、音频混合、色彩分级 | 8+ | +| **视频分析** | 转录(WhisperX)、场景检测、帧采样、视频理解(CLIP/BLIP-2)、音频能量分析 | 10+ | +| **视频下载** | 基于 yt-dlp,支持 YouTube、TikTok、Instagram、Vimeo 等 1000+ 站点 | 1 | +| **增强处理** | 图像/视频放大(Real-ESRGAN)、背景移除(rembg)、人脸增强/修复 | 5+ | +| **Avatar/唇同步** | SadTalker/MuseTalk、Wav2Lip、Kling Avatar、Kling Lip Sync | 4+ | +| **屏幕录制** | 屏幕捕获、录屏选择器 | 2+ | + +#### 2.2 生产管道(Pipelines) + +OpenMontage 定义了 12 条完整的生产管道,每条管道遵循统一的工作流: + +``` +research → proposal → script → scene_plan → assets → edit → compose → publish +``` + +| 管道名称 | 类型 | 适用场景 | +|----------|------|----------| +| **Animated Explainer** | AI 生成 | 教育内容、教程、主题解析 | +| **Animation** | 动效图形 | 社交媒体、产品演示、抽象概念 | +| **Avatar Spokesperson** | Avatar 驱动 | 企业通讯、培训、公告 | +| **Cinematic** | 电影感剪辑 | 品牌影片、预告片、促销内容 | +| **Clip Factory** | 批量短片段 | 长内容再利用为社交短视频 | +| **Documentary Montage** | 真实素材 | 视频散文、氛围片段、档案剪辑 | +| **Hybrid** | 混合 | 现有素材 + AI 生成补充 | +| **Localization & Dub** | 本地化处理 | 多语言字幕、配音、翻译 | +| **Podcast Repurpose** | 播客再利用 | 播客营销、音频可视化 | +| **Screen Demo** | 屏幕演示 | 产品演示、教程、文档 | +| **Talking Head** | 主讲人视频 | 演讲、Vlog、访谈 | +| **Character Animation** | 角色动画 | 本地 SVG 绑定角色动画 | + +#### 2.3 参考视频驱动创作 + +OpenMontage 支持从现有视频(YouTube Shorts、Reels、TikTok、本地片段)开始创作: +1. 粘贴参考视频 URL +2. Agent 分析转录、节奏、场景、关键帧和风格 +3. 输出 2-3 个差异化概念、工具路径、成本估算和样本预览 + +这是其区别于其他 AI 视频工具的重要特性。 + +#### 2.4 质量治理体系 + +| 治理机制 | 说明 | +|----------|------| +| **人类审批门控** | 提案、脚本、场景规划、生成资产、发布等阶段强制暂停等待用户批准 | +| **预合成验证** | 阻止违反交付承诺的渲染(如"动态主导"视频却含 80% 静态图片) | +| **后渲染自检** | ffprobe 验证、帧提取检查、音频电平分析、字幕检查 | +| **幻灯片风险评分** | 6 维度分析防止"动画 PPT"式低质量输出 | +| **供应商评分选择** | 7 维度评分引擎(任务适配 30%、输出质量 20%、控制特性 15%、可靠性 15%、成本效率 10%、延迟 5%、连续性 5%) | +| **决策审计追踪** | 记录每个创意和技术选择的替代方案、置信度分数和推理 | +| **预算控制** | 估算 → 预留 → 对账,支持观察/警告/封顶模式,单次操作审批阈值 | + +#### 2.5 Backlot 实时故事板 + +一个基于 FastAPI + SSE 的本地 Web 面板,实时展示生产过程: +- 管道阶段点亮状态 +- 脚本以剧本页面形式呈现 +- 场景卡片在资产生成时动态填充 +- 每个供应商决策和花费展示在面板上 +- 支持场景级审批门控(资产生成前审批视觉) +- 支持运行回放(从时间戳重建整个生产过程) + +--- + +### 3. 技术栈 + +#### 3.1 后端 / 工具层 + +| 技术 | 用途 | 版本要求 | +|------|------|----------| +| **Python** | 核心工具和基础设施 | 3.10+ | +| **Pydantic** | 配置模型验证、运行时数据校验 | v2+ | +| **PyYAML** | 管道清单和配置解析 | ≥6.0 | +| **JSONSchema** | 工件契约验证 | ≥4.20 | +| **Pillow** | 图像处理 | ≥10.0 | +| **NumPy** | 数值计算 | ≥1.24 | +| **Requests** | HTTP 客户端 | ≥2.31 | +| **yt-dlp** | 视频下载 | 动态依赖 | +| **FFmpeg** | 视频编码、合成、字幕烧录 | 系统依赖 | +| **FastAPI + Uvicorn** | Backlot 本地服务器 | ≥0.110 | +| **watchfiles** | 文件监控(Backlot 实时更新) | ≥0.21 | +| **google-genai** | Gemini API 客户端 | ≥1.0.0 | +| **openai** | OpenAI API 客户端(含 Videos API) | ≥2.44.0 | + +#### 3.2 前端 / 合成层 + +| 技术 | 用途 | +|------|------| +| **Node.js** | Remotion 和 HyperFrames 运行时 | ≥18 | +| **React + TypeScript** | Remotion 视频合成引擎 | +| **Remotion** | 基于 React 的程序化视频渲染(弹簧动画图像场景、统计卡、字幕等) | +| **HTML/CSS/GSAP** | HyperFrames 运动图形渲染(动力学排版、产品促销、SVG 角色动画) | +| **HyperFrames CLI** | `npx hyperframes` 驱动的 HTML→视频渲染 | +| **ManimCE / ManimGL** | 数学动画生成 | + +#### 3.3 架构模式 + +**Agent-First 指令驱动架构:** + +``` +Layer 1: tools/ + pipeline_defs/ → "What exists"(可执行能力 + 编排) +Layer 2: skills/ → "How OpenMontage uses it"(项目约定) +Layer 3: .agents/skills/ → "How the technology works"(通用 API 规则) +``` + +**工具契约设计(BaseTool):** +- 所有工具继承自 `BaseTool`,实现统一的 `ToolContract` +- 自动发现机制:`tool_registry.py` 扫描注册,无需手动注册 +- 标准化的成本估算、运行时间估算、依赖检查、健康报告 +- Backlot 事件自动埋点(通过 `_instrument_execute` 装饰器) + +**管道状态机:** +``` +idea → research → proposal → script → scene_plan → assets → edit → compose → publish +``` + +**关键基础设施模块:** +- `lib/config_model.py` — Pydantic 运行时配置加载 +- `lib/checkpoint.py` — 检查点读写(支持可恢复执行) +- `lib/pipeline_loader.py` — 管道清单加载 + 验证 +- `lib/media_profiles.py` — 平台特定的渲染配置文件 +- `tools/tool_registry.py` — 工具自动发现和注册 +- `tools/cost_tracker.py` — 预算治理(估算/预留/对账) + +--- + +### 4. 文件结构 + +``` +OpenMontage/ +├── .agents/skills/ # Layer 3 外部技术知识包 (~100+ skills) +│ ├── hyperframes*/ # HyperFrames 相关技能 +│ ├── remotion-best-practices/ +│ ├── flux-best-practices/ +│ ├── manimce-best-practices/ +│ ├── gsap*/ # GSAP 动画技能 +│ └── ... +│ +├── .cursor/ # Cursor IDE 规则和命令 +├── .github/ # GitHub 配置、Copilot 指令 +│ +├── backlot/ # Backlot 实时故事板 (FastAPI + SSE) +│ +├── docs/ # 文档 (架构、提供商指南、PR 审查指南等) +│ +├── ink-theater/ # Ink Theater 模块 +│ +├── lib/ # 核心基础设施 +│ ├── base_tool.py # 工具契约基类 (实际在 tools/) +│ ├── checkpoint.py # 检查点系统 +│ ├── config_model.py # Pydantic 配置模型 +│ ├── events.py # Backlot 事件发射 +│ ├── media_profiles.py # 平台渲染配置 +│ ├── pipeline_loader.py # 管道清单加载器 +│ ├── scoring.py # 供应商评分引擎 +│ └── ... +│ +├── pipeline_defs/ # YAML 管道清单 (12 条管道) +│ ├── animated-explainer.yaml +│ ├── animation.yaml +│ ├── cinematic.yaml +│ ├── documentary-montage.yaml +│ └── ... +│ +├── remotion-composer/ # React/Remotion 视频合成引擎 +│ ├── src/components/ # 8 个 Remotion 组件 +│ ├── package.json +│ └── tsconfig.json +│ +├── schemas/ # JSON Schema 契约验证 +│ ├── artifacts/ # 工件模式 (brief, script, scene_plan 等) +│ ├── pipelines/ # 管道清单模式 +│ ├── styles/ # 风格剧本模式 +│ └── tools/ # 工具 I/O 模式 +│ +├── scripts/ # 实用脚本 +│ +├── skills/ # Layer 2 OpenMontage 技能 +│ ├── INDEX.md # 技能索引 +│ ├── core/ # 核心技能 (hyperframes, 动画运行时选择等) +│ ├── creative/ # 创意技术技能 +│ ├── meta/ # 元技能 (reviewer, checkpoint-protocol, skill-creator) +│ └── pipelines/ # 每管道的阶段导演技能 +│ +├── styles/ # 视觉风格剧本 (YAML) +│ +├── tests/ # 测试 +│ ├── contracts/ # 契约测试 +│ └── qa/ # 质量验证测试 +│ +├── tools/ # 100+ Python 工具 (Agent 的"手") +│ ├── audio/ # TTS、音乐、音频混合、增强 (18 文件) +│ ├── graphics/ # 图像/图形生成 (15 文件) +│ ├── enhancement/ # 放大、背景移除、人脸增强 (5 文件) +│ ├── analysis/ # 转录、场景检测、帧采样、视频分析 (14 文件) +│ ├── avatar/ # Talking head、唇同步 +│ ├── subtitle/ # SRT/VTT 生成 +│ ├── video/ # 视频生成、合成、拼接、修剪 (50+ 文件) +│ │ ├── stock_sources/ # 免费素材源 (Pexels、Archive.org、NASA 等) +│ └── base_tool.py # 工具契约基类 +│ +├── AGENT_GUIDE.md # Agent 操作指南和契约 +├── PROJECT_CONTEXT.md # 架构参考 (单一真相源) +├── README.md / README_zh-CN.md +├── config.yaml # 全局配置 +├── Makefile # 一键设置、测试、演示 +├── requirements.txt # Python 依赖 +└── setup.py # Python 包配置 +``` + +--- + +### 5. 与 ContentForge 整合评估 + +#### 5.1 功能重叠矩阵 + +| ContentForge 功能 | OpenMontage 对应能力 | 重叠度 | +|-------------------|----------------------|--------| +| YouTube 视频下载 | `tools/analysis/video_downloader.py` (yt-dlp 封装) | **高** | +| 视频分析/转录 | `tools/analysis/transcriber.py` (WhisperX)、`azure_stt.py`、`dashscope_asr.py`、场景检测、帧采样 | **高** | +| AI 内容生成 | 脚本生成、场景规划、研究简报生成(Agent 驱动) | **高** | +| 多 Agent 对话 | Agent-First 架构,多阶段 Agent 协作 | **中** | +| 技能 (Skill) 系统 | 三层知识架构(tools/skills/.agents/skills) | **高** | +| Tauri 桌面应用 | ❌ 无桌面应用,纯 Agent/CLI 驱动 | **无** | +| Rust 后端 | ❌ Python 后端 | **无** | +| React 前端 | Remotion 合成器使用 React,但非用户界面 | **低** | +| Next.js | ❌ 未使用 | **无** | +| Markdown 输出 | ❌ 无原生 Markdown 输出 | **无** | +| Notes 输出 | ❌ 无 | **无** | +| XHS (小红书) 输出 | ❌ 无 | **无** | +| Slides 输出 | ❌ 无(有数据可视化组件但非演示文稿) | **低** | +| Video 输出 | **核心能力** — Remotion + HyperFrames + FFmpeg 完整视频生产 | **高** | + +#### 5.2 整合价值评级 + +| 维度 | 评级 | 理由 | +|------|------|------| +| **整体整合价值** | **高** | OpenMontage 在视频分析、转录、AI 内容生成 pipeline、技能系统架构、视频合成渲染等方面与 ContentForge 有高度互补性 | +| **直接代码复用** | **中** | 技术栈差异较大(Python vs Rust),但工具设计理念、API 封装模式、YAML 管道定义方式值得参考 | +| **架构借鉴** | **高** | Agent-First 架构、三层知识系统、质量门控机制、预算控制、决策审计追踪等设计理念可直接借鉴 | +| **功能互补** | **高** | ContentForge 缺乏视频合成渲染能力,OpenMontage 的 Remotion/HyperFrames 引擎可填补这一空白 | +| **整合复杂度** | **中-高** | 需要桥接 Python 和 Rust 运行时,或选择性提取/重写关键模块 | + +--- + +### 6. 整合建议 + +#### 6.1 高优先级 — 建议立即评估整合 + +**① 视频分析与转录模块** +- **可复用内容:** `tools/analysis/` 目录下的分析工具设计模式 +- **具体建议:** + - 参考 `transcriber.py`(WhisperX 封装,支持词级时间戳)的设计,为 ContentForge 构建 Rust 版本的转录管道 + - 参考 `video_analyzer.py` 的多维度视频分析(转录、场景、关键帧、风格)设计 ContentForge 的视频分析工作流 + - 参考 `frame_sampler.py` 的智能帧提取逻辑 + - 参考 `scene_detect.py` 的场景边界检测实现 +- **整合方式:** 架构和 API 设计模式借鉴,Rust 重新实现(或直接调用 Python 子进程作为过渡方案) + +**② 视频合成/渲染引擎** +- **可复用内容:** `remotion-composer/`(React/Remotion 视频合成)和 `tools/video/hyperframes_compose.py` +- **具体建议:** + - 将 Remotion 作为 ContentForge 的视频输出引擎:ContentForge 生成内容素材,调用 Remotion 渲染为视频 + - HyperFrames 可作为轻量级 HTML/GSAP 动画渲染方案,适合 XHS/Slides 风格的动态内容 + - 利用 OpenMontage 已定义的 8 个 Remotion 组件(TextCard、StatCard、ProgressBar、CalloutBox、ComparisonCard、Charts) +- **整合方式:** Node.js 子进程调用(ContentForge Rust 后端 → 调用 Remotion CLI / HyperFrames CLI) + +**③ 技能系统架构(三层知识架构)** +- **可复用内容:** 三层知识架构设计理念 +- **具体建议:** + - **Layer 1(能力层):** ContentForge 的工具/插件注册表,声明式定义可用能力 + - **Layer 2(约定层):** ContentForge 专有的技能文件,定义项目内如何使用工具 + - **Layer 3(技术层):** 外部技术的通用知识包(如 FLUX 最佳实践、Remotion 最佳实践等) + - OpenMontage 的 `skills/INDEX.md` 索引机制和技能发现逻辑值得借鉴 +- **整合方式:** 架构设计模式借鉴,无需直接代码复用 + +#### 6.2 中优先级 — 建议后续评估整合 + +**④ 质量门控与审计追踪机制** +- **可复用内容:** 预合成验证、后渲染自检、幻灯片风险评分、决策审计日志 +- **具体建议:** + - 为 ContentForge 的视频/内容输出引入"交付承诺验证"机制 + - 建立供应商/模型评分选择引擎(7 维度评分) + - 实现决策审计追踪(替代方案、置信度、推理记录) +- **整合方式:** 设计理念借鉴,Rust 实现 + +**⑤ 预算控制系统** +- **可复用内容:** `tools/cost_tracker.py` 的估算→预留→对账机制 +- **具体建议:** + - 为 ContentForge 的 AI API 调用引入成本估算和预算封顶 + - 参考 `observe`/`warn`/`cap` 三种模式 +- **整合方式:** 设计理念借鉴 + +**⑥ 管道/工作流定义(YAML + Markdown)** +- **可复用内容:** `pipeline_defs/` 的 YAML 管道清单格式和 `skills/pipelines/` 的阶段导演技能 +- **具体建议:** + - ContentForge 可定义自己的内容生产管道(如 YouTube → 分析 → 摘要 → XHS 笔记 → Slides) + - 每个阶段用 Markdown 技能文件定义 Agent 执行指南 + - YAML 清单定义阶段顺序、工具可用性、审批门控、成功标准 +- **整合方式:** 格式和模式借鉴 + +**⑦ YouTube 视频下载与分析工作流** +- **可复用内容:** `tools/analysis/video_downloader.py` + `transcript_fetcher.py` + `video_analyzer.py` +- **具体建议:** + - OpenMontage 已构建完整的"参考视频 → 分析 → 差异化概念"工作流 + - ContentForge 可直接复用这一工作流:用户输入 YouTube URL → 下载/分析 → 生成 ContentForge 内容 +- **整合方式:** 参考工作流设计,Rust 重新实现或 Python 子进程调用 + +#### 6.3 低优先级 / 不直接相关 + +| 模块 | 说明 | +|------|------| +| **Avatar/唇同步** | 除非 ContentForge 规划视频 Avatar 功能 | +| **屏幕录制** | 除非 ContentForge 规划屏幕演示内容类型 | +| **本地 GPU 视频生成** | 除非 ContentForge 规划本地视频生成能力 | +| **Backlot 故事板** | 可作为独立功能评估,但与 ContentForge 核心功能关联度较低 | +| **大量云 API 提供商集成** | ContentForge 已有自己的 Provider 系统,可评估选择性补充 | + +#### 6.4 技术整合路径建议 + +``` +阶段 1: 快速验证(1-2 周) +├── 在 ContentForge 中集成 yt-dlp 视频下载(直接复用逻辑) +├── 评估 Remotion 作为视频输出引擎的可行性 +└── 试点三层技能架构在 ContentForge 中的映射 + +阶段 2: 核心整合(4-6 周) +├── 构建 ContentForge 的视频分析管道(Rust 实现,参考 OpenMontage 设计) +├── 集成 Remotion 渲染流程(Rust → Node.js 子进程调用) +├── 引入质量门控机制(交付承诺验证、后渲染检查) +└── 建立决策审计日志 + +阶段 3: 深度整合(8-12 周) +├── 实现预算控制系统 +├── 构建 ContentForge 专属内容生产管道(YAML + Markdown) +├── 评估 HyperFrames 对 XHS/Slides 动态内容的适用性 +└── 完整的端到端测试和优化 +``` + +#### 6.5 风险与注意事项 + +| 风险 | 缓解措施 | +|------|----------| +| **AGPLv3 许可证** | OpenMontage 采用 AGPLv3,如直接集成代码需注意许可证合规性;建议以架构借鉴为主,独立实现 | +| **Python vs Rust 技术栈差异** | 选择性模块通过子进程调用(Python 脚本),核心逻辑 Rust 重写 | +| **Node.js 运行时依赖** | Remotion/HyperFrames 需要 Node.js,增加 ContentForge 桌面应用的部署复杂度 | +| **API 密钥管理** | OpenMontage 依赖大量外部 API,需与 ContentForge 的 Provider 配置体系统一 | +| **维护成本** | OpenMontage 更新频繁(GitHub Trending 项目),需评估长期维护成本 | + +--- + +### 7. 总结 + +OpenMontage 是一个在 AI 视频生产领域具有前瞻性的开源项目,其 **Agent-First 架构**、**三层知识系统**、**质量门控机制**和**多提供商评分选择**等设计理念对 ContentForge 具有高度参考价值。 + +对于 ContentForge 而言,最具整合价值的领域包括: +1. **视频合成引擎** — Remotion/HyperFrames 可填补 ContentForge 在视频输出方面的空白 +2. **视频分析/转录** — 工具设计模式和工作流可直接借鉴 +3. **技能系统架构** — 三层知识架构可提升 ContentForge 的 Agent 能力 +4. **质量治理** — 交付承诺验证和决策审计机制可提升内容质量 + +建议以**架构借鉴 + 选择性模块集成**的方式推进整合,优先验证 Remotion 视频渲染和视频分析管道的可行性,同时关注 AGPLv3 许可证合规性。 diff --git a/contentforge/external-repos/Video-Note-Extractor b/contentforge/external-repos/Video-Note-Extractor new file mode 160000 index 0000000..15577ed --- /dev/null +++ b/contentforge/external-repos/Video-Note-Extractor @@ -0,0 +1 @@ +Subproject commit 15577ed60525c13f77381d61d0c8aa3c7e7a5685 diff --git a/contentforge/external-repos/Video-Note-Extractor_ANALYSIS.md b/contentforge/external-repos/Video-Note-Extractor_ANALYSIS.md new file mode 100644 index 0000000..67f14c9 --- /dev/null +++ b/contentforge/external-repos/Video-Note-Extractor_ANALYSIS.md @@ -0,0 +1,193 @@ +## Video-Note-Extractor 仓库分析 + +> 分析日期:2025-07-25 +> 分析师:技术分析师 +> 仓库路径:`/Users/patrick/innate/projects/innate-keepthem/contentforge/external-repos/Video-Note-Extractor/` + +--- + +### 1. 项目概述 + +**AI Video Note Extractor** 是一个基于 Python 的轻量级脚本工具,旨在将本地视频文件自动转换为结构化笔记。其核心工作流程为:从视频提取音频 → 使用 OpenAI Whisper 进行语音转录 → 调用 Google Gemini AI 分析转录内容并生成结构化笔记(包含摘要、关键点、行动项、重要概念)。 + +该项目为纯命令行脚本,无图形界面,无 Web 服务,代码总量不足 100 行,属于原型级(Proof-of-Concept)项目。 + +--- + +### 2. 功能分析 + +#### 2.1 核心功能 + +| 功能模块 | 实现文件 | 说明 | +|---------|---------|------| +| 音频提取 | `audio_extractor.py` | 使用 MoviePy 从本地视频文件中提取音频为 WAV 格式 | +| 语音转录 | `transcriber.py` | 使用 OpenAI Whisper(`base` 模型)将音频转为纯文本 | +| AI 笔记生成 | `summarizer.py` | 使用 Google Gemini 2.5 Flash 分析转录文本,生成结构化笔记 | +| 主流程编排 | `main.py` | 串联上述三个步骤,处理用户输入并将结果保存到文件 | + +#### 2.2 工作流程 + +``` +用户输入本地视频路径 + ↓ +[audio_extractor.py] MoviePy + FFmpeg 提取音频 → audio/extracted_audio.wav + ↓ +[transcriber.py] Whisper base 模型转录 → transcripts/transcript.txt + ↓ +[summarizer.py] Gemini API 生成笔记 → notes/notes.txt + ↓ +输出:完整转录文本 + 结构化笔记 +``` + +#### 2.3 输出格式 + +生成的笔记包含以下结构化内容: +1. **Summary(摘要)** — 视频内容概述 +2. **Key Points(关键点)** — 核心要点提炼 +3. **Action Items(行动项)** — 可执行任务 +4. **Important Concepts(重要概念)** — 关键概念解释 + +#### 2.4 局限性 + +- **仅支持本地视频文件**,无 YouTube/网络视频下载能力 +- **无用户界面**,纯命令行交互 +- **硬编码配置**,API Key 直接写入 `config.py` +- **无错误处理**,各环节失败无重试或降级机制 +- **无并发/队列能力**,单文件串行处理 +- **输出格式单一**,仅生成纯文本笔记 +- **转录模型固定**,仅使用 Whisper `base` 模型,无模型选择能力 + +--- + +### 3. 技术栈 + +#### 3.1 编程语言与运行时 +- **Python 3**(具体版本未指定,推测 3.8+) + +#### 3.2 核心依赖 + +| 依赖包 | 版本 | 用途 | +|-------|------|------| +| `moviepy` | 未锁定 | 视频处理与音频提取 | +| `openai-whisper` | 未锁定 | 本地语音识别与转录 | +| `google-generativeai` | 未锁定 | Google Gemini API 调用 | + +#### 3.3 外部系统依赖 +- **FFmpeg** — MoviePy 的底层依赖,用于音视频编解码 + +#### 3.4 AI 模型 +- **OpenAI Whisper**(`base` 模型,本地运行) +- **Google Gemini 2.5 Flash**(云端 API 调用) + +#### 3.5 架构特征 +- 无架构设计,纯过程式脚本 +- 无类型注解、无单元测试、无日志系统 +- 无配置管理(环境变量/配置文件) +- 无依赖注入、无模块化设计 + +--- + +### 4. 文件结构 + +``` +Video-Note-Extractor/ +├── README.md # 项目说明(8 行) +├── requirements.txt # Python 依赖(3 个包) +├── .gitignore # Git 忽略规则 +├── config.py # API Key 配置(硬编码,含泄露风险) +├── main.py # 主流程入口(26 行) +├── audio_extractor.py # 音频提取模块(6 行) +├── transcriber.py # 语音转录模块(6 行) +├── summarizer.py # 笔记生成模块(33 行) +├── videos/ # 视频输入目录(含 test.mp4) +├── audio/ # 音频输出目录(含 extracted_audio.wav) +├── notes/ # 笔记输出目录(含 notes.txt) +└── .git/ # Git 版本控制 +``` + +**代码统计**: +- Python 源文件:7 个(含 config.py) +- 总代码行数:约 80 行(不含空行/注释) +- 核心逻辑行数:约 50 行 + +--- + +### 5. 与 ContentForge 整合评估 + +#### 5.1 整合价值判断:**低** + +#### 5.2 评估维度 + +| 维度 | ContentForge | Video-Note-Extractor | 匹配度 | +|------|-------------|---------------------|--------| +| **目标平台** | Tauri 桌面应用 + Web | 纯 Python CLI 脚本 | ❌ 不匹配 | +| **后端语言** | Rust + Node.js | Python | ❌ 不匹配 | +| **前端框架** | React 19 + Next.js | 无 | ❌ 不匹配 | +| **YouTube 下载** | 核心功能(yt-dlp 集成) | 不支持 | ❌ 无此能力 | +| **视频分析/转录** | 已有/计划功能 | 支持(Whisper) | ⚠️ 功能重叠 | +| **AI 内容生成** | 核心功能(多 Provider) | 支持(Gemini only) | ⚠️ 功能重叠 | +| **多 Agent 对话** | 核心架构 | 无 | ❌ 不匹配 | +| **Skill 系统** | 核心扩展机制 | 无 | ❌ 不匹配 | +| **内容输出** | Markdown/Notes/XHS/Slides/Video | 纯文本 | ⚠️ 输出格式单一 | +| **工程成熟度** | 生产级(单元测试、CI/CD、类型安全) | 原型级(无测试、无类型) | ❌ 差距大 | + +#### 5.3 功能重叠分析 + +ContentForge 已具备或正在规划以下重叠功能: +- **视频下载**:vYtDL 组件已集成 yt-dlp,支持 YouTube 及多平台下载 +- **音频提取**:FFmpeg 已在 ContentForge 依赖中 +- **语音转录**:可集成 Whisper(本地)或各 AI Provider 的语音 API +- **AI 笔记生成**:ContentForge 的多 Agent 系统可完成更复杂的分析任务 +- **结构化输出**:ContentForge 的 Skill 系统支持多种输出格式(Markdown、XHS、Slides 等) + +#### 5.4 不可复用因素 + +1. **技术栈完全不兼容**:Python 脚本无法直接整合到 Rust + React 的 Tauri 应用中 +2. **代码质量不适合生产**:无错误处理、无测试、硬编码配置、API Key 泄露风险 +3. **架构层级不符**:ContentForge 采用模块化 Skill 系统,该项目为紧耦合脚本 +4. **功能覆盖不足**:缺少 YouTube 下载(ContentForge 的核心场景)、缺少 UI、缺少多格式输出 + +--- + +### 6. 整合建议 + +#### 6.1 直接代码复用:不建议 + +该项目的代码量极少(约 50 行核心逻辑),且技术栈、架构、工程标准与 ContentForge 差距过大,**不建议直接引入任何源代码**。 + +#### 6.2 可借鉴的设计思路 + +虽然代码本身不适合复用,但以下**设计思路**可作为 ContentForge 功能规划的参考: + +| 借鉴点 | 说明 | 在 ContentForge 中的实现建议 | +|-------|------|---------------------------| +| **转录 → 结构化笔记流水线** | 视频→音频→转录→AI分析→结构化输出 | 在 ContentForge 中作为一个标准 Skill(如 `video-to-notes`)实现 | +| **Gemini 提示词模板** | 摘要/关键点/行动项/重要概念的四段式结构 | 可作为 ContentForge AI 笔记生成的默认 Prompt 模板,支持用户自定义 | +| **本地 Whisper 转录** | 使用本地模型降低 API 成本 | ContentForge 可支持「本地 Whisper / 云端 API」双模式转录 | + +#### 6.3 建议的 ContentForge 实现方案 + +若 ContentForge 需要实现类似功能,建议按以下方式自行构建: + +``` +ContentForge Skill: video-to-notes +├── 输入:YouTube URL / 本地视频文件 +├── 步骤 1:下载视频(复用 vYtDL / yt-dlp) +├── 步骤 2:提取音频(复用 FFmpeg,Rust 调用) +├── 步骤 3:语音转录(Skill 配置:whisper-local / openai / gemini / kimi) +├── 步骤 4:AI 分析(多 Agent 协作,可扩展提示词模板) +└── 输出:Markdown 笔记 / XHS 文案 / Slides 大纲 / 其他格式 +``` + +#### 6.4 总结 + +**Video-Note-Extractor 是一个有价值的概念验证(PoC),展示了「视频转录 + AI 笔记生成」的工作流,但其代码本身不适合直接整合到 ContentForge 中。** + +建议将其视为**需求参考**而非**代码来源**: +- 其提示词模板结构(摘要/关键点/行动项/重要概念)可用于设计 ContentForge 的默认笔记生成模板 +- 其「本地 Whisper + 云端 Gemini」的双模型策略可作为 ContentForge 转录功能的设计参考 +- 不需要 Fork、不需要引入代码,仅需记录其工作流设计理念即可 + +--- + +> **结论**:该仓库整合价值为 **低**,不建议进行代码层面的整合。建议将其作为「视频转 AI 笔记」功能的需求参考,在 ContentForge 中以更高工程标准、更完善架构重新实现。 diff --git a/contentforge/external-repos/capsummarize b/contentforge/external-repos/capsummarize new file mode 160000 index 0000000..82c28f4 --- /dev/null +++ b/contentforge/external-repos/capsummarize @@ -0,0 +1 @@ +Subproject commit 82c28f42c41086fb7a910b766f3164a4cfb63b2a diff --git a/contentforge/external-repos/capsummarize_ANALYSIS.md b/contentforge/external-repos/capsummarize_ANALYSIS.md new file mode 100644 index 0000000..2e690cf --- /dev/null +++ b/contentforge/external-repos/capsummarize_ANALYSIS.md @@ -0,0 +1,328 @@ +## capsummarize 仓库分析 + +**分析日期**: 2026-07-25 +**仓库路径**: `/Users/patrick/innate/projects/innate-keepthem/contentforge/external-repos/capsummarize/` +**版本**: 1.1.0 +**许可证**: MIT + +--- + +### 1. 项目概述 + +**CapSummarize** 是一个免费的、开源的 Chrome 浏览器扩展,核心功能是从在线视频(YouTube、Google Drive、Udemy、Zoom 等)的字幕/转录文本中提取内容,并利用用户选择的 AI Provider 生成多种格式的 AI 摘要、缩略图和视频片段。 + +**核心定位**: 浏览器内的视频内容再创作助手 —— 将视频转录文本转化为结构化内容(摘要、博客、笔记、社交媒体文案、图像、短视频脚本等)。 + +**关键特点**: +- **纯前端架构**: 无后端服务器,所有 prompt 模板本地打包,数据处理在浏览器内完成 +- **隐私优先**: 不收集用户数据,字幕拦截和转录提取完全本地执行 +- **多 Provider 支持**: 通过浏览器标签页注入的方式与 11 个 AI 聊天服务集成 +- **免费无限制**: 通过本地 prompt 模板 + 用户自有 AI 账户实现零成本使用 + +--- + +### 2. 功能分析 + +#### 2.1 核心功能模块 + +| 模块 | 功能描述 | 实现方式 | +|------|----------|----------| +| **字幕拦截提取** | 拦截视频页面的 VTT/WebVTT 字幕请求 | `interceptor.ts` 覆盖 `fetch()` 和 `XMLHttpRequest` | +| **VTT 解析** | 将 VTT 格式转为纯文本,支持 YouTube JSON 格式转 VTT | `vtt.ts` + `vtt-connectors.ts`(Connector 模式) | +| **文本摘要生成** | 15+ 种风格的 AI 文本摘要 | 本地 prompt 模板 → 注入 AI Provider 页面 | +| **AI 图像生成** | 基于转录内容生成缩略图、信息图等 | 支持 ChatGPT/Gemini/Grok,可选参考图 | +| **AI 视频生成** | 基于转录内容生成短视频片段 | 仅 Gemini 支持(8秒限制) | +| **Provider 注入** | 将 prompt 自动填入 AI 聊天页面并提交 | `chrome.scripting.executeScript` DOM 操作 | +| **历史记录** | 保存已提取的字幕和生成的摘要 | IndexedDB 本地存储 | +| **自定义模板** | 用户可创建自己的摘要风格模板 | IndexedDB + 动态加载 | + +#### 2.2 支持的摘要风格(文本类) + +| 风格 | 用途 | 输出格式 | +|------|------|----------| +| `default` | 通用平衡摘要 | 结构化 Markdown | +| `educational` | 教育/学习用途 | 含学习目标、知识点、自测题 | +| `technical` | 技术内容 | 含代码块、实现步骤、最佳实践 | +| `executive` | 高管简报 | 决策导向、行动项、风险评估 | +| `marketing` | 营销文案 | 转化导向、CTA、情感诉求 | +| `news` | 新闻报道 | 倒金字塔结构、客观事实 | +| `blog` | SEO 博客文章 | 含标题优化、代码示例、Mermaid 图 | +| `youtube` | YouTube 章节 | 带时间戳的章节导航 | +| `podcast` | 播客笔记 | 嘉宾信息、亮点时刻、引用 | +| `cheatsheet` | 技术速查表 | 卡片式 Markdown | +| `x` / `shorts` | 社交媒体 | Twitter 线程 / 短视频脚本 | +| `casual` / `kids` | 口语化/儿童友好 | 轻松叙事风格 | +| `recap` / `interview` | 快速回顾 / 面试准备 | 60秒回顾 / Q&A 格式 | + +#### 2.3 支持的视觉生成风格 + +| 风格 | 用途 | +|------|------| +| `thumbnail` (+ 6 种子风格) | YouTube 缩略图(MrBeast/Casey/Tech/DIY 等风格) | +| `infographic` | 信息图 | +| `comic` | 漫画风格故事 | +| `mindmap` | 思维导图 | +| `whiteboard` | 手绘白板风格 | +| `quote-card` | 金句卡片 | +| `scene` | 关键场景可视化 | + +--- + +### 3. 技术栈 + +#### 3.1 开发技术栈 + +| 层级 | 技术 | 版本/说明 | +|------|------|----------| +| **语言** | TypeScript | 5.9.3 | +| **构建工具** | Bun | 主要构建工具(也支持 npm) | +| **打包工具** | Bun Bundler | `--target browser` 模式 | +| **样式** | Tailwind CSS | v4.1.17 + @tailwindcss/postcss | +| **代码规范** | ESLint + Prettier | 标准配置 | +| **Chrome API** | Manifest V3 | Service Worker + Content Script + Side Panel | +| **存储** | IndexedDB | 自定义封装(大容量数据) | + +#### 3.2 Chrome 扩展架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Browser Page (youtube.com, etc.) │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ interceptor.js │ │ content.js │ │ +│ │ (injected into │ │ (content script)│ │ +│ │ page context) │ │ │ │ +│ │ • Override fetch│ │ • Inject │ │ +│ │ • Override XHR │ │ interceptor │ │ +│ │ • Post VTT data │→ │ • Relay to BG │ │ +│ └─────────────────┘ └────────┬────────┘ │ +│ │ postMessage │ +└────────────────────────────────┼────────────────────────────┘ + │ chrome.runtime.sendMessage +┌────────────────────────────────┼────────────────────────────┐ +│ Extension Background │ │ +│ ┌─────────────────────────────┴──────────┐ │ +│ │ background.ts (Service Worker) │ │ +│ │ • VTT Cache Manager │ │ +│ │ • Message Router │ │ +│ │ • Side Panel State │ │ +│ │ • History Management │ │ +│ └────────────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────┴────────────────┐ │ +│ │ Side Panel UI (sidepanel.html/ts) │ │ +│ │ • Variant Selector (Custom Dropdown) │ │ +│ │ • Provider Buttons │ │ +│ │ • Output Type Toggle (text/image/video) │ │ +│ │ • Aspect Ratio Toggle (wide/vertical) │ │ +│ │ • Reference Image Upload │ │ +│ └─────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 3.3 关键设计模式 + +- **Connector 模式** (`vtt-connectors.ts`): 可扩展的 VTT 格式解析器,当前支持 YouTube JSON 格式和标准 VTT +- **Prompt 模板系统** (`prompts.ts`): 集中管理 28+ 种 prompt 模板,通过 `{transcript}` 占位符注入转录文本 +- **Provider 配置驱动** (`providers.ts`): 每个 AI 服务通过 DOM selector 配置(prompt 输入框、提交按钮、前置操作),实现统一的注入逻辑 +- **Variant 缓存分层** (`variantsCache.ts` / `promptHelpers.ts`): 系统模板 + 用户自定义模板的分层加载 + +--- + +### 4. 文件结构 + +``` +capsummarize/ +├── src/ +│ ├── background.ts # Service Worker 入口 +│ ├── background/ +│ │ ├── messageHandlers.ts # 消息分发器 +│ │ ├── sidePanelManager.ts # Side Panel 状态管理 +│ │ ├── storageHelpers.ts # 存储工具 +│ │ └── vttCacheManager.ts # VTT 缓存管理 +│ ├── content.ts # Content Script(注入 interceptor) +│ ├── interceptor.ts # 页面内脚本:拦截 VTT 请求 +│ ├── floating-icon.ts # 浮动图标(页面内提示) +│ ├── manifest.ts # Manifest V3 配置生成器 +│ ├── config/ +│ │ ├── prompts.ts # 28+ AI Prompt 模板(~2178 行) +│ │ ├── promptHelpers.ts # Prompt 模板辅助函数 +│ │ ├── prePromptScripts.ts # Provider 前置操作脚本 +│ │ └── providers.ts # AI Provider 配置(URL + Selector) +│ ├── services/ +│ │ └── providerService.ts # Provider 注入服务(DOM 操作核心) +│ ├── ui/ +│ │ ├── components/ +│ │ │ ├── HistoryList.ts # 历史记录列表组件 +│ │ │ └── SettingsForm.ts # 设置表单组件 +│ │ ├── state/ +│ │ │ └── sidePanelState.ts # Side Panel 状态 +│ │ ├── sidepanel.ts # Side Panel 主逻辑(~1803 行) +│ │ ├── settings.ts # 设置页面逻辑 +│ │ ├── ui-utils.ts # UI 工具函数 +│ │ ├── sidepanel.html # Side Panel HTML +│ │ ├── settings.html # 设置页面 HTML +│ │ └── llm-providers/ # Provider 图标资源 +│ ├── utils/ +│ │ ├── vtt.ts # VTT 文本提取 + 缩略图检测 +│ │ ├── vtt-connectors.ts # VTT Connector 注册表 +│ │ ├── vttHistory.ts # VTT 历史记录 +│ │ ├── storage.ts # IndexedDB 封装 +│ │ ├── variantsCache.ts # Variant 加载器 +│ │ ├── promptsCache.ts # Prompt 使用统计 +│ │ ├── security.ts # XSS 过滤 + Prompt 验证 +│ │ ├── logger.ts # 日志工具 +│ │ ├── caption-enabler.ts # 自动启用字幕 +│ │ └── constants.ts # 全局常量 +│ ├── types/ # TypeScript 类型定义 +│ └── styles/ +│ └── globals.css # Tailwind 全局样式 +├── examples/ # 示例输出 +│ ├── summaries/ # 各风格摘要示例 +│ └── images/ # 生成图像示例 + Prompt +├── icons/ # 扩展图标 +├── scripts/ +│ └── build-with-env.js # 环境变量注入构建脚本 +├── build-css.js # CSS 构建脚本 +├── package.json # 项目配置(Bun 优先) +├── tailwind.config.js # Tailwind 配置 +├── tsconfig.json # TypeScript 配置 +├── wrangler.jsonc # Cloudflare Wrangler 配置 +├── AGENTS.md # AI Agent 上下文 +├── CONTRIBUTING.md # 贡献指南 +├── LICENSE # MIT 许可证 +└── README.md # 项目文档 +``` + +--- + +### 5. 与 ContentForge 整合评估 + +#### 5.1 整合价值总评: **高** + +CapSummarize 与 ContentForge 在**视频内容分析 → AI 内容生成**这条核心工作流上高度对齐。虽然两者的运行时环境不同(Chrome 扩展 vs Tauri 桌面应用),但 CapSummarize 的 **Prompt 模板系统**、**内容风格体系**和**VTT 处理逻辑**是可直接迁移的高价值资产。 + +#### 5.2 功能重叠度分析 + +| ContentForge 功能 | CapSummarize 对应功能 | 重叠度 | +|-------------------|----------------------|--------| +| YouTube 视频下载 | 字幕/转录提取 | 互补(下载 vs 在线分析) | +| 视频分析/转录 | VTT 解析 + 字幕拦截 | **高度重叠** | +| AI 内容生成 | 15+ 风格摘要 + 图像/视频生成 | **高度重叠** | +| 多 Agent 对话 | 单 Provider 注入模式 | 低重叠(架构不同) | +| Skill 系统 | Prompt 模板 + 自定义 Variant | **高度可借鉴** | +| Markdown/Notes/XHS/Slides/Video 输出 | Blog/X/Twitter/YouTube/图像/视频 | **输出类型对齐** | +| Tauri + Rust 后端 | Chrome 扩展架构 | 不重叠(需适配) | + +--- + +### 6. 整合建议 + +#### 6.1 高优先级 — 直接复用/移植 + +**1. Prompt 模板系统 → ContentForge Skill 系统** +- **复用范围**: `src/config/prompts.ts` 中全部 28+ 种 prompt 模板 +- **移植方式**: 将 prompt 模板迁移为 ContentForge 的 Skill 定义格式(JSON/YAML + 模板字符串) +- **适配点**: + - 将 `{transcript}` 占位符替换为 ContentForge 的输入管道变量 + - 增加 `systemPrompt` / `userPrompt` 分离以适配多 Agent 架构 + - 为每种风格添加 Skill 元数据(icon、description、outputFormat、tags) +- **预期收益**: 直接获得 15+ 种成熟的文本生成风格和 12+ 种视觉生成风格,省去大量 prompt 工程工作 + +**2. VTT 解析逻辑 → 视频转录处理模块** +- **复用范围**: `src/utils/vtt.ts` + `src/utils/vtt-connectors.ts` +- **移植方式**: 将浏览器环境下的 VTT 解析提取为纯 TypeScript 工具函数 +- **适配点**: + - 移除浏览器相关依赖(`window`、DOM 操作) + - 保留 `extractTextFromVTT()` 和 `isThumbnailVTT()` 核心逻辑 + - Connector 注册表模式可直接复用,方便未来支持更多平台格式 +- **预期收益**: 获得经过实战验证的 YouTube/Zoom/Udemy 多平台字幕解析能力 + +**3. Provider 配置模式 → AI Provider 统一配置** +- **复用范围**: `src/config/providers.ts` 的配置结构理念 +- **移植方式**: 参考其 `ProviderConfig` 接口设计 ContentForge 的 Provider 配置 schema +- **适配点**: + - CapSummarize 使用 DOM selector 注入(浏览器特有),ContentForge 应改为 API/IPC 调用模式 + - 保留 `textConfig` / `imageConfig` / `videoConfig` 的分层配置思想 + - 参考其 `isImageCapableProvider()` / `isVideoCapableProvider()` 的能力检测模式 +- **预期收益**: 建立统一的 Provider 能力声明体系,避免在 Tauri 端硬编码 Provider 能力 + +#### 6.2 中优先级 — 设计参考/部分复用 + +**4. UI 交互模式 → ContentForge 前端组件** +- **参考范围**: `src/ui/sidepanel.ts` 中的输出类型切换、风格选择器、参考图上传 +- **可借鉴设计**: + - Output Type Toggle(文本/图像/视频三段式切换)+ 条件渲染对应选项 + - Custom Dropdown 组件(带搜索/分类/自定义标签) + - 参考图上传 → 预览 → 删除的完整交互流 + - Provider 能力过滤(根据 outputType 动态显示可用 Provider) +- **移植方式**: 将 DOM 操作逻辑改写为 React 组件状态管理 + +**5. 历史记录与存储模式** +- **参考范围**: `src/utils/storage.ts`(IndexedDB 封装)+ `src/utils/vttHistory.ts` +- **可借鉴点**: + - 大容量数据(转录文本)与小数据(设置)分层存储策略 + - 带时间戳的数据版本管理 + - 过期数据自动清理机制 +- **移植方式**: 将 IndexedDB 逻辑替换为 Tauri SQLite/文件系统存储 + +**6. 安全过滤机制** +- **复用范围**: `src/utils/security.ts` +- **移植方式**: 直接复用 `sanitizeInput()`、`sanitizeVTTContent()`、`isValidPrompt()` 三个函数 +- **预期收益**: 获得经过 Chrome Web Store 安全审核验证的输入过滤逻辑 + +#### 6.3 低优先级 — 架构差异较大 + +**7. Chrome 扩展特有模块 — 不可直接复用** + +| 模块 | 不可复用原因 | 替代方案(ContentForge) | +|------|------------|----------------------| +| `interceptor.ts` | 依赖 `window.fetch`/`XMLHttpRequest` 覆盖和 `window.postMessage` | 在 Tauri 中通过 Rust 后端调用 yt-dlp 提取字幕 | +| `content.ts` | Content Script 是 Chrome 扩展特有概念 | 无需对应,ContentForge 是桌面应用 | +| `background.ts` | Service Worker 生命周期和 API 与 Tauri 完全不同 | Tauri 的主进程(Rust)+ 事件系统 | +| `providerService.ts` 注入逻辑 | 依赖 `chrome.scripting.executeScript` 和 DOM 操作 | 改为直接调用各 Provider API(OpenAI/Claude/Gemini 等 SDK) | +| Side Panel UI | Chrome Side Panel API 特有 | 改为 ContentForge 的 React 页面路由 | + +#### 6.4 推荐的整合实施路径 + +``` +Phase 1: Prompt 资产迁移(1-2 天) +├── 提取 prompts.ts 中所有 prompt 模板 +├── 转换为 ContentForge Skill 格式 +├── 建立 Skill 分类体系(text / image / video) +└── 在 ContentForge 中注册并测试输出质量 + +Phase 2: VTT 工具函数移植(1 天) +├── 提取 vtt.ts 和 vtt-connectors.ts 核心逻辑 +├── 移除浏览器依赖,转为纯 Node.js/TypeScript 模块 +├── 集成到 ContentForge 的视频分析管道 +└── 测试多平台字幕解析(YouTube/Zoom/Udemy) + +Phase 3: Provider 配置适配(1 天) +├── 参考 providers.ts 设计 ContentForge Provider schema +├── 实现 Provider 能力声明和过滤 +└── 将注入模式改为 API 调用模式 + +Phase 4: UI 组件开发(2-3 天) +├── 基于 sidepanel.ts 交互设计开发 React 组件 +├── 实现风格选择器、输出类型切换、参考图上传 +└── 集成到 ContentForge 的下载/分析结果页面 +``` + +#### 6.5 整合注意事项 + +1. **Prompt 版权**: CapSummarize 使用 MIT 许可证,prompt 模板可直接复用,建议保留原作者致谢 +2. **Provider 注入 vs API 调用**: CapSummarize 的 Provider 注入模式(打开网页自动填 prompt)是其架构限制下的巧妙方案。ContentForge 作为桌面应用,应直接使用各 Provider 的官方 SDK/HTTP API,体验更佳 +3. **图像/视频生成**: CapSummarize 的图像/视频 prompt 模板质量很高,但 ContentForge 需要集成对应的图像生成 API(DALL-E、Midjourney API、Stable Diffusion 等)而非依赖网页注入 +4. **历史数据不兼容**: CapSummarize 的 IndexedDB 数据格式与 ContentForge 的 SQLite schema 不同,历史记录无法直接迁移 + +--- + +### 附录:Prompt 模板完整清单 + +**文本摘要(16 种)**: `default`, `educational`, `technical`, `executive`, `marketing`, `news`, `podcast`, `kids`, `blog`, `youtube`, `cheatsheet`, `recap`, `interview`, `x`, `shorts`, `casual` + +**图像生成(12 种)**: `thumbnail`, `thumbnail-mrbeast`, `thumbnail-casey`, `thumbnail-theo`, `thumbnail-5min`, `thumbnail-tweet`, `infographic`, `comic`, `mindmap`, `whiteboard`, `quote-card`, `scene` + +**视频生成(6 种)**: `video-ad`, `video-trailer`, `video-recap`, `video-explainer`, `video-cinematic`, `video-social` + +**总计**: 34 种内置模板 + 用户自定义模板扩展 diff --git a/contentforge/external-repos/frameflow b/contentforge/external-repos/frameflow new file mode 160000 index 0000000..9354bed --- /dev/null +++ b/contentforge/external-repos/frameflow @@ -0,0 +1 @@ +Subproject commit 9354bedda7d2b72c3787d842e13e93e0130b0e91 diff --git a/contentforge/external-repos/frameflow_ANALYSIS.md b/contentforge/external-repos/frameflow_ANALYSIS.md new file mode 100644 index 0000000..d097d45 --- /dev/null +++ b/contentforge/external-repos/frameflow_ANALYSIS.md @@ -0,0 +1,335 @@ +## frameflow 仓库分析 + +> 分析日期:2025-07-25 +> 仓库路径:`/Users/patrick/innate/projects/innate-keepthem/contentforge/external-repos/frameflow/` +> 版本:v1.1.16 +> 来源:https://github.com/navidshad/frameflow + +--- + +### 1. 项目概述 + +**FrameFlow** 是一个 AI 驱动的多媒体视频/图像处理平台,定位为"高保真多媒体创作工作台"。它桥接了原始视频/图像素材与创意智能之间的差距,通过融合 **Google Gemini 多模态 AI** 与精确的 **FFmpeg 工程**,实现视频内容的智能分析、提取和生成。 + +项目由 Vilnius Gediminas Technical University (VGTU) 的学生团队开发,采用 MIT 许可证开源。 + +**核心定位**:不同于传统手动剪辑软件,FrameFlow 是一个 **AI Video Editor** —— 用户通过自然语言对话与 AI 交互,系统自动完成视频分析、场景检测、时间线生成和视频组装。 + +**三大支柱功能**: +1. **🎞️ Video to Short Video**:将长视频(如 2 小时技术讲座)智能压缩为精简高光片段(如 5 分钟学习指南) +2. **📸 Video to Thumbnail**:从视频中自动提取最具代表性的帧,生成 YouTube 级别缩略图 +3. **🎨 Images to Image**:利用多模态提示词,基于现有图像生成新图像或进行风格转换 + +**最新重大迭代**:项目已从纯 AI 对话式编辑扩展为 **双模式并存** —— 在原有 AI 图节点编辑器之外,新增了一个类 Premiere/CapCut 的**传统时间线视频编辑器**(timeline video editor),支持多轨道、拖拽剪辑、AI Persona 驱动编辑等高级功能。 + +--- + +### 2. 功能分析 + +#### 2.1 视频处理 Pipeline(5 阶段) + +FrameFlow 的核心是一组模块化的阶段式流水线: + +| 阶段 | 功能 | 关键技术 | 对应文件 | +|------|------|----------|----------| +| **Phase 1: 预处理** | 视频降采样(480p 代理)、音频提取(MP3)、原始转录生成 | FFmpeg, Gemini 2.5 Flash Lite | `extraction.ts` | +| **Phase 2: 意图识别** | 判断用户请求类型(纯聊天 / 生成视频 / 生成缩略图 / 图像生成) | Gemini 2.5 Flash, 状态机 | `intent.ts` | +| **Phase 3: 上下文丰富** | 转录校正、场景检测(PySceneDetect)、视觉描述生成 | PySceneDetect, Gemini Flash Lite | `extraction.ts` | +| **Phase 4: 时间线生成** | AI 导演模式:搜索最相关片段,生成 Timeline JSON 蓝图 | Gemini 3 Flash Preview | `generation.ts` | +| **Phase 5: 视频组装** | FFmpeg 复杂滤镜流处理,硬件加速编码(Mac VideoToolbox) | FFmpeg complex filter | `assembly.ts` | + +**特色能力**: +- **迭代编辑**:支持基于已有时间线进行增量修改("把第 3 段再缩短一点"),AI 仅修改指定部分 +- **智能代理**:高分辨率视频自动降采样到 480p 进行 AI 分析,既保证速度又保留元数据 +- **多模态融合**:同时利用音频转录和视觉场景描述进行决策 + +#### 2.2 时间线视频编辑器(Timeline Video Editor) + +这是项目的最新重大功能(PRD 文档近 10 万字符),实现了传统 NLE(Non-Linear Editor)的核心能力: + +| 功能模块 | 说明 | +|----------|------| +| **媒体导入** | 本地文件导入 + URL/YouTube 下载(yt-dlp) | +| **每素材预处理** | 每个导入的媒体文件独立进行代理生成、场景检测、转录、描述提取 | +| **多轨道时间线** | 支持 video / audio / overlay / text 四种轨道类型 | +| **剪辑操作** | 拖拽、裁剪(trim)、分割(split)、波纹删除(ripple delete)、变速(retime)、吸附(snap) | +| **AI Persona 驱动编辑** | 可切换不同 AI 角色(长视频/摘要模式),通过自然语言指令编辑时间线 | +| **Revision Tree** | 分支式版本树,支持在不同编辑版本间切换对比 | +| **Undo/Redo** | 50 步环形缓冲区 + 稀疏关键帧快照 | +| **导出渲染** | 原始质量 / 预览质量双路径,FFmpeg 多源拼接 | +| **辅助工具** | 静音检测(silence finder)、filmstrip 缩略图条 | + +#### 2.3 图节点聊天界面(Graph Chat Interface) + +- 基于 **Vue Flow** 的可视化节点图 +- 每个对话/任务作为一个节点,支持并行任务和版本分支可视化 +- 实时显示 AI Token 使用量和处理成本 + +#### 2.4 YouTube / 在线视频下载 + +- 基于 `ytdlp-nodejs` 封装,支持分辨率选择 +- 支持 YouTube、Google Drive 和直接媒体 URL +- 内置依赖管理器,可自动下载 yt-dlp 二进制文件 + +#### 2.5 AI 图像生成与放大 + +- 支持 Gemini 3.1 Flash Image Preview 进行图像生成 +- 支持 2K/4K 创意放大(Creative Upscaling) +- 支持以现有帧作为结构参考进行新图像生成 + +--- + +### 3. 技术栈 + +#### 3.1 前端 + +| 技术 | 版本 | 用途 | +|------|------|------| +| **Vue 3** | ^3.4.21 | 前端框架 | +| **Vite** | ^5.1.6 | 构建工具 | +| **Vue Router** | ^4.3.0 | 路由管理 | +| **Pinia** | ^3.0.4 | 状态管理 | +| **Tailwind CSS** | ^3.4.3 | CSS 框架 | +| **PilotUI** | ^1.29.1 | 内部组件系统 | +| **Vue Flow** | ^1.48.2 | 节点图可视化 | +| **markdown-it** | ^14.1.1 | Markdown 渲染 | + +#### 3.2 桌面壳 + +| 技术 | 版本 | 用途 | +|------|------|------| +| **Electron** | ^28.2.0 | 桌面应用壳 | +| **electron-vite** | ^2.1.0 | Electron 构建 | +| **electron-builder** | ^24.13.3 | 打包分发 | +| **@electron-toolkit** | ^3.0.0 | Electron 工具集 | + +#### 3.3 后端/主进程 + +| 技术 | 版本 | 用途 | +|------|------|------| +| **Node.js** | ≥18 | 运行时 | +| **TypeScript** | ^5.4.2 | 类型系统 | +| **@google/genai** | ^1.40.0 | Google Gemini AI SDK | + +#### 3.4 外部依赖/二进制工具 + +| 工具 | 用途 | +|------|------| +| **FFmpeg** | 视频处理核心(降采样、音频提取、帧提取、视频组装、filmstrip、静音检测) | +| **ffprobe** | 视频元数据探测 | +| **PySceneDetect** | 场景边界检测(内容阈值算法) | +| **yt-dlp** | YouTube/在线视频下载 | + +#### 3.5 AI 模型配置 + +FrameFlow 针对不同的操作类型配置了不同的 Gemini 模型: + +| 操作类型 | 默认模型 | 说明 | +|----------|----------|------| +| 原始转录 | Gemini 2.5 Flash Lite | 最快、最便宜 | +| 校正转录 | Gemini 2.5 Pro | 高精度 | +| 意图识别 | Gemini 2.5 Flash | 平衡速度和质量 | +| 新时间线生成 | Gemini 3 Flash Preview | 最新模型 | +| 缩略图生成 | Gemini 3.1 Flash Image Preview | 图像生成专用 | +| 场景描述 | Gemini 2.5 Flash Lite | 批量处理 | +| 图像放大 | Gemini 3.1 Flash Image Preview | 创意重渲染 | +| 编辑器编辑 | Gemini 3 Flash Preview | 时间线编辑器 | + +#### 3.6 项目结构 + +``` +frameflow/ +├── docs/ # 文档(架构、UI/UX、截图) +│ ├── architecture.md # 技术架构深度文档 +│ ├── ui_ux.md # 设计原则文档 +│ ├── setup.md # 安装指南 +│ └── screenshots/ # 界面截图 +├── src/ +│ ├── main/ # Electron 主进程(Node.js 后端) +│ │ ├── constants/ # 常量配置(Gemini 模型、路径、Persona) +│ │ ├── dependencies/ # 外部依赖管理(yt-dlp 下载器) +│ │ ├── editor/ # ⭐ 时间线编辑器核心(资产、上下文、历史、渲染) +│ │ ├── ffmpeg/ # ⭐ FFmpeg 封装(视频处理全家桶) +│ │ ├── gemini/ # ⭐ Gemini AI 适配器(多模态调用) +│ │ ├── pipeline/ # ⭐ AI 处理流水线(5 阶段) +│ │ │ └── phases/ # 各阶段实现 +│ │ ├── scenedetect/ # ⭐ PySceneDetect 封装(场景检测) +│ │ ├── tasks/ # 后台任务管理器 +│ │ ├── threads/ # 线程/会话持久化 +│ │ ├── timeline/ # 时间线生成与丰富化 +│ │ ├── index.ts # 主进程入口(IPC 路由) +│ │ ├── settings.ts # 设置管理 +│ │ └── ytdlp.ts # ⭐ yt-dlp 下载封装 +│ ├── preload/ # Electron 预加载脚本 +│ ├── renderer/ # Electron 渲染进程(前端) +│ │ └── src/ +│ │ ├── components/ # Vue 组件(聊天、图节点) +│ │ ├── editor/ # 时间线编辑器 UI 组件 +│ │ ├── pages/ # 页面(Home、Chat、Settings、Editor) +│ │ ├── stores/ # Pinia Store +│ │ └── utils/ # 工具函数 +│ └── shared/ # 共享类型定义 +├── video-editor-prd.md # 时间线编辑器 PRD(近 10 万字符) +└── package.json +``` + +--- + +### 4. 文件结构 + +#### 4.1 核心可复用模块(按价值排序) + +| 文件/目录 | 行数 | 功能价值 | 复用难度 | +|-----------|------|----------|----------| +| `src/main/ffmpeg/index.ts` | ~645 | FFmpeg 全功能封装(降采样、音频提取、视频组装、帧提取、filmstrip、静音检测) | 中(需适配 Tauri) | +| `src/main/gemini/adapter.ts` | ~772 | Gemini AI 统一适配器(文本/结构化/文件/图像生成/放大/成本计算) | 低(纯 TS 逻辑) | +| `src/main/scenedetect/index.ts` | ~407 | PySceneDetect 跨平台封装(场景边界检测) | 中(需适配 Tauri 命令调用) | +| `src/main/ytdlp.ts` | ~215 | yt-dlp 下载封装(分辨率选择、进度回调、文件名规范化) | 低(纯 TS 逻辑) | +| `src/main/pipeline/phases/extraction.ts` | ~363 | 预处理全流程(降采样→音频→转录→校正→场景检测→描述生成) | 中(需适配数据流) | +| `src/main/pipeline/phases/generation.ts` | ~73 | 时间线生成 orchestration | 中 | +| `src/main/timeline/` | - | 时间线丰富化和生成逻辑 | 中 | +| `src/main/editor/` | - | 时间线编辑器核心(资产、渲染、历史、版本) | 高(架构可借鉴) | +| `src/shared/types.ts` | ~491 | 完整类型定义(Thread、Message、Timeline、Editor 文档模型) | 低(纯类型) | +| `video-editor-prd.md` | ~932 | 时间线编辑器产品需求文档 | 低(参考文档) | + +#### 4.2 技术债与限制 + +- **AI 供应商锁定**:深度绑定 Google Gemini,缺乏多 Provider 支持 +- **Electron 技术债**:基于 Electron 28(较旧版本),sandbox 已禁用 +- **Python 依赖**:PySceneDetect 需要外部 Python 环境,跨平台部署复杂 +- **单用户架构**:无多用户/协作支持,数据存储在本地文件系统 +- **Vue 3 生态**:与 ContentForge 的 React 19 生态不兼容,UI 层无法直接复用 + +--- + +### 5. 与 ContentForge 整合评估 + +#### 5.1 整体评估:**高** + +FrameFlow 与 ContentForge 在核心功能和愿景上有高度重叠,尽管技术栈不同,但其**算法逻辑、处理管道和架构模式**具有极高的复用价值。 + +#### 5.2 功能重叠矩阵 + +| ContentForge 功能 | FrameFlow 对应功能 | 重叠度 | 互补性 | +|-------------------|-------------------|--------|--------| +| YouTube 视频下载 | yt-dlp 下载(本地+URL) | 🔴 高 | ContentForge 已有成熟实现,可互相验证 | +| 视频分析/转录 | 5 阶段 Pipeline(转录+场景+描述) | 🔴 高 | FrameFlow 的 Pipeline 更成熟,可大幅增强 ContentForge | +| AI 内容生成 | Gemini 多模态生成(文本/图像/视频) | 🟡 中 | FrameFlow 聚焦视频,ContentForge 范围更广 | +| 多 Agent 对话 | 图节点对话界面 | 🟡 中 | 架构理念相似,可借鉴 | +| 技能(Skill)系统 | AI Persona 系统 | 🟡 中 | Persona ≈ Skill 的子集,可互相参考 | +| Tauri 桌面应用 | Electron 桌面应用 | 🟢 低 | 两者都是桌面应用但技术栈不同 | +| React 前端 | Vue 3 前端 | 🟢 低 | UI 层无法直接复用 | +| 内容输出(MD/Notes/XHS/Slides) | 视频/缩略图/图像输出 | 🟢 低 | 输出形式互补 | + +#### 5.3 整合价值分层 + +| 层级 | 价值 | 说明 | +|------|------|------| +| **🔴 高价值** | 视频处理 Pipeline | FFmpeg 封装、场景检测、转录逻辑可直接移植为 ContentForge 的"视频分析"Skill | +| **🔴 高价值** | 时间线编辑器架构 | PRD 和类型设计可作为 ContentForge 视频编辑功能的蓝本 | +| **🟡 中价值** | AI 调用模式 | Gemini 适配器的多模态调用模式、成本追踪、重试机制可参考 | +| **🟡 中价值** | Pipeline 架构模式 | 阶段式流水线+后台任务管理器的模式可借鉴到 ContentForge 的 Skill 系统 | +| **🟢 低价值** | UI 组件 | Vue 3 组件无法直接复用,但交互设计可参考 | +| **🟢 低价值** | Electron 代码 | Tauri 与 Electron 架构差异大,IPC 层需重写 | + +#### 5.4 风险评估 + +| 风险 | 等级 | 说明 | +|------|------|------| +| 技术栈差异 | ⚠️ 中 | Vue→React、Electron→Tauri 需要重写 UI 和 IPC 层 | +| AI 供应商锁定 | ⚠️ 中 | FrameFlow 深度绑定 Gemini,ContentForge 需适配为多 Provider | +| Python 依赖 | ⚠️ 中 | PySceneDetect 的 Python 依赖在 Tauri 环境中部署更复杂 | +| 维护状态 | ✅ 低 | 项目活跃(最新 v1.1.16),MIT 许可证友好 | +| 架构复杂度 | ⚠️ 中 | Pipeline + Editor 双模式架构较重,需有选择地吸收 | + +--- + +### 6. 整合建议 + +#### 6.1 直接复用/移植模块(优先级 P0) + +**① FFmpeg 视频处理封装 (`src/main/ffmpeg/index.ts`)** +- **复用方式**:将 TypeScript 逻辑移植为 ContentForge 的 Tauri Rust 后端命令或 Node 侧载进程 +- **复用范围**:`toLowResolution`(代理生成)、`toAudio`(音频提取)、`extractFrame`(帧提取)、`assembleVideo`(视频组装)、`detectSilence`(静音检测)、`generateFilmstrip`(缩略图条) +- **适配点**:ContentForge 已使用 FFmpeg(vYtDL 依赖),可将 frameflow 的高级封装逻辑整合为新的 **Video Processing Skill** + +**② yt-dlp 下载逻辑 (`src/main/ytdlp.ts`)** +- **复用方式**:下载逻辑、分辨率选择策略、进度回调机制、文件名规范化可直接借鉴 +- **复用范围**:`downloadVideo`、`getVideoFormats`、`getYtDlpBinaryPath` +- **适配点**:ContentForge 的 vYtDL 已有 yt-dlp 集成,可吸收 frameflow 的分辨率选择 UI 和下载进度流设计 + +**③ 场景检测封装 (`src/main/scenedetect/`)** +- **复用方式**:PySceneDetect 的跨平台路径解析、CSV 解析、场景数据结构 +- **复用范围**:`SceneDetector` 类、`Scene` 类型定义 +- **适配点**:作为 ContentForge 视频分析 Skill 的"场景检测"步骤 + +#### 6.2 架构参考/模式借鉴(优先级 P1) + +**④ Pipeline 流水线架构 (`src/main/pipeline/`)** +- **借鉴点**:阶段注册 → 条件跳过 → 信号中断 → 状态更新的流水线模式 +- **应用场景**:ContentForge 的 Skill 系统可引入类似的 Pipeline 概念,用于编排复杂的视频处理工作流 +- **关键设计**:`PipelineContext` 的 `updateStatus`/`waitForTask`/`recordUsage`/`signal` 设计模式 + +**⑤ 时间线数据模型 (`src/shared/types.ts` 中的 Editor 类型)** +- **借鉴点**:`EditorDocument`、`MediaAsset`、`Track`、`TimelineItem`、`Clip` 的层级设计 +- **应用场景**:为 ContentForge 未来可能增加的视频编辑功能提供数据模型蓝本 +- **关键设计**:Track(轨道)+ TimelineItem(时间线片段)+ Clip(源素材片段)的三层分离 + +**⑥ AI Persona / 角色系统 (`src/main/constants/personas.ts`)** +- **借鉴点**:可切换的 AI 编辑角色,每个角色携带 systemPrompt、默认参数、功能集 +- **应用场景**:与 ContentForge 的 Skill 系统结合,定义"视频摘要专家"、"缩略图设计师"等角色 + +**⑦ 成本追踪机制 (`GeminiAdapter.calculateCost`)** +- **借鉴点**:按模型、按 Token、按音频时长、按图像数量的精细化成本计算 +- **应用场景**:ContentForge 的 AI 调用层可引入类似的成本追踪和预算控制 + +#### 6.3 灵感/设计参考(优先级 P2) + +**⑧ 时间线视频编辑器 PRD (`video-editor-prd.md`)** +- **参考价值**:这是一个非常完整的时间线编辑器产品需求文档(近 10 万字符,932 行) +- **内容涵盖**:媒体导入、预处理模型、多轨道设计、AI 编辑流程、版本树、Undo/Redo、导出渲染 +- **建议**:作为 ContentForge 未来视频编辑功能规划的参考文档 + +**⑨ Vue Flow 图节点界面** +- **参考价值**:并行任务和版本分支的可视化管理 +- **建议**:ContentForge 的多 Agent 对话系统可借鉴类似的节点图可视化 + +**⑩ 后台任务管理器 (`src/main/tasks/index.ts`)** +- **参考价值**:预处理任务的异步执行、进度流、错误恢复 +- **建议**:ContentForge 的下载队列系统可参考其任务状态机设计 + +#### 6.4 不推荐的整合方向 + +| 方向 | 原因 | +|------|------| +| 直接复用 Vue 3 组件 | 技术栈不兼容(React 19 vs Vue 3) | +| 直接复用 Electron IPC 代码 | Tauri 的命令系统与 Electron IPC 完全不同 | +| 直接复用 Pinia Store | 状态管理需迁移到 Zustand | +| 全盘引入 Gemini 作为唯一 AI Provider | ContentForge 应保持多 Provider 架构 | + +#### 6.5 建议的整合路线图 + +``` +Phase 1: 快速收益(1-2 周) + ├── 吸收 yt-dlp 分辨率选择策略到 vYtDL 下载模块 + ├── 参考 GeminiAdapter 设计 ContentForge 的多模态 AI 调用接口 + └── 引入成本追踪模式到 AI 调用层 + +Phase 2: 能力增强(2-4 周) + ├── 移植 FFmpeg 封装逻辑为 Rust/Tauri 命令或 Node 进程 + ├── 将场景检测(PySceneDetect)集成为视频分析 Skill 的子步骤 + └── 实现 Pipeline 编排模式用于复杂视频处理工作流 + +Phase 3: 战略储备(未来) + ├── 基于 EditorDocument 类型设计视频编辑数据模型 + ├── 参考时间线编辑器 PRD 规划 ContentForge 视频编辑功能 + └── 引入 AI Persona 概念增强 Skill 系统的角色化 +``` + +--- + +### 7. 总结 + +FrameFlow 是一个**技术成熟、架构清晰、文档完善**的 AI 视频处理项目。虽然它与 ContentForge 采用不同的前端技术栈(Vue 3 vs React 19)和桌面框架(Electron vs Tauri),但其在**视频处理算法、AI 多模态调用、Pipeline 编排、时间线数据模型**等方面的积累具有极高的复用价值。 + +**整合价值:高** —— 建议以"吸收算法逻辑、借鉴架构模式、参考产品文档"的策略进行整合,而非直接代码迁移。特别是其 FFmpeg 封装、场景检测 Pipeline、时间线编辑器数据模型,可以显著增强 ContentForge 的视频分析和编辑能力。 diff --git a/contentforge/external-repos/skill-studio b/contentforge/external-repos/skill-studio new file mode 160000 index 0000000..cd0bb0a --- /dev/null +++ b/contentforge/external-repos/skill-studio @@ -0,0 +1 @@ +Subproject commit cd0bb0af53865d4a9643968080bfc5a8137b72d9 diff --git a/contentforge/external-repos/skill-studio_ANALYSIS.md b/contentforge/external-repos/skill-studio_ANALYSIS.md new file mode 100644 index 0000000..e9cdccd --- /dev/null +++ b/contentforge/external-repos/skill-studio_ANALYSIS.md @@ -0,0 +1,351 @@ +## skill-studio 仓库分析 + +> 分析时间:2026-06-07 +> 仓库路径:`/Users/patrick/innate/projects/innate-keepthem/contentforge/external-repos/skill-studio/` +> 分析师:技术分析师(Orchestrator Agent) + +--- + +### 1. 项目概述 + +**Skill Studio** 是一个**本地优先的桌面端 Skill 资产管理工具**,用于创建、导入、组织、版本化、对比和同步 AI Agent 技能。它将个人 Skill 工作区、版本快照、外部市场、平台目录、项目空间和团队交付流程整合到同一个应用中,让 Skill 从散落的本地文件变为可追踪、可回滚、可复用、可交付的资产。 + +**核心定位**: +- 面向 AI Agent 开发者的 Skill 资产管理 IDE +- 支持 **45+ 种 Agent 平台**(Cursor、Claude Code、Codex、Windsurf、Roo Code、Kimi Code CLI 等) +- 本地优先(Local-First):所有数据保存在 `~/.skill-studio/`,不依赖云服务 +- 当前版本 **v0.1.0**,已发布 Windows、macOS、Linux 跨平台预览版 + +**开源信息**: +- 许可证:Apache License 2.0 +- 仓库:`https://github.com/liu673/skill-studio` +- 作者:Jensen + +--- + +### 2. 功能分析 + +Skill Studio 包含 8 个核心功能模块: + +| 模块 | 功能描述 | +|---|---| +| **总览 (Dashboard)** | 个人 Skill、快照、团队待处理事项和核心资产状态一览 | +| **技能资产 (Skills Workspace)** | Skill 的增删改查、分类(Collection)、标签(Tag)、文件树浏览、外部编辑器集成 | +| **技能详情 (Skill Detail)** | 浏览文件树、读取和编辑文件、打开外部编辑器、打开所在目录 | +| **版本快照 (Snapshots)** | 创建快照、查看历史、对比版本差异(diff)、恢复工作副本、设置生效版本 | +| **市场与导入 (Market)** | 从本地目录、Git 仓库、内置模板和外部 Skill 市场(ClawHub、Skills.sh 等)导入资产 | +| **平台中心 (Platforms)** | 检测 Agent 平台目录、配置同步目录和同步模式(复制/符号链接) | +| **项目空间 (Projects)** | 为项目绑定 Skill 和平台目录,生成同步计划,执行项目级同步 | +| **团队空间 (Teams)** | 团队 Skill 库、提交(Submit)、差异评审、合并(Merge)、推荐版本和拉取(Pull) | + +**关键特性**: +- **快照版本管理**:每次快照保存完整目录副本(非增量),简化恢复逻辑 +- **平台同步**:支持复制模式(copy)和符号链接模式(symlink)同步 Skill 到各 Agent 平台目录 +- **团队协作**:完整的提交-评审-合并工作流,类似 Git 但面向 Skill 目录 +- **变更检测**:自动检测工作副本与快照之间的差异(added/deleted/modified files) +- **多市场聚合**:内置多个外部 Skill 市场 API 适配器 + +--- + +### 3. 技术栈 + +#### 3.1 整体架构 + +``` +┌─────────────────────────────────────────────┐ +│ Desktop Shell (Tauri 2) │ +│ ┌─────────────────┐ ┌──────────────────┐ │ +│ │ React Frontend │ │ Rust Backend │ │ +│ │ UI Components │◄──┼─────────────────►│ │ +│ │ State/Model │ │ SQLite / FS │ │ +│ └─────────────────┘ └──────────────────┘ │ +│ localhost IPC (invoke / events) │ +└─────────────────────────────────────────────┘ +``` + +#### 3.2 前端技术栈 + +| 技术 | 版本 | 用途 | +|---|---|---| +| React | 18.3.1 | UI 框架 | +| TypeScript | ~5.6.2 | 类型系统 | +| Vite | ^6.0.3 | 构建工具 | +| Ant Design (antd) | ^5.21.0 | UI 组件库 | +| react-router-dom | ^7.13.1 | 路由管理 | +| lucide-react | ^0.577.0 | 图标库 | +| Vitest | ^4.1.3 | 测试框架 | + +**状态管理**:React Context + localStorage(无 Redux/Zustand) +- `localStorage` 持久化用户偏好(分类、标签、主题、语言) +- React Context 提供运行时共享状态 +- 各 `features` 内部自行管理组件级状态 + +**前端目录结构**: +``` +src/ + app/ # 应用装配层:Provider、路由、全局布局 + features/ # 按业务域拆分的功能模块 + dashboard/ # 总览 + skills/ # 技能资产、详情、文件浏览 + snapshots/ # 版本快照、历史、差异对比 + market/ # 市场发现、导入 + platforms/ # 平台连接、同步配置 + projects/ # 项目空间、同步计划 + teams/ # 团队库、提交、合并、拉取 + settings/ # 主题、语言、数据目录 + shared/ # 跨域复用:Tauri 调用封装、通用组件、工具函数 + styles/ # 全局样式、设计 token + types/ # 前端领域类型定义 +``` + +#### 3.3 后端技术栈(Rust) + +| 技术 | 版本 | 用途 | +|---|---|---| +| Tauri | 2 | 桌面应用框架 | +| rusqlite | 0.32 (bundled) | SQLite 数据库 | +| serde / serde_json | 1 | 序列化 | +| uuid | 1 | UUID 生成 | +| chrono | 0.4 | 时间处理 | +| sha2 | 0.10 | 哈希计算 | +| similar | 2.6 | 文本差异计算 | +| tokio | 1 | 异步运行时 | +| reqwest | 0.12 | HTTP 客户端 | +| regex | 1 | 正则表达式 | +| dirs | 5 | 目录路径获取 | + +**Rust 模块分层**: +``` +commands/ (Tauri IPC 入口) + │ + ▼ +store/ (业务逻辑层:协调 db 和 filesystem) + │ + ├──▶ db/ (数据持久化:SQLite) + │ + └──▶ workspace/ (文件系统:工作区路径) +``` + +#### 3.4 数据存储 + +- **元数据**:SQLite(`~/.skill-studio/metadata.db`) +- **应用设置**:JSON 文件(`~/.skill-studio/settings.json`) +- **工作区配置**:JSON 文件(`~/.skill-studio/workspace.json`) +- **Skill 文件**:原始目录形式存储在 `~/.skill-studio/skills/` +- **快照**:完整目录副本存储在 `~/.skill-studio/snapshots/` +- **项目空间**:`~/.skill-studio/projects/` +- **导入缓存**:`~/.skill-studio/imports/` +- **团队版本**:`~/.skill-studio/team/versions/` + +**数据库表结构**(共 20+ 张表): +- `skills` / `skill_snapshots` / `skill_sources` / `skill_import_logs` / `skill_tags` / `skill_tag_relations` / `skill_collections` / `collection_items` +- `platform_connections` / `sync_logs` / `platform_release_targets` +- `teams` / `team_members` / `team_skills` / `team_skill_versions` / `team_submissions` / `team_delivery_targets` / `team_delivery_logs` / `team_activity_logs` +- `projects` / `project_platform_connections` / `project_skill_assignments` / `project_sync_logs` + +--- + +### 4. 文件结构 + +``` +skill-studio/ +├── .github/ # CI/CD 工作流 +│ ├── workflows/ +│ │ ├── ci.yml +│ │ ├── ci-macos.yml +│ │ ├── ci-windows.yml +│ │ └── release.yml +│ ├── ISSUE_TEMPLATE/ +│ └── PULL_REQUEST_TEMPLATE.md +├── docs/ +│ ├── ARCHITECTURE.md # 技术架构文档 +│ ├── RELEASE.md # 发布指南 +│ ├── release-notes.md # Release 模板 +│ └── assets/screenshots/ # 截图资源 +├── scripts/ +│ ├── generate-brand-assets.mjs +│ ├── generate_checksums.sh +│ └── generate_checksums.ps1 +├── src/ # 前端源码 +│ ├── App.tsx / App.test.tsx / main.tsx +│ ├── app/ # 应用装配层 +│ │ ├── AppProviders.tsx +│ │ ├── AppShell.tsx +│ │ ├── navigation.tsx +│ │ └── providers/ # 全局 Provider +│ ├── features/ # 按业务域拆分 +│ │ ├── dashboard/ +│ │ ├── skills/ # 技能资产(API、组件、模型、状态) +│ │ ├── snapshots/ # 版本快照 +│ │ ├── market/ # 市场与导入 +│ │ ├── platforms/ # 平台中心 +│ │ ├── projects/ # 项目空间 +│ │ ├── teams/ # 团队空间 +│ │ └── settings/ # 系统设置 +│ ├── shared/ # 跨域复用 +│ │ ├── components/ # Diff 组件、文件树 +│ │ ├── tauri/ # Tauri IPC 调用封装 + 浏览器预览 Mock +│ │ └── ui/ # 通用 UI 组件 +│ ├── styles/ # 全局样式 +│ └── types/ # 类型定义 +├── src-tauri/ # Rust 后端 +│ ├── src/ +│ │ ├── main.rs # 二进制入口 +│ │ ├── lib.rs # 库入口,注册 Tauri 命令 +│ │ ├── bootstrap.rs # 应用初始化 +│ │ ├── commands/ # Tauri IPC 命令(按领域拆分) +│ │ │ ├── skills.rs +│ │ │ ├── snapshots.rs +│ │ │ ├── market.rs +│ │ │ ├── platforms.rs +│ │ │ ├── projects.rs +│ │ │ ├── teams.rs +│ │ │ ├── files.rs +│ │ │ ├── settings.rs +│ │ │ ├── health.rs +│ │ │ └── organization.rs +│ │ ├── db/ # SQLite 操作层 +│ │ │ ├── schema.rs # 表结构定义 +│ │ │ ├── migrations.rs +│ │ │ ├── skills.rs +│ │ │ ├── snapshots.rs +│ │ │ ├── projects.rs +│ │ │ └── teams.rs +│ │ ├── store/ # 业务逻辑层 +│ │ │ ├── files.rs +│ │ │ ├── import.rs +│ │ │ ├── platform.rs +│ │ │ ├── project.rs +│ │ │ ├── settings.rs +│ │ │ ├── description.rs +│ │ │ ├── common.rs +│ │ │ └── organization.rs +│ │ ├── workspace/ # 工作区路径管理 +│ │ ├── snapshot/ # 快照逻辑 +│ │ ├── team/ # 团队协作逻辑 +│ │ ├── market/ # 市场数据适配层 +│ │ ├── diff.rs # 文本差异计算 +│ │ └── domain.rs # 领域模型 +│ ├── tests/ # 集成测试 +│ ├── Cargo.toml / Cargo.lock +│ ├── tauri.conf.json # Tauri 配置 +│ ├── capabilities/default.json +│ └── build.rs +├── package.json +├── tsconfig.json / tsconfig.node.json +├── vite.config.ts +├── index.html +├── README.md / README_en.md +├── ROADMAP.md +├── CHANGELOG.md +├── CONTRIBUTING.md +├── SECURITY.md +├── CODE_OF_CONDUCT.md +├── LICENSE (Apache 2.0) +└── NOTICE +``` + +--- + +### 5. 与 ContentForge 整合评估 + +#### 5.1 整合价值:**高** + +**评分依据**: + +| 维度 | 评分 | 说明 | +|---|---|---| +| 技术栈匹配度 | ★★★★★ | 完全一致:Tauri v2 + React + Rust + SQLite | +| 功能重叠度 | ★★★★☆ | Skill 管理是 ContentForge 核心功能之一 | +| 架构理念一致性 | ★★★★★ | 本地优先、前后端分离、IPC 通信 | +| 代码质量 | ★★★★☆ | 分层清晰,有测试覆盖,Schema 设计完善 | +| 生态扩展性 | ★★★★★ | 45+ 平台支持可扩展 ContentForge Agent 生态 | +| 复用成本 | ★★★★☆ | 需适配 ContentForge 的 Next.js 路由和 Zustand 状态管理 | + +**核心价值点**: + +1. **Skill 生命周期管理**:ContentForge 的 Skill 系统可直接复用 Skill Studio 的完整实现(创建 → 版本化 → 对比 → 同步 → 交付) +2. **Agent 平台生态**:45+ 平台的目录检测和同步机制,可将 ContentForge 从单一工具扩展为多 Agent 协同平台 +3. **技术架构参考**:Rust 后端的分层设计(commands → store → db/workspace)可作为 ContentForge Rust 模块的架构模板 +4. **团队协作能力**:提交-评审-合并工作流可用于 ContentForge 的多用户内容协作场景 + +#### 5.2 风险与注意事项 + +| 风险 | 级别 | 说明 | +|---|---|---| +| 前端框架差异 | 中 | Skill Studio 使用 Vite + react-router-dom,ContentForge 使用 Next.js App Router,需适配 | +| 状态管理差异 | 中 | Skill Studio 使用 React Context + localStorage,ContentForge 使用 Zustand + Tauri Storage | +| UI 组件库差异 | 中 | Skill Studio 使用 Ant Design,ContentForge 使用 Tailwind CSS + 自定义组件 | +| 项目成熟度 | 低 | v0.1.0 预览版,API 可能不稳定 | +| 许可证兼容 | 低 | Apache 2.0 与 ContentForge 兼容 | +| 外部市场依赖 | 低 | 市场模块依赖外部 API,ContentForge 可选择性禁用 | + +--- + +### 6. 整合建议 + +#### 6.1 直接复用模块(高优先级) + +| 模块 | 复用方式 | 说明 | +|---|---|---| +| **Rust 后端 - 平台检测与同步** | 移植/引用 | `src-tauri/src/store/platform.rs`、`commands/platforms.rs` — 45+ 平台目录识别规则和同步逻辑可直接移植到 ContentForge | +| **Rust 后端 - SQLite 数据库层** | 架构参考 | `src-tauri/src/db/` 的 schema 设计和 CRUD 模式可作为 ContentForge 数据库模块的模板 | +| **Rust 后端 - 文件系统操作** | 移植 | `src-tauri/src/store/files.rs`、`workspace/paths.rs` — 路径规范化、文件复制/删除/恢复逻辑 | +| **Rust 后端 - Diff 计算** | 移植 | `src-tauri/src/diff.rs` — 基于 `similar` 库的文本差异计算封装 | +| **Rust 后端 - 快照机制** | 架构参考 | `src-tauri/src/snapshot/` — 完整目录快照、版本对比、恢复逻辑 | +| **React - Diff 组件** | 移植 | `src/shared/components/diff/` — SplitDiff、TextDiff、DiffFileTree 组件 | +| **React - 文件树组件** | 移植 | `src/shared/ui/SkillFileTree.tsx`、`src/features/skills/components/FileExplorerPanel.tsx` | + +#### 6.2 架构参考与适配(中优先级) + +| 模块 | 参考内容 | 说明 | +|---|---|---| +| **Tauri IPC 命令注册模式** | 架构参考 | `commands.rs` 的宏定义命令处理器模式,可优化 ContentForge 的 commands.rs | +| **领域模型设计** | 参考 | `domain.rs` 中的 Skill、Snapshot、PlatformConnection 等类型定义 | +| **前端 features 目录组织** | 参考 | 按业务域拆分的 features/ 目录结构,可借鉴到 ContentForge 的组件组织 | +| **市场导入流程** | 参考 | `src/features/market/` 的导入状态机设计(useMarketImportFlow) | +| **版本对比状态管理** | 参考 | `src/features/snapshots/model/versionCompareState.ts` | + +#### 6.3 可选扩展功能(低优先级) + +| 模块 | 场景 | 说明 | +|---|---|---| +| **团队空间 (Teams)** | 多用户协作 | 如果 ContentForge 未来需要多用户内容协作,可参考 Skill Studio 的提交-评审-合并工作流 | +| **项目空间 (Projects)** | 项目级内容管理 | 项目绑定 Skill 和同步计划的模式可用于 ContentForge 的项目级内容组织 | +| **外部市场聚合** | Skill 生态 | 如果 ContentForge 计划建立 Skill 市场生态,可直接复用市场适配层 | +| **自动更新机制** | 应用分发 | `tauri.conf.json` 中的 updater 配置和 GitHub Releases 集成模式 | + +#### 6.4 具体整合路径建议 + +**路径一:Rust 后端模块移植(推荐)** +1. 将 Skill Studio 的 `src-tauri/src/db/schema.rs` 中与 Skill 相关的表结构合并到 ContentForge 的 SQLite 数据库 +2. 移植 `store/platform.rs` 的平台检测逻辑到 ContentForge 的 Rust 后端 +3. 移植 `store/files.rs` 的文件操作工具函数 +4. 在 ContentForge 前端新增 "Skill 管理" 页面,调用移植后的 Rust 命令 + +**路径二:作为独立子应用嵌入** +1. 将 Skill Studio 作为 ContentForge 的 "Skill 管理" 子模块 +2. 共享同一个 Tauri 应用实例和 SQLite 数据库 +3. 通过统一的导航栏在 ContentForge 主功能和 Skill 管理之间切换 + +**路径三:架构参考,重写前端** +1. 保留 Skill Studio 的 Rust 后端逻辑作为参考 +2. 使用 ContentForge 现有的技术栈(Next.js + Tailwind + Zustand)重写前端 +3. 复用业务逻辑层(store/)的设计模式,但适配到 ContentForge 的状态管理方案 + +--- + +### 7. 总结 + +Skill Studio 是一个架构清晰、功能完整的 Skill 资产管理工具,与 ContentForge 在技术栈(Tauri v2 + React + Rust + SQLite)和核心理念(本地优先、Skill 系统)上高度契合。**整合价值评定为「高」**。 + +**最推荐的复用方向**: +1. **Rust 后端的平台检测与同步模块** — 直接扩展 ContentForge 的 Agent 生态支持 +2. **Skill 版本管理和快照机制** — 补齐 ContentForge Skill 系统的版本化能力 +3. **Diff 组件和文件树组件** — 提升 ContentForge 的内容对比和浏览体验 + +通过整合 Skill Studio 的能力,ContentForge 可以从单一的内容创作工具升级为完整的 **AI Agent 内容创作 + Skill 资产管理平台**。 + +--- + +> 本报告基于 Skill Studio v0.1.0 代码库分析生成。 diff --git a/contentforge/external-repos/skill-zoo b/contentforge/external-repos/skill-zoo new file mode 160000 index 0000000..0f1c63d --- /dev/null +++ b/contentforge/external-repos/skill-zoo @@ -0,0 +1 @@ +Subproject commit 0f1c63d449f8accc65f214c85ef8e61f508a43c0 diff --git a/contentforge/external-repos/skill-zoo_ANALYSIS.md b/contentforge/external-repos/skill-zoo_ANALYSIS.md new file mode 100644 index 0000000..fec805b --- /dev/null +++ b/contentforge/external-repos/skill-zoo_ANALYSIS.md @@ -0,0 +1,269 @@ +## skill-zoo 仓库分析 + +> 分析日期:2026-07-25 +> 仓库路径:`/Users/patrick/innate/projects/innate-keepthem/contentforge/external-repos/skill-zoo/` +> 原始仓库:https://github.com/luochang212/skill-zoo + +--- + +### 1. 项目概述 + +**skill-zoo** 是一个开源的**本地 Agent Skill 管理器**(Local Agent Skills Manager),用于为 AI 编码工具发现、安装、创建和管理 Skill 文件。项目核心洞察是:"一个 Skill 就是一个文件"——这个应用本质上是一个带有专用 UI 的文件管理器。 + +**项目定位**:面向 AI 编码助手(Claude Code、Codex、Gemini、Cursor、Trae 等)的 Skill 生态管理工具,提供桌面 GUI + CLI 双重控制界面。 + +**版本**:v0.3.40 | **License**:MIT | **平台**:macOS / Windows / Linux + +**分发形态**: +- **Desktop App**:Tauri v2 桌面应用(主产品) +- **CLI**:npm 包 `skill-zoo`(`skill-zoo list`、`skill-zoo doctor`、`skill-zoo wui` 等) +- **WUI**:CLI 启动的轻量本地 Web UI + +--- + +### 2. 功能分析 + +#### 2.1 核心功能 + +| 功能模块 | 说明 | +|---------|------| +| **浏览与发现** | 从 GitHub 仓库搜索 Skill,支持推荐仓库轮播、关键词搜索 | +| **安装与更新** | 从 GitHub 下载 ZIP 解压安装,支持单 Skill / 批量更新,追踪 commit SHA | +| **Skill 创作** | 内置 Markdown 编辑器,支持 YAML frontmatter(name / description),实时预览 | +| **批量操作** | 批量安装、删除、合并重复 Skill、归档/恢复 | +| **安全审计** | 集成 skills.sh 社区审计分数,显示 pass/warn/fail 状态 | +| **一致性检查** | 主动检测三类不一致:duplicate(重复)、conflict(冲突)、mismatch(不匹配) | +| **Skill 归档** | 将 Skill 移入 `~/.skill-zoo/archive/` 作为临时存储,减少 Agent 上下文负载 | +| **外部导入** | 扫描本地磁盘上的 Skill 目录并作为 symlink 导入管理,不移动源文件 | +| **多 Agent 兼容** | 支持 17 种 AI 编码工具,通过 symlink/junction 统一管理 | +| **使用追踪** | 解析 Agent 的使用记录(仅 Claude Code / Codex / OpenCode 支持) | + +#### 2.2 Skill 生命周期 + +``` +GitHub 仓库 ──→ 下载 ZIP ──→ 解压到 ~/.agents/skills// ──→ 向各 Agent 目录创建 symlink + ↑ │ + └──────── 检测 commit SHA 差异 ─────────────────────────────────┘ + +本地 Skill 目录 ──→ 外部导入 ──→ 注册到 imports.json ──→ 向 Agent 目录创建 symlink +``` + +#### 2.3 设计哲学 + +- **文件系统是真相来源**:缓存不是真相,文件系统才是。缓存从文件系统状态重建。 +- **SSOT + Symlinks**:`~/.agents/skills/` 是规范存储,Agent 目录只包含指向真实位置的 symlink/junction。 +- **尊重用户文件**:本地 Skill 绝不复制或移动,直接 symlink。 +- **Tauri IPC 是唯一桥梁**:前端从不触碰文件系统,所有外部交互通过类型化的 Rust 命令完成。 + +--- + +### 3. 技术栈 + +#### 3.1 前端层 + +| 技术 | 版本 | 用途 | +|------|------|------| +| React | ^19.2.7 | UI 框架 | +| TypeScript | ^6.0.3 | 类型系统 | +| Vite | ^8.1.3 | 构建工具 | +| Tailwind CSS | ^4.3.2 | 原子化样式 | +| shadcn/ui | — | UI 组件基座(Radix UI 封装) | +| TanStack React Query | ^5.101.2 | 服务端状态管理 | +| Framer Motion | ^12.42.2 | 动画过渡 | +| i18next + react-i18next | ^26.3.4 | 国际化(中/英) | +| react-markdown + rehype | ^10.1.0 | Markdown 渲染 | +| @dnd-kit/react | 0.5.0 | 拖放交互 | +| lucide-react | ^1.23.0 | 图标 | +| modern-screenshot | ^4.7.0 | 截图 | + +#### 3.2 后端层(Rust / Tauri v2) + +| 技术 | 版本 | 用途 | +|------|------|------| +| Tauri | v2 | 桌面运行时 | +| reqwest | 0.13 | HTTP 客户端(GitHub API / ZIP 下载) | +| tokio | 1.x | 异步运行时 | +| serde + serde_json | 1.0 | 序列化 | +| serde_yaml | 0.9 | YAML frontmatter 解析 | +| rusqlite | 0.32 | SQLite(bundled) | +| chrono | 0.4 | 时间处理 | +| sha2 | 0.11 | 内容哈希 | +| zip | 8.6 | ZIP 解压 | +| notify | 7 | 文件系统监听 | +| arboard | 3.6.1 | 剪贴板 | +| regex | 1.12 | 正则 | +| dirs | 6.0 | 跨平台目录路径 | + +#### 3.3 CLI 层 + +| 技术 | 版本 | 用途 | +|------|------|------| +| Node.js | ≥20 | 运行时 | +| Commander.js | ^14.0.2 | CLI 框架 | +| unified + remark/rehype | ^11.0.5 | Markdown 处理 | +| yaml | ^2.8.2 | YAML 解析 | +| tsup | ^8.5.1 | 构建打包 | + +#### 3.4 工具链 + +| 环节 | 工具 | +|------|------| +| Lint | oxlint + clippy | +| Format | oxfmt + cargo fmt | +| Test | Vitest(前端)+ Rust tests(后端) | +| Package Manager | Bun | +| CI | GitHub Actions | + +--- + +### 4. 文件结构 + +``` +skill-zoo/ +├── src/ # React 前端 +│ ├── App.tsx # 根组件:视图路由(discover / local / settings) +│ ├── components/ +│ │ ├── skills/ # Skill 浏览、详情、安装、创建 +│ │ │ ├── BrowseSkills.tsx # 发现页:搜索、推荐仓库 +│ │ │ ├── InstalledSkills.tsx # 本地 Skill 列表 + 侧边栏分类 +│ │ │ ├── SkillDetail.tsx # Skill 详情:Markdown 编辑器/预览 +│ │ │ ├── SkillCreateView.tsx # 新建 Skill +│ │ │ ├── SkillCard.tsx # Skill 卡片组件 +│ │ │ ├── SkillFileTree.tsx # 文件树 +│ │ │ ├── ConsistencyPanel.tsx # 一致性检查面板 +│ │ │ └── ... +│ │ ├── settings/ # 设置页:主题、语言、维护、关于 +│ │ ├── layout/ # 顶部导航 Header +│ │ └── ui/ # shadcn/ui 基础组件 +│ ├── hooks/ # React Query hooks & 缓存失效 +│ ├── i18n/ # 翻译文件(en.json / zh.json) +│ ├── lib/ # Tauri API 客户端、Agent 配置、工具函数 +│ └── types/skills.ts # TypeScript 类型定义 +│ +├── src-tauri/ # Tauri + Rust 后端 +│ ├── src/ +│ │ ├── commands/ +│ │ │ ├── skill.rs # Skill 相关 IPC 命令(~80 个命令) +│ │ │ └── settings.rs # 设置相关 IPC 命令 +│ │ ├── services/ +│ │ │ ├── skill.rs # Skill 扫描、缓存、symlink 管理 +│ │ │ ├── github.rs # GitHub API 集成(搜索、下载、README) +│ │ │ ├── cli.rs # 原生 Rust 实现的 npx skill 兼容层 +│ │ │ ├── lock.rs # .skill-lock.json 管理 +│ │ │ ├── watcher.rs # 文件系统监听(notify) +│ │ │ └── tray.rs # 系统托盘 +│ │ ├── persistence/ # 持久化层 +│ │ │ ├── metadata.rs # 用户元数据(starred, isMine) +│ │ │ ├── archive.rs # 归档 manifest +│ │ │ ├── external_imports.rs # 外部导入注册表 +│ │ │ └── update_history.rs # 更新历史 +│ │ ├── config.rs # Agent 配置 & 路径检测(17 个 Agent) +│ │ ├── store.rs # AppState(RwLock 管理的运行时状态) +│ │ ├── error.rs # 错误类型(AppError / CommandError) +│ │ └── lib.rs # Tauri Builder setup(启动时缓存重建、文件监听) +│ ├── resources/ # 轮播图、推荐仓库 JSON +│ └── Cargo.toml +│ +├── packages/ +│ └── cli/ # npm CLI 包 +│ ├── src/ +│ │ ├── cli.ts # Commander 命令定义 +│ │ ├── protocol/ # 本地协议实现(与桌面共享 schema) +│ │ │ ├── scan.ts # Skill 扫描 +│ │ │ ├── consistency.ts # 一致性检查 +│ │ │ ├── diagnostics.ts # doctor 诊断 +│ │ │ ├── archive.ts # 归档操作 +│ │ │ ├── imports.ts # 外部导入 +│ │ │ └── types.ts # 共享类型 +│ │ └── wui/ # 轻量 Web UI(纯 HTML/JS/CSS) +│ └── package.json +│ +├── fixtures/ +│ └── local-protocol/ # 桌面协议 fixture(版本兼容测试) +│ ├── lock-v3-full.json +│ ├── archive-v1-full.json +│ └── imports-v1-full.json +│ +├── skills/ # 项目自身的 automation skills +├── docs/ # 截图、开发文档、本地协议文档 +└── package.json # Bun workspace(包含 packages/*) +``` + +--- + +### 5. 与 ContentForge 整合评估 + +#### 5.1 整合价值:中 + +| 维度 | 评估 | +|------|------| +| **技术栈匹配度** | **极高** — 同为 Tauri v2 + React 19 + TypeScript + Tailwind + Rust,架构模式高度重合 | +| **业务功能重叠** | **低** — skill-zoo 专注 Skill 发现/安装/管理,ContentForge 专注内容创作(YouTube/AI/输出) | +| **Skill 概念对齐** | **部分对齐** — 两者都有 "Skill" 概念,但 skill-zoo 的 Skill 是静态 Markdown 指令文件,ContentForge 的 Skill 是可执行的内容生成工作流 | +| **组件复用价值** | **中高** — Markdown 编辑器、文件树、卡片布局、设置面板等 UI 模式可直接借鉴 | +| **架构参考价值** | **高** — Tauri IPC 组织、Rust 服务层设计、前端状态管理、持久化策略均可参考 | + +#### 5.2 不适合直接整合的原因 + +1. **核心使命不同**:skill-zoo 是 "Skill 包管理器"(类似 npm/apt),ContentForge 是 "内容创作平台"(类似剪映 + Notion)。两者的核心用户流程没有直接交集。 +2. **Agent 生态耦合**:skill-zoo 深度绑定 17 种 AI 编码工具的目录结构和 symlink 机制,这些对 ContentForge 无意义。 +3. **GitHub 中心主义**:skill-zoo 的所有发现/安装/更新逻辑围绕 GitHub 仓库 ZIP 下载,与 ContentForge 的本地内容创作场景不匹配。 + +--- + +### 6. 整合建议 + +#### 6.1 可直接借鉴/复用的模块(高优先级) + +| 模块 | 复用方式 | 说明 | +|------|---------|------| +| **Tauri IPC 命令组织** | 架构参考 | `commands/skill.rs` + `commands/settings.rs` 的分离模式,每个命令的输入输出类型定义 | +| **Rust 服务层设计** | 架构参考 | `services/` 目录结构:skill / github / cli / lock / watcher / tray 的拆分方式 | +| **错误处理模式** | 代码借鉴 | `error.rs` 中 `AppError` / `CommandError` 的分层设计,IO 错误上下文包装 | +| **文件系统 Watcher** | 代码借鉴 | `services/watcher.rs` 基于 `notify` 的增量缓存刷新机制 | +| **持久化版本控制** | 架构参考 | `fixtures/local-protocol/` 的 schema 演进策略(v1 / v2 / future 版本兼容测试) | +| **路径安全验证** | 代码借鉴 | `validate_skill_directory`、`is_path_under_roots` 等路径遍历防护 | + +#### 6.2 可适配到 ContentForge 的 UI 组件(中优先级) + +| 组件 | 适配场景 | +|------|---------| +| **SkillDetail / SkillCreateView** | ContentForge 的 "内容模板编辑器" — Markdown + YAML frontmatter 编辑模式 | +| **SkillFileTree** | 内容素材库的文件树浏览 | +| **SkillCard / SkillCardRow** | 内容模板/预设的卡片展示 | +| **SettingsView + Section 组件** | 应用设置页面的布局模式(主题、语言、维护、关于) | +| **ConsistencyPanel** | 内容项目的一致性检查面板(如:重复内容、冲突配置) | +| **MarkdownContent(预览)** | 任何需要 Markdown 渲染的地方(rehype-highlight + rehype-sanitize 配置) | + +#### 6.3 可参考的设计模式(低优先级) + +| 模式 | 应用场景 | +|------|---------| +| **TanStack React Query + hook 封装** | ContentForge 中后端状态管理(如下载队列、AI 生成任务) | +| **前端缓存 + 后台重建** | 启动时立即加载缓存、后台异步重建的 UX 模式 | +| **拖放排序(@dnd-kit/react)** | 内容模板排序、播放列表排序 | +| **i18next 组织方式** | `src/i18n/index.ts` 的初始化 + JSON 扁平键命名风格 | +| **系统托盘 + 窗口拖拽** | `services/tray.rs` + `handleDragMouseDown` 的无框窗口体验 | + +#### 6.4 不建议复用的部分 + +| 模块 | 原因 | +|------|------| +| `services/github.rs` | GitHub 仓库搜索/ZIP 下载与 ContentForge 业务无关 | +| `services/cli.rs` | npx skill 兼容层是 skill-zoo 特有的需求 | +| `config.rs` 中的 17 个 Agent 配置 | AI 编码工具的目录约定对 ContentForge 无意义 | +| Symlink / Junction 管理逻辑 | 多 Agent 共享 Skill 的机制不适用于内容创作场景 | +| `skills.sh` 审计集成 | 第三方 Skill 审计服务与 ContentForge 无关 | + +#### 6.5 潜在的功能扩展方向 + +若 ContentForge 未来需要**内容模板市场**或**用户自定义 Prompt 模板管理**,skill-zoo 的架构可作为参考实现: + +- 将 "Skill" 替换为 "Content Template" +- 将 "GitHub 仓库" 替换为 "内容模板市场 API" +- 将 "Agent 目录" 替换为 "项目级模板目录" +- 保留:本地编辑、版本控制、归档、一致性检查等机制 + +--- + +> **总结**:skill-zoo 是一个技术实现精良、架构设计清晰的桌面应用,与 ContentForge 在技术栈上高度一致,但在业务领域上差异显著。建议将 skill-zoo 作为**架构和 UI 模式的参考库**,而非直接整合的目标。重点借鉴其 Tauri IPC 组织、Rust 服务层、前端状态管理和持久化策略。 diff --git a/contentforge/external-repos/youtube-rag-system b/contentforge/external-repos/youtube-rag-system new file mode 160000 index 0000000..8911525 --- /dev/null +++ b/contentforge/external-repos/youtube-rag-system @@ -0,0 +1 @@ +Subproject commit 8911525eb36262955ab0aca667be7cc4bf30103c diff --git a/contentforge/external-repos/youtube-rag-system_ANALYSIS.md b/contentforge/external-repos/youtube-rag-system_ANALYSIS.md new file mode 100644 index 0000000..b75be27 --- /dev/null +++ b/contentforge/external-repos/youtube-rag-system_ANALYSIS.md @@ -0,0 +1,295 @@ +## youtube-rag-system 仓库分析 + +> **分析日期**: 2025-07-25 +> **仓库路径**: `/Users/patrick/innate/projects/innate-keepthem/contentforge/external-repos/youtube-rag-system/` +> **原始仓库**: https://github.com/XynaxDev/youtube-rag-system + +--- + +### 1. 项目概述 + +**ClipIQ**(仓库名 `youtube-rag-system`)是一个面向 YouTube 视频的 **RAG(检索增强生成)智能分析引擎**。项目核心定位是将长视频内容转化为可验证的智能信息,支持单视频深度分析和双视频对比分析两种主要工作流。 + +**核心产品形态**: +- 后端:Python FastAPI 服务,提供视频处理、RAG 对话、摘要生成、双视频对比等 REST API +- 前端:React 19 + Vite 单页应用,提供 Landing 页、Dashboard、视频摘要、视频对比、历史记录等交互界面 +- 数据层:ChromaDB 向量数据库存储视频转录片段的语义嵌入,支持持久化索引和会话缓存 + +**项目成熟度**:中等偏高。代码结构清晰,具备完整的错误处理、降级策略(多重转录获取 fallback)、速率限制处理、会话管理和历史记录功能。但当前为内存会话存储(非 Redis/DB),向量索引本地持久化,适合单机或轻量部署。 + +--- + +### 2. 功能分析 + +#### 2.1 单视频智能分析(Single-video Intelligence) + +| 功能模块 | 说明 | +|---------|------| +| **URL 解析与元数据获取** | 提取 YouTube video ID,通过 YouTube Data API v3 获取标题、频道、发布日期、描述 | +| **转录提取(多层 Fallback)** | 主路径:`youtube-transcript-api` 优先语言获取;降级路径1:英文轨道回退;降级路径2:首个可用轨道;降级路径3:默认轨道;降级路径4:yt-dlp 解析 VTT/JSON3 字幕;降级路径5:legacy `get_transcript` 方法 | +| **自适应分块(Adaptive Chunking)** | 基于总字符数动态计算 chunk size(600-1200 字符),15% 重叠率;带时间戳前缀的文档分块 | +| **向量索引与持久化** | 使用 `bge-m3` 嵌入模型(Ollama)+ ChromaDB;支持跨会话复用已持久化的向量索引 | +| **RAG 对话(Chat)** | 混合检索策略:SelfQueryRetriever(时间感知)+ Dense Semantic Search + Lexical Fallback + Temporal Neighbor Expansion + Time-window Search | +| **智能路由(Intent Router)** | LLM 驱动的查询分类:SUMMARY(摘要)/ RAG(事实问答)/ CHAT(闲聊);自动判断是否需要时间戳、是否需要历史上下文 | +| **摘要生成(Summary)** | 生成结构化摘要(概述 + Key Takeaways 要点列表)+ 3 个视频专属起始问题 | +| **时间戳接地(Timestamp Grounding)** | 所有 RAG 回答附带证据来源的时间戳(mm:ss 格式),可点击跳转;支持时间范围查询的精确检索 | +| **答案验证(Answer Support)** | LLM 二次验证回答是否被视频证据支持,若判定为 NOT_FOUND 则触发 rescue retrieval 重试 | + +#### 2.2 双视频智能分析(Dual-video Intelligence) + +| 功能模块 | 说明 | +|---------|------| +| **视频对比(Compare)** | 同时处理两个视频,生成对比报告:双视频摘要、各自快照、交叉裁决、要点对比、推荐/学习计划(学习模式下) | +| **范围感知路由(Scope-aware Routing)** | 自动检测用户询问的是 Video A / Video B / Both;支持显式指定(如 "first video") | +| **查询分类** | COMPARATIVE(比较)/ COMMON(共同主题)/ FOCUSED_INFO(具体信息)/ GENERAL(一般) | +| **学习模式(Study Mode)** | 针对技术/教育类视频启用深度分析风格,输出架构推理、权衡分析、实践意义 | +| **技术内容检测** | 自动检测视频是否包含分析性/技术性内容,决定是否启用学习模式 | + +#### 2.3 前端功能 + +| 页面 | 功能 | +|------|------| +| **Landing** | 产品首页展示 | +| **Dashboard** | 功能导航(摘要/对比)+ 最近活动分页列表 | +| **Summarize** | 粘贴 YouTube URL → 处理中动画终端 → 生成摘要结果 | +| **SummaryResult** | 展示视频元数据、结构化摘要、起始问题 | +| **Compare** | 输入两个视频 URL + 问题 → 选择学习模式 → 生成对比报告 | +| **CompareResult** | 展示双视频对比分析结果(Markdown 渲染) | +| **History** | 本地存储的历史记录浏览和恢复 | + +--- + +### 3. 技术栈 + +#### 3.1 后端技术栈 + +| 层级 | 技术 | 版本 | 用途 | +|------|------|------|------| +| **运行时** | Python | 3.11+ | 核心运行时 | +| **Web 框架** | FastAPI | 0.115 | REST API 服务 | +| **数据验证** | Pydantic | 2.9 | 请求/响应模型校验 | +| **LLM 编排** | LangChain | 0.3 | RAG pipeline、Prompt 管理、输出解析 | +| **LLM 模型** | OpenRouter | - | 通过 `init_chat_model` 调用(默认 `arcee-ai/trinity-large-preview:free`) | +| **嵌入模型** | Ollama + bge-m3 | - | 本地嵌入生成(带 NaN/Inf 安全包装) | +| **向量数据库** | ChromaDB | 0.4.24 | 向量存储与相似性检索 | +| **YouTube 交互** | youtube-transcript-api / google-api-python-client / yt-dlp | 1.2.4 / 2.130.0 | 转录获取、元数据获取、字幕解析 | +| **其他** | uvicorn, python-dotenv, httpx, lark | - | 服务运行、环境变量、HTTP 请求、查询构造 | + +#### 3.2 前端技术栈 + +| 层级 | 技术 | 版本 | 用途 | +|------|------|------|------| +| **框架** | React | 19 | UI 组件框架 | +| **语言** | TypeScript | 5.8 | 类型安全 | +| **构建工具** | Vite | 6 | 开发服务器与打包 | +| **样式** | Tailwind CSS | 4.1.14 | 原子化 CSS | +| **路由** | React Router DOM | 7.13 | 客户端路由 | +| **动画** | Framer Motion | 12.34 | 页面过渡与交互动画 | +| **平滑滚动** | Lenis | 1.3 | 平滑滚动体验 | +| **图标** | Lucide React | 0.546 | 图标库 | +| **提示** | Sonner | 2.0 | Toast 通知 | +| **Markdown 渲染** | react-markdown | 10.1 | 摘要/对比结果渲染 | +| **视频播放** | react-player | 2.16 | 视频嵌入播放 | + +#### 3.3 系统架构 + +``` +User Input (URL / Question) + → FastAPI Route Layer (/api/process, /api/chat, /api/summary, /api/compare) + → RAG Pipeline (single or dual) + → Transcript + Metadata + Chunking (transcript.py) + → Embeddings + Chroma persistent index (embeddings.py + retriever.py) + → Hybrid Retrieval + Ranking + Grounding (retrieval_helpers.py) + → LLM Response Policy + Formatting (policy_helpers.py) + → Frontend Chat/Summary UI + Timestamp Seek +``` + +--- + +### 4. 文件结构 + +``` +youtube-rag-system/ +├── .env.example # 根级环境变量示例 +├── .gitignore +├── README.md # 项目文档(ClipIQ 品牌) +│ +├── backend/ # Python FastAPI 后端 +│ ├── main.py # FastAPI 入口(CORS、路由挂载) +│ ├── requirements.txt # Python 依赖清单 +│ ├── .env.example # 后端环境变量示例 +│ └── app/ +│ ├── __init__.py +│ ├── config.py # 环境变量加载、LLM 初始化 +│ ├── schemas.py # Pydantic 请求/响应模型 +│ ├── routes/ +│ │ ├── __init__.py +│ │ └── video.py # API 路由实现(/api/*) +│ └── rag/ # 核心 RAG 模块 +│ ├── __init__.py +│ ├── pipeline.py # 单视频处理、对话、摘要、清理(1000+ 行) +│ ├── multi_video_pipeline.py # 双视频检索、范围路由、对比生成(1000+ 行) +│ ├── compare_service.py # 对比服务编排(调用 multi_video_pipeline) +│ ├── transcript.py # 转录提取、元数据获取、分块(960+ 行,含多重 fallback) +│ ├── retriever.py # Chroma 向量存储创建/加载/删除、SelfQueryRetriever +│ ├── retrieval_helpers.py # 混合排序、时间戳提取/对齐、词汇回退(430 行) +│ ├── policy_helpers.py # 响应策略分类(CHAT/RAG/SUMMARY)、答案支持验证 +│ └── embeddings.py # 安全嵌入包装器、输入清理、块验证 +│ +├── frontend/ # React + Vite 前端 +│ ├── index.html +│ ├── package.json +│ ├── package-lock.json +│ ├── tsconfig.json +│ ├── vite.config.ts +│ ├── .env.example +│ ├── public/ +│ │ ├── landingpage.png +│ │ └── ytlogo.svg +│ └── src/ +│ ├── App.tsx # 路由配置(BrowserRouter + Layout) +│ ├── main.tsx # 应用入口 +│ ├── index.css # 全局样式 +│ ├── components/ +│ │ ├── ui/sonner.tsx # Toast UI 组件 +│ │ ├── BottomNav.tsx # 底部导航 +│ │ ├── Layout.tsx # 布局外壳 +│ │ ├── Sidebar.tsx # 侧边栏 +│ │ └── GlobalToast.tsx # Toast 上下文 Provider +│ ├── lib/ +│ │ ├── api.ts # API 服务层(封装所有后端调用) +│ │ ├── history.ts # 本地历史记录管理(localStorage) +│ │ └── utils.ts # 工具函数(cn 等) +│ └── pages/ +│ ├── Landing.tsx # 落地页 +│ ├── Dashboard.tsx # 智能中枢(功能入口 + 最近活动) +│ ├── Summarize.tsx # 视频摘要输入页 +│ ├── SummaryResult.tsx # 摘要结果展示页 +│ ├── Compare.tsx # 双视频对比输入页 +│ ├── CompareResult.tsx # 对比结果展示页 +│ └── History.tsx # 历史记录页 +│ +└── notebooks/ # Jupyter Notebook 示例 + ├── singleVideo.ipynb # 单视频处理示例 + └── multiVideo.ipynb # 多视频处理示例 +``` + +--- + +### 5. 与 ContentForge 整合评估 + +#### 5.1 功能映射对比 + +| ContentForge 核心功能 | ClipIQ 对应能力 | 匹配度 | +|---------------------|----------------|--------| +| YouTube 视频下载 | 无直接下载能力,但有转录提取 + 元数据获取 | ⚠️ 部分匹配 | +| 视频分析/转录 | **核心能力** — 多层 fallback 转录提取、自适应分块、时间戳对齐 | ✅ 高度匹配 | +| AI 内容生成 | **核心能力** — RAG 驱动的摘要、问答、对比报告 | ✅ 高度匹配 | +| 多 Agent 对话 | 单视频/双视频对话模式、意图路由、范围感知路由 | ✅ 可映射为多 Agent | +| Skill 系统 | 无直接 Skill 系统,但有模块化 RAG pipeline | ⚠️ 需适配 | +| Tauri 桌面应用 | 前端为 Vite SPA,非 Tauri | ❌ 不匹配 | +| Rust 后端 | 后端为 Python FastAPI | ❌ 不匹配 | +| React 前端 + Next.js | 前端为 React 19 + Vite(非 Next.js) | ⚠️ 部分匹配 | +| 内容输出(Markdown/Notes/XHS/Slides/Video) | 输出为 Markdown 格式摘要/对比报告 | ⚠️ 部分匹配 | + +#### 5.2 整合价值判断 + +**总体整合价值:高(High)** + +**价值依据**: + +1. **转录提取引擎可直接复用**:ClipIQ 的 `transcript.py` 实现了业界最完善的 YouTube 转录获取逻辑(5 层 fallback + 速率限制处理 + 缓存机制),可直接作为 ContentForge 视频分析管道的转录输入源。 + +2. **RAG Pipeline 可直接增强 ContentForge 的视频分析 Agent**:ContentForge 目前已有视频下载和基础分析能力,ClipIQ 的 RAG pipeline(混合检索、时间戳接地、答案验证)可将视频分析从"简单转录"升级为"智能问答"。 + +3. **双视频对比是 ContentForge 的差异化功能补充**:ContentForge 当前功能列表中没有明确的双视频对比分析能力,ClipIQ 的 `multi_video_pipeline.py` 可直接作为新增功能模块。 + +4. **摘要生成与内容输出管道对接**:ClipIQ 生成的结构化 Markdown 摘要可直接进入 ContentForge 的内容输出管道(Notes/Slides/XHS 等)。 + +**整合风险与注意事项**: + +| 风险点 | 说明 | 缓解措施 | +|--------|------|---------| +| **技术栈异构** | ClipIQ 后端为 Python,ContentForge 为 Rust + TS | 将 ClipIQ 作为独立微服务或 Sidecar 进程运行,通过 HTTP API 通信 | +| **依赖较重** | 需要 Ollama(本地嵌入)+ OpenRouter(LLM)+ ChromaDB | 评估是否可以替换为 ContentForge 已有的 AI Provider 体系 | +| **会话存储** | 当前为内存存储,不适合多实例部署 | 接入 ContentForge 的存储层(SQLite/Redis) | +| **嵌入模型绑定** | 硬编码使用 Ollama + bge-m3 | 抽象为通用 Embedding Provider 接口,支持多种嵌入源 | + +--- + +### 6. 整合建议 + +#### 6.1 高优先级整合模块(建议直接复用/移植) + +**模块 1:`transcript.py` — 转录提取引擎** +- **复用方式**:将 `transcript.py` 作为独立 Python 模块嵌入 ContentForge 后端,或通过子进程调用 +- **核心价值**:5 层 fallback 的转录获取策略(youtube-transcript-api → 英文回退 → 首个可用 → 默认轨道 → yt-dlp VTT/JSON3 → legacy API)是 ContentForge 当前最缺乏的深度能力 +- **适配工作量**:中等 — 需要解耦 `CHROMA_PERSIST_DIR` 依赖,适配 ContentForge 的配置体系 + +**模块 2:`pipeline.py` — 单视频 RAG Pipeline** +- **复用方式**:将核心 RAG 逻辑(`chat_with_video`、`summarize_video`、`process_video`)移植为 ContentForge 的一个 Skill 或 Agent +- **核心价值**:提供视频智能问答和摘要能力,直接增强 ContentForge 的"视频分析"功能 +- **适配工作量**:中高 — 需要替换 OpenRouter LLM 调用为 ContentForge 的 AI Provider 抽象层;替换 ChromaDB 为 ContentForge 的向量存储方案(或保留 ChromaDB 作为 sidecar) + +**模块 3:`multi_video_pipeline.py` + `compare_service.py` — 双视频对比** +- **复用方式**:作为 ContentForge 新增"视频对比"功能的核心引擎 +- **核心价值**:ContentForge 目前没有双视频对比能力,这是显著的差异化功能 +- **适配工作量**:中等 — Prompt 模板和路由逻辑可直接复用,主要工作是 API 适配和 UI 集成 + +#### 6.2 中优先级整合模块(可选复用) + +**模块 4:`retrieval_helpers.py` — 检索辅助工具** +- **复用价值**:时间戳解析、词汇回退、文档排序、时间邻居扩展等算法逻辑 +- **建议**:提取核心算法函数,移植到 ContentForge 的 Rust/TS 后端或作为 Python 微服务保留 + +**模块 5:`policy_helpers.py` — 意图路由与答案验证** +- **复用价值**:LLM 驱动的查询分类(SUMMARY/RAG/CHAT)和答案支持验证机制 +- **建议**:可作为 ContentForge Agent 系统的路由层参考,但需重写为 ContentForge 的 AI Provider 调用风格 + +**模块 6:前端页面参考(`Summarize.tsx`、`Dashboard.tsx`)** +- **复用价值**:UI 交互流程设计(URL 输入 → 处理中状态 → 结果展示)和动画效果 +- **建议**:作为 ContentForge 视频分析功能的 UI 设计参考,而非直接复用代码(技术栈差异:Vite vs Next.js) + +#### 6.3 整合架构建议 + +**推荐方案:Python Sidecar 微服务** + +``` +ContentForge Desktop (Tauri + Next.js) + ↓ HTTP API +┌─────────────────────────────────────┐ +│ youtube-rag-system (Python FastAPI) │ ← 作为 sidecar 进程运行 +│ - transcript.py │ +│ - pipeline.py │ +│ - multi_video_pipeline.py │ +│ - ChromaDB (本地向量存储) │ +└─────────────────────────────────────┘ + ↓ 返回结构化 Markdown +ContentForge 内容输出管道 + → Notes / XHS / Slides / Video +``` + +**优势**: +- 保留 ClipIQ 完整的 Python 生态和 RAG 能力,无需重写核心逻辑 +- 与 ContentForge 主进程解耦,技术栈互不干扰 +- 可独立升级和维护 + +**替代方案:核心算法移植到 Rust/TS** +- 仅移植 `transcript.py` 的 fallback 策略(调用外部 API 和 yt-dlp) +- RAG pipeline 使用 ContentForge 已有的 AI Provider + 向量存储方案重新实现 +- 工作量更大,但架构更统一 + +#### 6.4 具体实施步骤建议 + +| 步骤 | 任务 | 预估工作量 | +|------|------|----------| +| 1 | 评估 ContentForge 当前视频分析模块的接口,确定对接点 | 1-2 天 | +| 2 | 提取 `transcript.py` 为独立模块,解耦 Chroma/配置依赖 | 2-3 天 | +| 3 | 将 ClipIQ FastAPI 服务封装为 ContentForge sidecar,标准化 API 响应格式 | 3-5 天 | +| 4 | 在 ContentForge 前端新增"视频智能分析"入口,调用 sidecar API | 3-5 天 | +| 5 | 集成双视频对比功能到 ContentForge 工作流 | 2-3 天 | +| 6 | 将 ClipIQ 生成的 Markdown 摘要接入 ContentForge 内容输出管道 | 2-3 天 | + +--- + +> **结论**:`youtube-rag-system`(ClipIQ)是一个功能完善、架构清晰的 YouTube 视频 RAG 分析系统。其转录提取引擎(多层 fallback)和 RAG pipeline(混合检索 + 时间戳接地)对 ContentForge 具有**高度整合价值**。建议采用 **Python Sidecar 微服务** 方案进行整合,优先复用 `transcript.py`、`pipeline.py` 和 `multi_video_pipeline.py` 三大核心模块,可显著提升 ContentForge 在视频智能分析领域的竞争力。 diff --git a/docs/how-to/desktop-app.md b/docs/how-to/desktop-app.md new file mode 100644 index 0000000..e606c53 --- /dev/null +++ b/docs/how-to/desktop-app.md @@ -0,0 +1,254 @@ +# 桌面应用启动与开发指南 + +本文档介绍如何启动 vYtDL Desktop 桌面应用,以及如何在不同场景下高效开发。 + +## 目录 + +- [环境准备](#环境准备) +- [快速启动](#快速启动) +- [开发模式详解](#开发模式详解) + - [模式一:完整桌面应用开发](#模式一完整桌面应用开发) + - [模式二:仅前端 UI 开发(无需桌面壳)](#模式二仅前端-ui-开发无需桌面壳) + - [模式三:前端 + Web API 模式](#模式三前端--web-api-模式) +- [项目结构速览](#项目结构速览) +- [构建生产版本](#构建生产版本) +- [常见问题](#常见问题) + +--- + +## 环境准备 + +确保已安装以下依赖(可运行 `docs/how-to/setup.sh` 一键安装): + +| 依赖 | 用途 | 验证命令 | +|------|------|----------| +| Node.js 20+ | 前端运行时 | `node --version` | +| pnpm 9+ | 包管理器 | `pnpm --version` | +| Rust | Tauri 桌面壳 | `rustc --version` | +| yt-dlp | 视频下载引擎 | `yt-dlp --version` | +| FFmpeg | 音频提取 | `ffmpeg -version` | + +验证所有依赖: + +```bash +cd vYtDL-desktop +task check +``` + +(如未安装 [Task](https://taskfile.dev/),可运行 `brew install go-task`) + +--- + +## 快速启动 + +```bash +# 进入桌面应用目录 +cd vYtDL-desktop + +# 方式 A:使用 Task(推荐) +task dev + +# 方式 B:使用 pnpm +pnpm tauri:dev +``` + +首次运行会自动执行 `pnpm install` 安装依赖。 + +`task dev` 等价于: +1. 安装 Node 依赖(如未安装) +2. 启动 Next.js 开发服务器(端口 3002) +3. 启动 Tauri 桌面窗口并连接到开发服务器 + +启动后会自动打开桌面窗口,前端代码支持热更新(HMR),修改 React 组件后页面自动刷新。 + +--- + +## 开发模式详解 + +### 模式一:完整桌面应用开发 + +**适用场景**:开发涉及 Rust 后端的功能(下载、文件系统操作、数据库),或需要测试完整的桌面集成。 + +```bash +cd vYtDL-desktop +pnpm tauri:dev +# 或 +task dev +``` + +**行为**: +- 启动 Next.js 开发服务器(`localhost:3002`) +- 编译 Rust 后端并以开发模式启动 Tauri 窗口 +- 前端热更新、Rust 代码更改需重新编译(Tauri 会自动处理) + +**停止**: + +```bash +task stop +# 或手动:Ctrl+C 然后 pkill -f "next dev"; pkill -f vytdl-desktop +``` + +--- + +### 模式二:仅前端 UI 开发(无需桌面壳) + +**适用场景**:只需要修改页面布局、样式、组件逻辑,不需要后端功能。这是最轻量的开发方式,启动快、热更新即时。 + +```bash +cd vYtDL-desktop/apps/desktop +pnpm dev +# Next.js 开发服务器运行在 http://localhost:3002 +``` + +**行为**: +- 仅启动 Next.js 开发服务器,不启动 Tauri 桌面窗口 +- 在浏览器中打开 `http://localhost:3002` 即可查看页面 +- React 组件修改即时热更新,无需重启 +- **不依赖任何桌面壳或 Rust 编译**,修改 UI 后页面自动刷新 + +**注意事项**: +- 由于没有 Tauri 后端,调用 Tauri IPC 的操作(如下载、文件操作)会失败 +- 纯 UI 层(布局、样式、组件交互、i18n 切换等)可以正常开发和预览 +- 状态管理(Zustand)在浏览器中同样有效 + +**何时使用模式二**: +- 调整页面布局和样式 +- 开发/调试 React 组件 +- 修改 i18n 翻译文案 +- 开发纯前端功能(表单校验、列表展示等) + +--- + +### 模式三:前端 + Web API 模式 + +**适用场景**:需要在浏览器中完整测试前后端功能,但不想启动桌面壳。需要先启动 Web API 服务器。 + +```bash +# 终端 1:启动 Web API 服务器 +cd vYtDL-desktop/web-server +pnpm dev + +# 终端 2:启动前端开发服务器 +cd vYtDL-desktop/apps/desktop +pnpm dev +``` + +前端会自动通过 `api-client.ts` 检测环境:在没有 Tauri IPC 时,回退到 HTTP API 模式(`POST /api/{command}`)。这样可以在浏览器中完整体验所有功能,包括下载、设置等。 + +--- + +## 项目结构速览 + +``` +vYtDL-desktop/ +├── apps/desktop/ # 桌面应用前端 +│ ├── src/ +│ │ ├── app/ # Next.js App Router 页面 +│ │ │ ├── page.tsx # 首页(下载) +│ │ │ ├── settings/ # 设置页 +│ │ │ ├── library/ # 媒体库 +│ │ │ ├── workspace/ # AI 工作区 +│ │ │ └── analyze/ # VTT 分析 +│ │ ├── components/ # React 组件 +│ │ │ ├── download-form.tsx # 下载表单 +│ │ │ ├── download-list.tsx # 下载列表 +│ │ │ └── layout/ # 布局组件 +│ │ ├── i18n/ # 国际化 +│ │ │ └── locales/ # en.json, zh.json, ja.json +│ │ ├── store/ # Zustand 状态管理 +│ │ ├── lib/api-client.ts # API 抽象层 +│ │ └── types/ # TypeScript 类型 +│ ├── src-tauri/ # Tauri Rust 后端 +│ │ ├── src/ +│ │ │ ├── lib.rs # 应用入口 +│ │ │ ├── commands.rs # IPC 命令 +│ │ │ ├── downloader.rs # 下载核心逻辑 +│ │ │ ├── queue.rs # 下载队列 +│ │ │ ├── database.rs # SQLite 数据库 +│ │ │ └── vtt_analysis.rs # VTT 分析 +│ │ └── Cargo.toml +│ └── package.json +├── packages/ui/ # 共享 UI 组件库 +├── packages/utils/ # 共享工具库 +├── web-server/ # Docker Web API 服务器 +├── scripts/ # 构建/启动脚本 +├── Taskfile.yml # Task 任务定义 +└── pnpm-workspace.yaml # pnpm 工作区配置 +``` + +--- + +## 构建生产版本 + +```bash +cd vYtDL-desktop + +# 构建桌面应用安装包 +task build +# 等价于:pnpm install && 下载 yt-dlp 二进制 && pnpm tauri:build + +# 查看构建产物 +task bundle +``` + +构建产物位于 `apps/desktop/src-tauri/target/release/bundle/`: +- macOS: `.dmg` 安装包 +- Windows: `.msi` / `.exe` 安装包 +- Linux: `.AppImage` / `.deb` + +--- + +## 常见问题 + +### 1. `tauri dev` 启动后页面空白 + +检查 Next.js 是否正常启动: + +```bash +cd vYtDL-desktop/apps/desktop +pnpm dev +# 访问 http://localhost:3002 确认页面正常 +``` + +### 2. yt-dlp 找不到 + +需要将 yt-dlp 二进制放入 Tauri 资源目录: + +```bash +cd vYtDL-desktop +python3 scripts/download-yt-dlp-binaries.py +# 或手动复制: +cp $(which yt-dlp) apps/desktop/src-tauri/resources/yt-dlp/macos/yt-dlp +touch apps/desktop/src-tauri/resources/yt-dlp/macos/.downloaded +``` + +### 3. pnpm 依赖安装失败 + +```bash +pnpm install --force +# 如仍有问题,清理后重装: +rm -rf node_modules apps/desktop/node_modules pnpm-lock.yaml +pnpm install +``` + +### 4. Tauri 编译报错(macOS) + +确保安装了 Xcode Command Line Tools: + +```bash +xcode-select --install +``` + +### 5. 端口 3002 被占用 + +```bash +# 查看占用进程 +lsof -i :3002 +# 杀掉进程 +kill -9 +``` + +### 6. 前端热更新不生效(模式二) + +- 确认在浏览器中打开的是 `http://localhost:3002` 而非构建产物 +- 检查 `apps/desktop/.next` 目录是否存在,可尝试删除后重启:`rm -rf apps/desktop/.next && cd apps/desktop && pnpm dev` diff --git a/package.json b/package.json index f7fb7b2..bf683dc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,11 @@ "check": "turbo lint typecheck", "clean": "turbo clean", "vytdl:dev": "pnpm --filter @vytdl/desktop tauri:dev", - "vytdl:build": "pnpm --filter @vytdl/desktop tauri:build" + "vytdl:build": "pnpm --filter @vytdl/desktop tauri:build", + "desktop:check": "task -d apps/vytdl-desktop check", + "desktop:dev": "task -d apps/vytdl-desktop dev", + "desktop:build": "task -d apps/vytdl-desktop build", + "desktop:bundle": "task -d apps/vytdl-desktop bundle" }, "devDependencies": { "prettier": "^3.4.0", diff --git a/packages/contentforge-core/python/contentforge/pipeline/engine.py b/packages/contentforge-core/python/contentforge/pipeline/engine.py index e53d51a..71d6981 100644 --- a/packages/contentforge-core/python/contentforge/pipeline/engine.py +++ b/packages/contentforge-core/python/contentforge/pipeline/engine.py @@ -45,6 +45,33 @@ def step_type(self) -> str: class IngestionHandler(StepHandler): """采集步骤处理器。""" + @property + def step_type(self) -> str: + return "ingest" + + def execute(self, step: PipelineStep, inputs: List[ContentUnit], context: Dict) -> List[ContentUnit]: + from contentforge.ingestion.agent_reach import AgentReachIngestor + + source_url = step.config.get("url", "") + platform = step.config.get("platform", "auto") + + ingestor = AgentReachIngestor() + + if platform == "twitter": + unit = ingestor.fetch_twitter(source_url) + elif platform == "youtube": + unit = ingestor.fetch_youtube(source_url) + elif platform == "rss": + units = ingestor.fetch_rss(source_url, limit=step.config.get("limit", 5)) + return units + elif platform == "web": + unit = ingestor.fetch_web(source_url) + else: + unit = ingestor.fetch(source_url) + + return [unit] + """采集步骤处理器。""" + @property def step_type(self) -> str: return "ingest" diff --git a/packages/contentforge-core/python/contentforge/pipeline/presets.py b/packages/contentforge-core/python/contentforge/pipeline/presets.py index 692d171..46ddc16 100644 --- a/packages/contentforge-core/python/contentforge/pipeline/presets.py +++ b/packages/contentforge-core/python/contentforge/pipeline/presets.py @@ -337,6 +337,15 @@ class PresetRegistry: """预设注册表(Go CLI 兼容)。""" def list_all(self) -> List[Dict]: + """返回所有预设列表(Go CLI 兼容格式)。""" + return [ + { + "id": name, + "name": name, + "description": PRESETS[name].description, + } + for name in list_presets() + ] """返回所有预设列表。""" return list_presets() diff --git a/packages/contentforge-core/python/contentforge/pipeline/runner.py b/packages/contentforge-core/python/contentforge/pipeline/runner.py index 93f3766..82410f1 100644 --- a/packages/contentforge-core/python/contentforge/pipeline/runner.py +++ b/packages/contentforge-core/python/contentforge/pipeline/runner.py @@ -80,6 +80,9 @@ def run( - 通过 pipeline_id 从预设加载 """ # 从预设加载 pipeline + if pipeline_id is not None and pipeline is None: + preset = get_preset(pipeline_id) + pipeline = preset.to_pipeline() if pipeline_id is not None and pipeline is None: preset = get_preset(pipeline_id) pipeline = preset diff --git a/packages/contentforge-core/python/contentforge/processing/ai_engine.py b/packages/contentforge-core/python/contentforge/processing/ai_engine.py index 58b500a..6a3f761 100644 --- a/packages/contentforge-core/python/contentforge/processing/ai_engine.py +++ b/packages/contentforge-core/python/contentforge/processing/ai_engine.py @@ -28,7 +28,7 @@ class AIAPIError(AIEngineError): @dataclass class AIConfig: - provider: str # "openai" | "claude" | "ollama" + provider: str = "openai" # "openai" | "claude" | "ollama" api_key: Optional[str] = None base_url: Optional[str] = None model: str = "gpt-4o-mini" diff --git a/packages/contentforge-core/python/contentforge/processing/analyzer.py b/packages/contentforge-core/python/contentforge/processing/analyzer.py index 90d9c84..e3a7619 100644 --- a/packages/contentforge-core/python/contentforge/processing/analyzer.py +++ b/packages/contentforge-core/python/contentforge/processing/analyzer.py @@ -15,7 +15,7 @@ from collections import Counter from typing import Dict, List, Optional, Set -from contentforge.models import ContentUnit +from contentforge.models import ContentUnit, ContentType, SourceInfo from contentforge.processing.ai_engine import AIEngine, AIEngineError logger = logging.getLogger(__name__) @@ -45,11 +45,11 @@ "topics": ["topic1", "topic2", ...], "keywords": ["keyword1", "keyword2", ...], "entities": ["entity1", "entity2", ...], - "sentiment": {{ + "sentiment": { "label": "positive|neutral|negative", "confidence": 0.85, "explanation": "..." - }}, + }, "audience": "description", "quality_score": 8, "quality_notes": "..." @@ -185,8 +185,8 @@ def analyze_text(self, text: str, mode: str = "ai") -> AnalysisResult: """分析纯文本。""" unit = ContentUnit( id="temp", - source=ContentUnit.__dataclass_fields__["source"].default_factory(), # type: ignore - type=ContentUnit.__dataclass_fields__["type"].default, + source=SourceInfo(platform="text", url=""), + type=ContentType.ARTICLE, extracted_text=text, ) return self.analyze(unit, mode=mode) diff --git a/packages/contentforge-core/python/contentforge/processing/summarizer.py b/packages/contentforge-core/python/contentforge/processing/summarizer.py index 8338e03..3c9a97a 100644 --- a/packages/contentforge-core/python/contentforge/processing/summarizer.py +++ b/packages/contentforge-core/python/contentforge/processing/summarizer.py @@ -11,7 +11,7 @@ import logging from typing import Dict, List, Optional -from contentforge.models import ContentUnit, ContentStatus +from contentforge.models import ContentUnit, ContentStatus, ContentType, SourceInfo from contentforge.processing.ai_engine import AIEngine, AIEngineError logger = logging.getLogger(__name__) @@ -182,12 +182,10 @@ def summarize_text( """对纯文本生成摘要。""" if max_length > 0: max_input_length = max_length - # 创建临时 ContentUnit - from contentforge.models import SourceInfo unit = ContentUnit( id="temp", source=SourceInfo(platform="text", url=""), - type=ContentUnit.type, # type: ignore + type=ContentType.ARTICLE, extracted_text=text, ) return self.summarize(unit, style=style, max_input_length=max_input_length) diff --git a/packages/contentforge-core/python/contentforge/processing/translator.py b/packages/contentforge-core/python/contentforge/processing/translator.py index c67c204..9499bde 100644 --- a/packages/contentforge-core/python/contentforge/processing/translator.py +++ b/packages/contentforge-core/python/contentforge/processing/translator.py @@ -11,7 +11,7 @@ import logging from typing import Dict, List, Optional -from contentforge.models import ContentUnit, ContentStatus +from contentforge.models import ContentUnit, ContentStatus, ContentType, SourceInfo from contentforge.processing.ai_engine import AIEngine, AIEngineError logger = logging.getLogger(__name__) @@ -229,11 +229,10 @@ def translate_text( mode: str = "full", ) -> TranslationResult: """翻译纯文本。""" - from contentforge.models import SourceInfo unit = ContentUnit( id="temp", source=SourceInfo(platform="text", url=""), - type=ContentUnit.__dataclass_fields__["type"].default, # type: ignore + type=ContentType.ARTICLE, extracted_text=text, ) return self.translate(unit, target_language=target_language, source_language=source_language, mode=mode) diff --git a/packages/contentforge-core/python/contentforge/processing/xiaohongshu_converter.py b/packages/contentforge-core/python/contentforge/processing/xiaohongshu_converter.py index c8f4c03..2a37ab1 100644 --- a/packages/contentforge-core/python/contentforge/processing/xiaohongshu_converter.py +++ b/packages/contentforge-core/python/contentforge/processing/xiaohongshu_converter.py @@ -15,7 +15,7 @@ import re from typing import Dict, List, Optional -from contentforge.models import ContentUnit, ContentStatus +from contentforge.models import ContentUnit, ContentStatus, ContentType, SourceInfo from contentforge.processing.ai_engine import AIEngine, AIEngineError logger = logging.getLogger(__name__) @@ -164,8 +164,8 @@ def convert_text( """转换纯文本。""" unit = ContentUnit( id="temp", - source=ContentUnit.__dataclass_fields__["source"].default_factory(), # type: ignore - type=ContentUnit.__dataclass_fields__["type"].default, + source=SourceInfo(platform="text", url=""), + type=ContentType.ARTICLE, extracted_text=text, ) return self.convert(unit, max_length=max_length, language=language, emoji_density=emoji_density) diff --git a/scripts/build-desktop.py b/scripts/build-desktop.py deleted file mode 100755 index ca246f5..0000000 --- a/scripts/build-desktop.py +++ /dev/null @@ -1,404 +0,0 @@ -#!/usr/bin/env python3 -""" -vYtDL Desktop Build & Run Script — Cross-platform (macOS, Linux, Windows) - -Supports development, production build, and distribution bundling. - -Usage: - python scripts/build-desktop.py dev # Run in development mode - python scripts/build-desktop.py build # Build production app - python scripts/build-desktop.py bundle # Build + create installer/package - python scripts/build-desktop.py check # Check dependencies only - -Options: - --target Cross-compile target (e.g., x86_64-pc-windows-msvc) - --verbose, -v Verbose output - --skip-deps Skip dependency installation step -""" - -import argparse -import os -import platform -import shutil -import subprocess -import sys -from pathlib import Path - - -# ───────────────────────────── Colors ───────────────────────────── -class Colors: - HEADER = "\033[95m" - OKBLUE = "\033[94m" - OKCYAN = "\033[96m" - OKGREEN = "\033[92m" - WARNING = "\033[93m" - FAIL = "\033[91m" - ENDC = "\033[0m" - BOLD = "\033[1m" - - -def color(text: str, code: str) -> str: - if sys.platform == "win32" and not os.environ.get("TERM"): - return text - return f"{code}{text}{Colors.ENDC}" - - -def info(msg: str) -> None: - print(f"{color('[INFO]', Colors.OKBLUE)} {msg}") - - -def ok(msg: str) -> None: - print(f"{color('[OK]', Colors.OKGREEN)} {msg}") - - -def warn(msg: str) -> None: - print(f"{color('[WARN]', Colors.WARNING)} {msg}") - - -def error(msg: str) -> None: - print(f"{color('[ERROR]', Colors.FAIL)} {msg}", file=sys.stderr) - - -def step(msg: str) -> None: - print(f"\n{color('▶', Colors.OKCYAN)} {color(msg, Colors.BOLD)}") - - -# ───────────────────────────── Paths ───────────────────────────── -def get_project_root() -> Path: - return Path(__file__).parent.parent.resolve() - - -def get_tauri_src_dir(project_dir: Path) -> Path: - return project_dir / "apps" / "desktop" / "src-tauri" - - -# ───────────────────────────── Dependency Checks ───────────────────────────── -def which(cmd: str) -> str | None: - """Find command in PATH.""" - result = shutil.which(cmd) - return result - - -def run_silently(cmd: list[str]) -> tuple[int, str, str]: - """Run a command silently and return (returncode, stdout, stderr).""" - try: - result = subprocess.run( - cmd, - capture_output=True, - text=True, - check=False, - ) - return result.returncode, result.stdout.strip(), result.stderr.strip() - except FileNotFoundError: - return -1, "", f"Command not found: {cmd[0]}" - - -def check_deps() -> dict[str, dict[str, str | bool]]: - """Check all required dependencies and return status.""" - deps = {} - - # pnpm - pnpm_path = which("pnpm") - deps["pnpm"] = { - "found": pnpm_path is not None, - "path": pnpm_path or "", - "version": "", - "install_hint": "Install pnpm: https://pnpm.io/installation", - } - if pnpm_path: - rc, out, _ = run_silently([pnpm_path, "--version"]) - if rc == 0: - deps["pnpm"]["version"] = out - - # Node.js - node_path = which("node") - deps["node"] = { - "found": node_path is not None, - "path": node_path or "", - "version": "", - "install_hint": "Install Node.js: https://nodejs.org/", - } - if node_path: - rc, out, _ = run_silently([node_path, "--version"]) - if rc == 0: - deps["node"]["version"] = out - - # Rust - rustc_path = which("rustc") - deps["rust"] = { - "found": rustc_path is not None, - "path": rustc_path or "", - "version": "", - "install_hint": "Install Rust: https://rustup.rs/", - } - if rustc_path: - rc, out, _ = run_silently([rustc_path, "--version"]) - if rc == 0: - deps["rust"]["version"] = out - - # cargo - cargo_path = which("cargo") - deps["cargo"] = { - "found": cargo_path is not None, - "path": cargo_path or "", - "version": "", - "install_hint": "Install Rust: https://rustup.rs/", - } - if cargo_path: - rc, out, _ = run_silently([cargo_path, "--version"]) - if rc == 0: - deps["cargo"]["version"] = out - - # git - git_path = which("git") - deps["git"] = { - "found": git_path is not None, - "path": git_path or "", - "version": "", - "install_hint": "Install git: https://git-scm.com/", - } - if git_path: - rc, out, _ = run_silently([git_path, "--version"]) - if rc == 0: - deps["git"]["version"] = out.splitlines()[0] - - return deps - - -def print_deps(deps: dict[str, dict[str, str | bool]]) -> None: - """Print dependency status in a table.""" - print() - print(f"{'Dependency':<12} {'Status':<10} {'Version':<20} {'Path'}") - print("-" * 70) - for name, info in deps.items(): - found = info["found"] - status = color("OK", Colors.OKGREEN) if found else color("MISSING", Colors.FAIL) - version = info.get("version", "") or "-" - path = info.get("path", "") or "-" - print(f"{name:<12} {status:<10} {version:<20} {path}") - print() - - -def verify_deps(required: list[str], deps: dict[str, dict[str, str | bool]]) -> bool: - """Verify required dependencies are present.""" - missing = [] - for name in required: - if not deps.get(name, {}).get("found", False): - missing.append(name) - - if missing: - error(f"Missing required dependencies: {', '.join(missing)}") - for name in missing: - hint = deps.get(name, {}).get("install_hint", "") - if hint: - info(f" → {hint}") - return False - return True - - -# ───────────────────────────── Build Steps ───────────────────────────── -def install_deps(project_dir: Path, verbose: bool = False) -> bool: - """Install Node dependencies via pnpm.""" - step("Installing Node dependencies") - pnpm = which("pnpm") - assert pnpm is not None - - cmd = [pnpm, "install"] - if not verbose: - cmd.append("--silent") - - return run_command(cmd, cwd=project_dir, verbose=verbose) == 0 - - -def download_yt_dlp_binaries(project_dir: Path, verbose: bool = False) -> bool: - """Download yt-dlp binaries for bundling.""" - resources_dir = project_dir / "apps" / "desktop" / "src-tauri" / "resources" / "yt-dlp" - - # Check if already extracted - marker = resources_dir / ".downloaded" - if marker.exists(): - info("yt-dlp binaries already present, skipping download") - return True - - script = project_dir / "scripts" / "download-yt-dlp-binaries.py" - if not script.exists(): - warn(f"Download script not found: {script}") - return True # Not fatal — user may have binaries already - - step("Downloading yt-dlp binaries") - return run_command([sys.executable, str(script)], cwd=project_dir, verbose=verbose) == 0 - - -def run_dev(project_dir: Path, target: str | None, verbose: bool = False) -> int: - """Run the desktop app in development mode.""" - step("Starting vYtDL Desktop in development mode") - info("Press Ctrl+C to stop") - - pnpm = which("pnpm") - assert pnpm is not None - - cmd = [pnpm, "tauri", "dev"] - if target: - cmd.extend(["--target", target]) - - return run_command(cmd, cwd=project_dir, verbose=verbose) - - -def run_build(project_dir: Path, target: str | None, verbose: bool = False) -> int: - """Build the desktop app for production.""" - step("Building vYtDL Desktop for production") - - pnpm = which("pnpm") - assert pnpm is not None - - cmd = [pnpm, "tauri", "build"] - if target: - cmd.extend(["--target", target]) - if verbose: - cmd.append("--verbose") - - return run_command(cmd, cwd=project_dir, verbose=verbose) - - -def run_bundle(project_dir: Path, target: str | None, verbose: bool = False) -> int: - """Build and bundle the desktop app (alias for build).""" - rc = run_build(project_dir, target, verbose) - if rc != 0: - return rc - - step("Build complete") - src_dir = get_tauri_src_dir(project_dir) - - # Show output locations - targets_dir = src_dir / "target" - if targets_dir.exists(): - info(f"Build artifacts: {targets_dir}") - release_dir = targets_dir / "release" / "bundle" - if release_dir.exists(): - ok(f"Installers/packages: {release_dir}") - # List contents - for item in sorted(release_dir.rglob("*")): - if item.is_file(): - size = item.stat().st_size - print(f" {item.relative_to(release_dir)} ({format_size(size)})") - - return 0 - - -def format_size(size: int) -> str: - """Format byte size to human readable.""" - for unit in ["B", "KB", "MB", "GB"]: - if size < 1024: - return f"{size:.1f} {unit}" - size /= 1024 - return f"{size:.1f} TB" - - -# ───────────────────────────── Command Runner ───────────────────────────── -def run_command(cmd: list[str], cwd: Path, verbose: bool = False) -> int: - """Run a command with streamed output.""" - info(f"Running: {' '.join(cmd)}") - info(f"Working directory: {cwd}") - - env = os.environ.copy() - # Ensure pnpm is in PATH if found - pnpm_dir = which("pnpm") - if pnpm_dir: - env["PATH"] = str(Path(pnpm_dir).parent) + os.pathsep + env.get("PATH", "") - - process = subprocess.Popen( - cmd, - cwd=cwd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - env=env, - ) - - if process.stdout: - for line in process.stdout: - print(line, end="") - - return process.wait() - - -# ───────────────────────────── Main ───────────────────────────── -def main() -> int: - parser = argparse.ArgumentParser( - description="Build and run vYtDL Desktop", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=__doc__, - ) - parser.add_argument( - "command", - choices=["dev", "build", "bundle", "check"], - help="Action to perform", - ) - parser.add_argument( - "--target", - metavar="TRIPLE", - help="Rust target triple for cross-compilation (e.g., x86_64-pc-windows-msvc)", - ) - parser.add_argument( - "--verbose", "-v", - action="store_true", - help="Enable verbose output", - ) - parser.add_argument( - "--skip-deps", - action="store_true", - help="Skip dependency installation step", - ) - - args = parser.parse_args() - - project_dir = get_project_root() - info(f"Project directory: {project_dir}") - info(f"Platform: {platform.system()} ({platform.machine()})") - - # ── Check dependencies ── - step("Checking dependencies") - deps = check_deps() - print_deps(deps) - - if args.command == "check": - required = ["pnpm", "node", "rust", "cargo"] - ok_status = verify_deps(required, deps) - return 0 if ok_status else 1 - - # For dev/build/bundle, require core deps - required = ["pnpm", "node"] - if args.command in ("build", "bundle"): - required.extend(["rust", "cargo"]) - - if not verify_deps(required, deps): - return 1 - - # ── Install Node deps ── - if not args.skip_deps: - if not install_deps(project_dir, verbose=args.verbose): - error("Failed to install dependencies") - return 1 - - # ── Download yt-dlp binaries ── - if args.command in ("build", "bundle"): - download_yt_dlp_binaries(project_dir, verbose=args.verbose) - - # ── Execute command ── - if args.command == "dev": - return run_dev(project_dir, args.target, verbose=args.verbose) - elif args.command == "build": - return run_build(project_dir, args.target, verbose=args.verbose) - elif args.command == "bundle": - return run_bundle(project_dir, args.target, verbose=args.verbose) - - return 0 - - -if __name__ == "__main__": - try: - sys.exit(main()) - except KeyboardInterrupt: - print("\nInterrupted by user") - sys.exit(130) diff --git a/scripts/download-yt-dlp-binaries.py b/scripts/download-yt-dlp-binaries.py index 70bb648..0120218 100644 --- a/scripts/download-yt-dlp-binaries.py +++ b/scripts/download-yt-dlp-binaries.py @@ -30,7 +30,7 @@ def get_project_root() -> Path: def get_resources_dir() -> Path: return ( - get_project_root() / "apps" / "desktop" / "src-tauri" / "resources" / "yt-dlp" + get_project_root() / "apps" / "vytdl-desktop" / "src-tauri" / "resources" / "yt-dlp" ) diff --git a/scripts/start-desktop.ps1 b/scripts/start-desktop.ps1 deleted file mode 100644 index b5ef1c4..0000000 --- a/scripts/start-desktop.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env pwsh - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" - -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$projectDir = (Resolve-Path (Join-Path $scriptDir "..")).Path - -Set-Location $projectDir - -if (-not (Get-Command pnpm -ErrorAction SilentlyContinue)) { - Write-Error "Error: pnpm is not installed.`nPlease install pnpm (https://pnpm.io/installation) and try again." - exit 1 -} - -Write-Host "Starting vYtDL Desktop..." -Write-Host "Project directory: $projectDir" - -pnpm tauri:dev diff --git a/scripts/start-desktop.py b/scripts/start-desktop.py deleted file mode 100644 index f391d40..0000000 --- a/scripts/start-desktop.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -""" -vYtDL Desktop Launcher - Cross-platform startup script - -Supports macOS, Linux, and Windows. - -Usage: - python scripts/start-desktop.py -""" - -import os -import subprocess -import sys -from pathlib import Path - - -def get_project_root() -> Path: - """Get the project root directory.""" - return Path(__file__).parent.parent.resolve() - - -def check_command(cmd: list[str]) -> bool: - """Check if a command exists in PATH.""" - try: - result = subprocess.run( - cmd, - capture_output=True, - text=True, - shell=(sys.platform == "win32" and cmd[0] == "where"), - ) - return result.returncode == 0 - except FileNotFoundError: - return False - - -def run_command(cmd: list[str], cwd: Path) -> int: - """Run a shell command and stream output.""" - print(f"[INFO] Running: {' '.join(cmd)}") - print(f"[INFO] Working directory: {cwd}") - process = subprocess.Popen( - cmd, - cwd=cwd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - ) - if process.stdout: - for line in process.stdout: - print(line, end="", flush=True) - return process.wait() - - -def main() -> int: - project_dir = get_project_root() - - # Check for pnpm - if sys.platform == "win32": - has_pnpm = check_command(["where", "pnpm"]) - else: - has_pnpm = check_command(["which", "pnpm"]) - - if not has_pnpm: - print("[ERROR] pnpm is not installed or not in PATH.") - print("[INFO] Please install pnpm: https://pnpm.io/installation") - return 1 - - print(f"[INFO] Project directory: {project_dir}") - print("[INFO] Delegating to build-desktop.py dev ...") - - build_script = project_dir / "scripts" / "build-desktop.py" - return run_command([sys.executable, str(build_script), "dev"], cwd=project_dir) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/start-desktop.sh b/scripts/start-desktop.sh deleted file mode 100755 index 9e64816..0000000 --- a/scripts/start-desktop.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" - -cd "${PROJECT_DIR}" - -if ! command -v pnpm >/dev/null 2>&1; then - echo "Error: pnpm is not installed." >&2 - echo "Please install pnpm (https://pnpm.io/installation) and try again." >&2 - exit 1 -fi - -echo "Starting vYtDL Desktop..." -echo "Project directory: ${PROJECT_DIR}" - -pnpm tauri:dev diff --git a/scripts/vytdl-launcher.py b/scripts/vytdl-launcher.py deleted file mode 100755 index da5300c..0000000 --- a/scripts/vytdl-launcher.py +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env python3 -""" -vYtDL Launcher - Development & Scheduling Tool - -Usage: - python vytdl-launcher.py dev # Start development environment - python vytdl-launcher.py build # Full production build - python vytdl-launcher.py clean # Clean all build artifacts - python vytdl-launcher.py schedule # Run scheduled download tasks -""" - -import argparse -import json -import os -import signal -import subprocess -import sys -import time -from datetime import datetime -from pathlib import Path -from typing import List, Optional - -# Color codes for terminal output -COLOR = { - "reset": "\033[0m", - "bold": "\033[1m", - "dim": "\033[2m", - "red": "\033[91m", - "green": "\033[92m", - "yellow": "\033[93m", - "blue": "\033[94m", - "magenta": "\033[95m", - "cyan": "\033[96m", -} - - -def log(level: str, msg: str) -> None: - """Print colored log message.""" - colors = { - "info": COLOR["blue"], - "success": COLOR["green"], - "warn": COLOR["yellow"], - "error": COLOR["red"], - "cmd": COLOR["cyan"], - } - prefix = f"{colors.get(level, '')}[{level.upper()}]{COLOR['reset']}" - print(f"{prefix} {msg}", flush=True) - - -def get_project_root() -> Path: - """Get the project root directory.""" - return Path(__file__).parent.parent.resolve() - - -def get_desktop_dir() -> Path: - """Get the desktop app directory.""" - return get_project_root() / "apps" / "desktop" - - -def run_command(cmd: List[str], cwd: Optional[Path] = None, env: Optional[dict] = None) -> int: - """Run a shell command and stream output.""" - log("cmd", f"{' '.join(cmd)} (cwd: {cwd or Path.cwd()})") - process = subprocess.Popen( - cmd, - cwd=cwd, - env={**os.environ, **(env or {})}, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - ) - if process.stdout: - for line in process.stdout: - print(line, end="", flush=True) - return process.wait() - - -class ProcessManager: - """Manages multiple subprocesses for dev mode.""" - - def __init__(self): - self.processes: List[subprocess.Popen] = [] - self._shutdown = False - - def add(self, name: str, cmd: List[str], cwd: Optional[Path] = None) -> subprocess.Popen: - """Start and track a subprocess.""" - log("info", f"Starting {COLOR['bold']}{name}{COLOR['reset']}...") - proc = subprocess.Popen( - cmd, - cwd=cwd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - ) - self.processes.append(proc) - return proc - - def stream(self, name: str, proc: subprocess.Popen, color: str) -> None: - """Stream process output with a colored prefix.""" - prefix = f"{COLOR[color]}[{name}]{COLOR['reset']}" - if proc.stdout: - for line in proc.stdout: - print(f"{prefix} {line}", end="", flush=True) - - def shutdown(self, *_args) -> None: - """Gracefully terminate all tracked processes.""" - if self._shutdown: - return - self._shutdown = True - log("warn", "Shutting down processes...") - for proc in self.processes: - if proc.poll() is None: - proc.terminate() - try: - proc.wait(timeout=5) - except subprocess.TimeoutExpired: - proc.kill() - log("success", "All processes stopped.") - sys.exit(0) - - -def cmd_dev() -> int: - """Start the full development environment.""" - desktop = get_desktop_dir() - pm = ProcessManager() - - # Register signal handlers for graceful shutdown - signal.signal(signal.SIGINT, pm.shutdown) - signal.signal(signal.SIGTERM, pm.shutdown) - - log("info", f"Project root: {get_project_root()}") - log("info", f"Desktop app: {desktop}") - log("info", "Starting development environment...") - print() - - # Start Next.js dev server - frontend = pm.add( - "frontend", - ["pnpm", "next", "dev", "--port", "3002"], - cwd=desktop, - ) - - # Wait a moment for Next.js to start - time.sleep(2) - - # Start Tauri dev - tauri = pm.add( - "tauri", - ["pnpm", "tauri", "dev"], - cwd=desktop, - ) - - # Stream both outputs - from threading import Thread - - t1 = Thread(target=pm.stream, args=("frontend", frontend, "cyan")) - t2 = Thread(target=pm.stream, args=("tauri", tauri, "magenta")) - t1.daemon = True - t2.daemon = True - t1.start() - t2.start() - - # Wait for either process to exit - while True: - time.sleep(0.5) - if frontend.poll() is not None: - log("error", f"Frontend exited with code {frontend.returncode}") - pm.shutdown() - return frontend.returncode - if tauri.poll() is not None: - log("error", f"Tauri exited with code {tauri.returncode}") - pm.shutdown() - return tauri.returncode - - -def cmd_build() -> int: - """Run a full production build.""" - desktop = get_desktop_dir() - log("info", "Starting full production build...") - - # Step 1: Build frontend - log("info", "Building Next.js frontend...") - code = run_command(["pnpm", "build"], cwd=desktop) - if code != 0: - log("error", "Frontend build failed!") - return code - log("success", "Frontend build complete.") - - # Step 2: Build Rust backend - log("info", "Building Tauri Rust backend...") - code = run_command(["cargo", "build", "--release"], cwd=desktop / "src-tauri") - if code != 0: - log("error", "Rust build failed!") - return code - log("success", "Rust build complete.") - - log("success", "Full production build successful!") - return 0 - - -def cmd_clean() -> int: - """Clean all build artifacts.""" - desktop = get_desktop_dir() - paths_to_clean = [ - desktop / ".next", - desktop / "out", - desktop / "node_modules" / ".cache", - desktop / "src-tauri" / "target", - ] - - log("info", "Cleaning build artifacts...") - for path in paths_to_clean: - if path.exists(): - log("cmd", f"rm -rf {path}") - if path.is_dir(): - import shutil - shutil.rmtree(path) - else: - path.unlink() - - # Also clean packages/ui - ui_dir = get_project_root() / "packages" / "ui" - for name in [".turbo", ".cache"]: - p = ui_dir / name - if p.exists(): - import shutil - shutil.rmtree(p) - - log("success", "Clean complete.") - return 0 - - -def cmd_schedule() -> int: - """Run scheduled download tasks.""" - config_path = get_project_root() / "scripts" / "schedule.json" - - if not config_path.exists(): - # Create example config - example = { - "tasks": [ - { - "name": "Daily Playlist Backup", - "url": "https://www.youtube.com/playlist?list=...", - "cron": "0 2 * * *", - "quality": "1080", - "format": "mp4", - "output_dir": "~/Downloads/vYtDL/scheduled", - "enabled": False, - } - ], - "settings": { - "yt_dlp_path": "yt-dlp", - "default_output_dir": "~/Downloads/vYtDL/scheduled", - }, - } - config_path.write_text(json.dumps(example, indent=2, ensure_ascii=False)) - log("warn", f"Created example schedule config: {config_path}") - log("info", "Please edit the config and re-run.") - return 0 - - config = json.loads(config_path.read_text()) - tasks = [t for t in config.get("tasks", []) if t.get("enabled", False)] - - if not tasks: - log("warn", "No enabled tasks found in schedule config.") - return 0 - - log("info", f"Running {len(tasks)} scheduled task(s)...") - yt_dlp = config.get("settings", {}).get("yt_dlp_path", "yt-dlp") - - for task in tasks: - name = task["name"] - url = task["url"] - output_dir = os.path.expanduser(task.get("output_dir", "~/Downloads/vYtDL/scheduled")) - quality = task.get("quality", "best") - fmt = task.get("format", "mp4") - - log("info", f"Task: {name}") - log("cmd", f"URL: {url}") - - os.makedirs(output_dir, exist_ok=True) - - cmd = [ - yt_dlp, - "--format", f"bestvideo[height<={quality}]+bestaudio/best" if quality != "best" else "best", - "--merge-output-format", fmt, - "--output", os.path.join(output_dir, "%(title)s.%(ext)s"), - "--write-subs", - "--sub-langs", "en,zh", - "--no-playlist" if not task.get("is_playlist") else "--yes-playlist", - url, - ] - - code = run_command(cmd) - if code == 0: - log("success", f"Task '{name}' completed successfully.") - else: - log("error", f"Task '{name}' failed with exit code {code}.") - - return 0 - - -def main() -> int: - parser = argparse.ArgumentParser( - description="vYtDL Launcher - Development & Scheduling Tool", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - python vytdl-launcher.py dev # Start dev environment - python vytdl-launcher.py build # Full production build - python vytdl-launcher.py clean # Clean artifacts - python vytdl-launcher.py schedule # Run scheduled downloads - """, - ) - parser.add_argument( - "command", - choices=["dev", "build", "clean", "schedule"], - help="Command to run", - ) - args = parser.parse_args() - - # Ensure we're in the right directory - os.chdir(get_project_root()) - - commands = { - "dev": cmd_dev, - "build": cmd_build, - "clean": cmd_clean, - "schedule": cmd_schedule, - } - - return commands[args.command]() - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tasks/refactor/desktop/desktop.md b/tasks/refactor/desktop/desktop.md new file mode 100644 index 0000000..4713716 --- /dev/null +++ b/tasks/refactor/desktop/desktop.md @@ -0,0 +1,6 @@ +# Desktop Application + +## Task 1: 检查项目开始编写如何启动桌面应用 + +1. docs 目录中添加如何启动destkop application 脚本 +2. 如何可以在更新页面的情况下也不启动桌面应用,也可以更新 \ No newline at end of file diff --git a/tasks/tracing/Monorepo-refactor-unify-all-projects-into-single-monorepo/issue-1.md b/tasks/tracing/Monorepo-refactor-unify-all-projects-into-single-monorepo/issue-1.md new file mode 100644 index 0000000..e57cca3 --- /dev/null +++ b/tasks/tracing/Monorepo-refactor-unify-all-projects-into-single-monorepo/issue-1.md @@ -0,0 +1,20 @@ +# Tracing: Monorepo refactor: unify all projects into single monorepo + +## Task Entry (2026-07-26 23:42:12) + +- **Issue**: #1 +- **Title**: Monorepo refactor: unify all projects into single monorepo +- **Started At**: 2026-07-26 23:42:12 +- **Status**: completed + +### Original Task Description + +```markdown +Refactor the repository into a unified monorepo structure with pnpm workspaces, go.work, and Turborepo. +``` +- **Completed At**: 2026-07-27 07:02:09 + +### Implementation Summary + +Monorepo refactoring completed. All projects moved via git mv to new dir structure. apps/ packages/ tools/ services/ extensions/. pnpm install and go build verified. + diff --git a/vYtDL-desktop/package.json b/vYtDL-desktop/package.json deleted file mode 100644 index aec9175..0000000 --- a/vYtDL-desktop/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "vytdl-desktop-monorepo", - "private": true, - "version": "0.1.0", - "packageManager": "pnpm@9.0.0", - "scripts": { - "dev": "pnpm --filter @vytdl/desktop dev", - "build": "pnpm --filter @vytdl/desktop build", - "tauri": "pnpm --filter @vytdl/desktop tauri", - "tauri:dev": "pnpm --filter @vytdl/desktop tauri:dev", - "tauri:build": "pnpm --filter @vytdl/desktop tauri:build", - "lint": "pnpm -r lint", - "format": "pnpm -r format", - "desktop:check": "python3 scripts/build-desktop.py check", - "desktop:dev": "python3 scripts/build-desktop.py dev --skip-deps", - "desktop:build": "python3 scripts/build-desktop.py build", - "desktop:bundle": "python3 scripts/build-desktop.py bundle" - }, - "devDependencies": { - "typescript": "^5.6.0" - } -} diff --git a/vYtDL-desktop/pnpm-workspace.yaml b/vYtDL-desktop/pnpm-workspace.yaml deleted file mode 100644 index e9b0dad..0000000 --- a/vYtDL-desktop/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - - 'apps/*' - - 'packages/*' From 431b3c7e5cd0027bd40376f5251499a40caf9af4 Mon Sep 17 00:00:00 2001 From: damnPat Date: Mon, 3 Aug 2026 23:12:19 +0800 Subject: [PATCH 2/2] fix: correct monorepo paths for ContentForge env and yt-dlp bootstrap PYTHONPATH and Tauri yt-dlp resources still pointed at pre-monorepo locations, which broke contentforge:check and desktop cargo builds. Co-authored-by: Cursor --- packages/contentforge-core/scripts/cf-env.sh | 2 +- scripts/bootstrap-ytdlp-dev.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contentforge-core/scripts/cf-env.sh b/packages/contentforge-core/scripts/cf-env.sh index 99f4c67..96c01c5 100644 --- a/packages/contentforge-core/scripts/cf-env.sh +++ b/packages/contentforge-core/scripts/cf-env.sh @@ -10,6 +10,6 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" export PATH="/opt/homebrew/bin:${PATH}" export PATH="${REPO_ROOT}/.venv-cf/bin:${PATH}" -export PYTHONPATH="${REPO_ROOT}/contentforge/core/python:${PYTHONPATH}" +export PYTHONPATH="${REPO_ROOT}/packages/contentforge-core/python:${PYTHONPATH}" export CF_HOME="${REPO_ROOT}" export CONTENTFORGE_VENV="${REPO_ROOT}/.venv-cf" diff --git a/scripts/bootstrap-ytdlp-dev.sh b/scripts/bootstrap-ytdlp-dev.sh index b82c861..d19c24a 100755 --- a/scripts/bootstrap-ytdlp-dev.sh +++ b/scripts/bootstrap-ytdlp-dev.sh @@ -4,7 +4,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RES_DIR="${SCRIPT_DIR}/../apps/desktop/src-tauri/resources/yt-dlp" +RES_DIR="${SCRIPT_DIR}/../apps/vytdl-desktop/src-tauri/resources/yt-dlp" MACOS_DIR="${RES_DIR}/macos" MARKER="${MACOS_DIR}/.downloaded"