From 708114eec51ccd62c1d0ab72d50b040a94c41811 Mon Sep 17 00:00:00 2001 From: Yao Jingxi <23722032@bjtu.edu.cn> Date: Sun, 12 Jul 2026 21:29:16 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E9=87=8D=E5=86=99=E4=B8=BA=20Rust=20+=20Ta?= =?UTF-8?q?uri=20=E6=A1=8C=E9=9D=A2=E5=BA=94=E7=94=A8=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=20Python=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除全部 Python 代码(ftb_translater 包、main.py、pyproject/uv.lock、tests) - 新增 Tauri 2 后端:SNBT 解析、章节抽取、格式保护、DeepSeek 批量并发翻译、SQLite 历史与 ZIP 导出、系统钥匙串存储 API Key - 新增 React + Vite 前端工作台/历史/设置界面 - CI 改为 Node + Rust 构建打包 - 修正默认模型为有效的 deepseek-chat(原 deepseek-v4-flash 无效,会导致首次翻译请求失败) --- .env.example | 5 - .github/workflows/build.yml | 193 +- .gitignore | 10 +- README.md | 203 +- ftb_translater/__init__.py | 3 - ftb_translater/app.py | 1539 ----- ftb_translater/backup.py | 30 - ftb_translater/cache.py | 56 - ftb_translater/chapters.py | 226 - ftb_translater/config.py | 168 - ftb_translater/credential_store.py | 161 - ftb_translater/deepseek_client.py | 170 - ftb_translater/format_guard.py | 470 -- ftb_translater/history_db.py | 303 - ftb_translater/logger.py | 56 - ftb_translater/paths.py | 104 - ftb_translater/report.py | 44 - ftb_translater/snbt.py | 209 - ftb_translater/translator.py | 582 -- ftb_translater/user_paths.py | 24 - index.html | 5 + main.py | 5 - package-lock.json | 2072 +++++++ package.json | 26 + pyproject.toml | 21 - src-tauri/Cargo.lock | 5137 +++++++++++++++++ src-tauri/Cargo.toml | 33 + src-tauri/app-icon.svg | 8 + src-tauri/build.rs | 3 + src-tauri/capabilities/default.json | 7 + src-tauri/gen/schemas/acl-manifests.json | 1 + src-tauri/gen/schemas/capabilities.json | 1 + src-tauri/gen/schemas/desktop-schema.json | 2358 ++++++++ src-tauri/gen/schemas/macOS-schema.json | 2358 ++++++++ src-tauri/icons/128x128.png | Bin 0 -> 2252 bytes src-tauri/icons/128x128@2x.png | Bin 0 -> 4629 bytes src-tauri/icons/32x32.png | Bin 0 -> 647 bytes src-tauri/icons/64x64.png | Bin 0 -> 1247 bytes src-tauri/icons/Square107x107Logo.png | Bin 0 -> 1864 bytes src-tauri/icons/Square142x142Logo.png | Bin 0 -> 2615 bytes src-tauri/icons/Square150x150Logo.png | Bin 0 -> 2983 bytes src-tauri/icons/Square284x284Logo.png | Bin 0 -> 5208 bytes src-tauri/icons/Square30x30Logo.png | Bin 0 -> 632 bytes src-tauri/icons/Square310x310Logo.png | Bin 0 -> 5926 bytes src-tauri/icons/Square44x44Logo.png | Bin 0 -> 844 bytes src-tauri/icons/Square71x71Logo.png | Bin 0 -> 1428 bytes src-tauri/icons/Square89x89Logo.png | Bin 0 -> 1705 bytes src-tauri/icons/StoreLogo.png | Bin 0 -> 992 bytes .../android/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../icons/android/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1647 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 3006 bytes .../android/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 1325 bytes .../icons/android/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1592 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 2081 bytes .../android/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1318 bytes .../android/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3525 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 4106 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 2732 bytes .../android/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 5308 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 6086 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 4143 bytes .../android/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 7373 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 8297 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 5814 bytes .../android/values/ic_launcher_background.xml | 4 + src-tauri/icons/icon.icns | Bin 0 -> 53612 bytes src-tauri/icons/icon.ico | Bin 0 -> 9533 bytes src-tauri/icons/icon.png | Bin 0 -> 9086 bytes src-tauri/icons/ios/AppIcon-20x20@1x.png | Bin 0 -> 389 bytes src-tauri/icons/ios/AppIcon-20x20@2x-1.png | Bin 0 -> 800 bytes src-tauri/icons/ios/AppIcon-20x20@2x.png | Bin 0 -> 800 bytes src-tauri/icons/ios/AppIcon-20x20@3x.png | Bin 0 -> 1259 bytes src-tauri/icons/ios/AppIcon-29x29@1x.png | Bin 0 -> 614 bytes src-tauri/icons/ios/AppIcon-29x29@2x-1.png | Bin 0 -> 1129 bytes src-tauri/icons/ios/AppIcon-29x29@2x.png | Bin 0 -> 1129 bytes src-tauri/icons/ios/AppIcon-29x29@3x.png | Bin 0 -> 1752 bytes src-tauri/icons/ios/AppIcon-40x40@1x.png | Bin 0 -> 800 bytes src-tauri/icons/ios/AppIcon-40x40@2x-1.png | Bin 0 -> 1572 bytes src-tauri/icons/ios/AppIcon-40x40@2x.png | Bin 0 -> 1572 bytes src-tauri/icons/ios/AppIcon-40x40@3x.png | Bin 0 -> 2409 bytes src-tauri/icons/ios/AppIcon-512@2x.png | Bin 0 -> 19844 bytes src-tauri/icons/ios/AppIcon-60x60@2x.png | Bin 0 -> 2409 bytes src-tauri/icons/ios/AppIcon-60x60@3x.png | Bin 0 -> 3513 bytes src-tauri/icons/ios/AppIcon-76x76@1x.png | Bin 0 -> 1411 bytes src-tauri/icons/ios/AppIcon-76x76@2x.png | Bin 0 -> 2981 bytes src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png | Bin 0 -> 3197 bytes src-tauri/src/chapters.rs | 136 + src-tauri/src/core.rs | 584 ++ src-tauri/src/lib.rs | 65 + src-tauri/src/main.rs | 3 + src-tauri/src/snbt.rs | 188 + src-tauri/src/storage.rs | 236 + src-tauri/tauri.conf.json | 12 + src/main.tsx | 100 + src/styles.css | 14 + tests/run_groups.py | 37 - tests/test_core.py | 583 -- tests/test_credential_store.py | 80 - tests/test_history_db.py | 168 - tests/test_live_curseforge.py | 147 - tests/test_live_deepseek_e2e.py | 152 - tsconfig.json | 9 + uv.lock | 773 --- vite.config.ts | 9 + 104 files changed, 13445 insertions(+), 6451 deletions(-) delete mode 100644 .env.example delete mode 100644 ftb_translater/__init__.py delete mode 100644 ftb_translater/app.py delete mode 100644 ftb_translater/backup.py delete mode 100644 ftb_translater/cache.py delete mode 100644 ftb_translater/chapters.py delete mode 100644 ftb_translater/config.py delete mode 100644 ftb_translater/credential_store.py delete mode 100644 ftb_translater/deepseek_client.py delete mode 100644 ftb_translater/format_guard.py delete mode 100644 ftb_translater/history_db.py delete mode 100644 ftb_translater/logger.py delete mode 100644 ftb_translater/paths.py delete mode 100644 ftb_translater/report.py delete mode 100644 ftb_translater/snbt.py delete mode 100644 ftb_translater/translator.py delete mode 100644 ftb_translater/user_paths.py create mode 100644 index.html delete mode 100644 main.py create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 pyproject.toml create mode 100644 src-tauri/Cargo.lock create mode 100644 src-tauri/Cargo.toml create mode 100644 src-tauri/app-icon.svg create mode 100644 src-tauri/build.rs create mode 100644 src-tauri/capabilities/default.json create mode 100644 src-tauri/gen/schemas/acl-manifests.json create mode 100644 src-tauri/gen/schemas/capabilities.json create mode 100644 src-tauri/gen/schemas/desktop-schema.json create mode 100644 src-tauri/gen/schemas/macOS-schema.json create mode 100644 src-tauri/icons/128x128.png create mode 100644 src-tauri/icons/128x128@2x.png create mode 100644 src-tauri/icons/32x32.png create mode 100644 src-tauri/icons/64x64.png create mode 100644 src-tauri/icons/Square107x107Logo.png create mode 100644 src-tauri/icons/Square142x142Logo.png create mode 100644 src-tauri/icons/Square150x150Logo.png create mode 100644 src-tauri/icons/Square284x284Logo.png create mode 100644 src-tauri/icons/Square30x30Logo.png create mode 100644 src-tauri/icons/Square310x310Logo.png create mode 100644 src-tauri/icons/Square44x44Logo.png create mode 100644 src-tauri/icons/Square71x71Logo.png create mode 100644 src-tauri/icons/Square89x89Logo.png create mode 100644 src-tauri/icons/StoreLogo.png create mode 100644 src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 src-tauri/icons/android/mipmap-hdpi/ic_launcher.png create mode 100644 src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png create mode 100644 src-tauri/icons/android/mipmap-mdpi/ic_launcher.png create mode 100644 src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png create mode 100644 src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png create mode 100644 src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png create mode 100644 src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png create mode 100644 src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 src-tauri/icons/android/values/ic_launcher_background.xml create mode 100644 src-tauri/icons/icon.icns create mode 100644 src-tauri/icons/icon.ico create mode 100644 src-tauri/icons/icon.png create mode 100644 src-tauri/icons/ios/AppIcon-20x20@1x.png create mode 100644 src-tauri/icons/ios/AppIcon-20x20@2x-1.png create mode 100644 src-tauri/icons/ios/AppIcon-20x20@2x.png create mode 100644 src-tauri/icons/ios/AppIcon-20x20@3x.png create mode 100644 src-tauri/icons/ios/AppIcon-29x29@1x.png create mode 100644 src-tauri/icons/ios/AppIcon-29x29@2x-1.png create mode 100644 src-tauri/icons/ios/AppIcon-29x29@2x.png create mode 100644 src-tauri/icons/ios/AppIcon-29x29@3x.png create mode 100644 src-tauri/icons/ios/AppIcon-40x40@1x.png create mode 100644 src-tauri/icons/ios/AppIcon-40x40@2x-1.png create mode 100644 src-tauri/icons/ios/AppIcon-40x40@2x.png create mode 100644 src-tauri/icons/ios/AppIcon-40x40@3x.png create mode 100644 src-tauri/icons/ios/AppIcon-512@2x.png create mode 100644 src-tauri/icons/ios/AppIcon-60x60@2x.png create mode 100644 src-tauri/icons/ios/AppIcon-60x60@3x.png create mode 100644 src-tauri/icons/ios/AppIcon-76x76@1x.png create mode 100644 src-tauri/icons/ios/AppIcon-76x76@2x.png create mode 100644 src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png create mode 100644 src-tauri/src/chapters.rs create mode 100644 src-tauri/src/core.rs create mode 100644 src-tauri/src/lib.rs create mode 100644 src-tauri/src/main.rs create mode 100644 src-tauri/src/snbt.rs create mode 100644 src-tauri/src/storage.rs create mode 100644 src-tauri/tauri.conf.json create mode 100644 src/main.tsx create mode 100644 src/styles.css delete mode 100644 tests/run_groups.py delete mode 100644 tests/test_core.py delete mode 100644 tests/test_credential_store.py delete mode 100644 tests/test_history_db.py delete mode 100644 tests/test_live_curseforge.py delete mode 100644 tests/test_live_deepseek_e2e.py create mode 100644 tsconfig.json delete mode 100644 uv.lock create mode 100644 vite.config.ts diff --git a/.env.example b/.env.example deleted file mode 100644 index 097c3e1..0000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# API Key is saved through the app settings UI, not in this file. -DEEPSEEK_BASE_URL=https://api.deepseek.com -DEEPSEEK_MODEL=deepseek-v4-flash -FTB_TRANSLATER_BATCH_SIZE=auto -FTB_TRANSLATER_CONCURRENCY=auto diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b8fde1b..07eeeb4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,23 +3,9 @@ name: CI/CD on: pull_request: push: - branches: - - main - - master - tags: - - "v*" + branches: [main, master] + tags: ["v*"] workflow_dispatch: - inputs: - run_live_e2e: - description: "Run live CurseForge/DeepSeek e2e tests" - required: false - default: false - type: boolean - live_deepseek_entries: - description: "Sample size for live DeepSeek e2e" - required: false - default: "12" - type: string concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -28,163 +14,54 @@ concurrency: permissions: contents: read -env: - PYTHON_VERSION: "3.11" - UV_LOCKED: "1" - UV_SYSTEM_PYTHON: "1" - jobs: test: name: Test (${{ matrix.os }}) - runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: - - ubuntu-latest - - windows-latest - - macos-latest - + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - enable-cache: true - - - name: Sync dependencies - run: uv sync --dev --locked - - - name: Run unit tests - run: uv run python -m tests.run_groups unit + node-version: 22 + cache: npm + - uses: dtolnay/rust-toolchain@stable + - name: Linux system dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + - run: npm ci + - run: npm run build + - run: cargo test --manifest-path src-tauri/Cargo.toml package: - name: Package (${{ matrix.suffix }}) + name: Package (${{ matrix.os }}) needs: test - runs-on: ${{ matrix.os }} if: github.event_name != 'pull_request' strategy: fail-fast: false matrix: - include: - - os: windows-latest - suffix: windows - - os: macos-latest - suffix: macos - + os: [windows-latest, macos-latest] + runs-on: ${{ matrix.os }} steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - enable-cache: true - - - name: Sync dependencies - run: uv sync --dev --locked - - - name: Build Windows exe - if: runner.os == 'Windows' - run: > - uv run python -m PyInstaller - --noconfirm - --clean - --onefile - --windowed - --name FTB-Translater - --collect-data customtkinter - main.py - - - name: Prepare Windows artifact - if: runner.os == 'Windows' - shell: pwsh - run: | - New-Item -ItemType Directory -Force artifacts | Out-Null - Copy-Item "dist/FTB-Translater.exe" "artifacts/FTB-Translater-windows.exe" -Force - Get-Item "artifacts/FTB-Translater-windows.exe" - - - name: Build macOS app - if: runner.os == 'macOS' - run: > - uv run python -m PyInstaller - --noconfirm - --clean - --windowed - --name FTB-Translater - --collect-data customtkinter - main.py - - - name: Prepare macOS dmg - if: runner.os == 'macOS' - run: | - mkdir -p artifacts - hdiutil create \ - -volname FTB-Translater \ - -srcfolder dist/FTB-Translater.app \ - -ov \ - -format UDZO \ - artifacts/FTB-Translater-macos.dmg - ls -lh artifacts/FTB-Translater-macos.dmg - - - name: Upload package artifact - uses: actions/upload-artifact@v4 + node-version: 22 + cache: npm + - uses: dtolnay/rust-toolchain@stable + - run: npm ci + - run: npm run tauri -- build + - uses: actions/upload-artifact@v4 with: - name: FTB-Translater-${{ matrix.suffix }} - path: artifacts/* + name: FTB-Translater-${{ runner.os }} + path: | + src-tauri/target/release/bundle/dmg/*.dmg + src-tauri/target/release/bundle/nsis/*.exe + src-tauri/target/release/bundle/msi/*.msi if-no-files-found: error - retention-days: 14 - - live-e2e: - name: Live e2e - needs: test - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' && inputs.run_live_e2e - env: - DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} - FTB_TRANSLATER_CURSEFORGE_URL: ${{ secrets.FTB_TRANSLATER_CURSEFORGE_URL }} - FTB_TRANSLATER_LIVE_DEEPSEEK_ENTRIES: ${{ inputs.live_deepseek_entries }} - - steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - enable-cache: true - - - name: Sync dependencies - run: uv sync --dev --locked - - - name: Run full e2e tests - run: uv run python -m tests.run_groups e2e - - - name: Upload e2e output - if: always() - uses: actions/upload-artifact@v4 - with: - name: live-e2e-output - path: .ftb-translater/e2e-runs/ - if-no-files-found: ignore - retention-days: 7 release: name: Publish GitHub release @@ -193,15 +70,11 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') permissions: contents: write - steps: - - name: Download package artifacts - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v4 with: path: release-artifacts merge-multiple: true - - - name: Publish release assets - uses: softprops/action-gh-release@v2 + - uses: softprops/action-gh-release@v2 with: files: release-artifacts/* diff --git a/.gitignore b/.gitignore index 964bb1d..91f7dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,6 @@ -.env -.venv/ .idea/ -__pycache__/ -*.py[cod] -.pytest_cache/ .ftb-translater/ history.sqlite3 -*.egg-info/ -build/ dist/ -*.spec +node_modules/ +src-tauri/target/ diff --git a/README.md b/README.md index fd94d29..3df9c5b 100644 --- a/README.md +++ b/README.md @@ -1,192 +1,61 @@ # FTB Translater -FTB Translater 是一个用于汉化现代 FTB Quests 任务文本的桌面工具,翻译接口使用 DeepSeek。目前固定支持 `en_us -> zh_cn`。 +用于汉化现代 FTB Quests 任务文本的纯 Rust + Tauri 桌面工具。目前固定支持 `en_us → zh_cn`,翻译接口兼容 DeepSeek Chat Completions API。 ## 功能 -- 支持新版语言文件:`config/ftbquests/quests/lang/en_us.snbt` -- 支持章节式任务文件:`config/ftbquests/quests/chapters/*.snbt` -- `lang` 模式写入或覆盖 `lang/zh_cn.snbt` -- `chapters` 模式原地改写 `chapters/*.snbt` 中可翻译文本字段 -- 写入前自动备份 `lang` 或 `chapters` 目录 -- 生成翻译缓存、报告、备份和可导出的翻译历史 +- 支持 `config/ftbquests/quests/lang/en_us.snbt` +- 支持 `config/ftbquests/quests/chapters/*.snbt` +- 自动识别整合包、config、quests、lang 或 chapters 目录 +- 写入前自动备份原始 `lang` 或 `chapters` 目录 +- 自动批处理与并发 DeepSeek 请求 +- 保护颜色码、占位符、资源 ID、宏、URL、转义序列和换行 +- 格式检查失败时保留原文,支持在结果页人工修正 +- 翻译缓存、JSON 报告、SQLite 历史与 ZIP 导出 +- API Key 存入 macOS 钥匙串、Windows 凭据管理器或 Linux Secret Service +- 浅色/深色主题和响应式桌面布局 -## 安装 +项目不依赖 Python,也不会在运行时启动 sidecar 或外部解释器。 -需要 Python 3.11 或更高版本。 +## 开发环境 -```powershell -python -m pip install -e . -``` - -也可以使用 uv: - -```powershell -uv sync --dev -``` +- Node.js 20+ +- Rust stable +- Tauri 2 对应的系统依赖 -## 运行 +安装依赖: -```powershell -python main.py +```bash +npm install ``` -安装为可执行脚本后也可以运行: - -```powershell -ftb-translater -``` - -启动后选择整合包目录即可。也可以直接选择它下面的 `config`、`config/ftbquests`、`config/ftbquests/quests`、`lang` 或 `chapters` 目录,程序会自动定位 FTB Quests 任务目录。 - -## 配置 - -右上角“设置”里可以填写 DeepSeek API Key 和翻译参数。 - -API Key 优先保存到系统凭证管理器: - -- macOS:钥匙串 -- Windows:凭据管理器 -- Linux:Secret Service - -如果系统凭证后端不可用,程序会回退到本地受限文件,避免把 API Key 明文写入 `.env`。旧版本 `.env` 里的 `DEEPSEEK_API_KEY` 会在启动时尝试迁移到新存储。 - -设置面板还可以配置: - -- DeepSeek API 地址:`DEEPSEEK_BASE_URL` -- 模型名:`DEEPSEEK_MODEL` -- 翻译风格:`FTB_TRANSLATER_STYLE` -- 批大小:`FTB_TRANSLATER_BATCH_SIZE`,填 `auto` 使用自动策略 -- 并发数:`FTB_TRANSLATER_CONCURRENCY`,填 `auto` 使用自动策略 - -手动指定并发数也可以使用环境变量: +启动开发版: -```powershell -$env:FTB_TRANSLATER_CONCURRENCY=6 +```bash +npm run tauri dev ``` -恢复自动调节: +## 测试与构建 -```powershell -$env:FTB_TRANSLATER_CONCURRENCY=auto +```bash +npm run build +cargo test --manifest-path src-tauri/Cargo.toml +npm run tauri -- build ``` -## 翻译流程 +构建产物位于 `src-tauri/target/release/bundle/`。 -1. 选择整合包或 FTB Quests 目录。 -2. 点击扫描,确认程序识别到 `lang` 或 `chapters` 模式。 -3. 点击开始汉化。 -4. 程序会先弹窗确认覆盖写入,然后创建备份。 -5. 翻译完成后可以查看日志、报告和历史记录。 +## 数据位置 -程序会自动切分翻译请求,并根据任务规模选择保守的并发数。翻译时日志区域会显示 DeepSeek 调用、批次进度、备份创建和覆盖写入目标。 - -如果 DeepSeek 返回的译文丢失受保护格式,该条译文会被丢弃并保留原文。当前保护内容包括: - -- FTB / Minecraft 格式码,例如 `&e`、`&r`、`§a` -- 占位符,例如 `%s`、`%1$s` -- 物品或标签 token,例如 ``、`#forge:ingots/iron` -- 字面转义序列,例如 `\n`、`\t` -- 实际换行和制表符数量 - -## 输出 - -翻译会写入或更新: - -- `config/ftbquests/quests/lang/zh_cn.snbt`,或 `config/ftbquests/quests/chapters/*.snbt` -- `config/ftbquests/quests/.ftb-translater/cache.json` -- `config/ftbquests/quests/.ftb-translater/report-latest.json` -- `config/ftbquests/quests/.ftb-translater/backups/YYYYMMDD-HHMMSS/` -- 当前运行目录下的 `history.sqlite3` - -`report-latest.json` 包含本次翻译摘要、失败项、格式告警和中英映射。完整输出文件内容保存在历史数据库中,用于后续导出。 - -## 翻译历史 - -右上角“历史”入口会列出已保存的翻译记录。每条记录包含整合包路径、模式、模型、条目数、失败数、告警数和缓存命中数。 - -历史数据库保存在当前运行目录的 `history.sqlite3`。从源码目录执行 `python main.py` 时,数据库会出现在项目根目录;从其他目录启动时,数据库会出现在对应的当前工作目录。 - -历史页面支持导出 ZIP: - -- `lang` 模式导出 `lang/zh_cn.snbt` -- `chapters` 模式导出 `chapters/*.snbt` -- ZIP 内附带 `manifest.json` - -## 测试 - -普通测试组: - -```powershell -python -m tests.run_groups unit -``` - -或直接运行完整普通测试发现: - -```powershell -uv run python -m unittest discover -s tests -p "test_*.py" -``` - -完整流程测试组: - -```powershell -python -m tests.run_groups e2e -``` - -完整流程测试会启用真实 live 测试开关。它会下载真实 CurseForge 整合包 zip,先用假翻译器跑一遍真实下载处理流程,再抽样调用真实 DeepSeek API 跑一遍付费端到端流程。 - -真实 DeepSeek 测试的输出目录: +应用设置和 `history.sqlite3` 保存到系统应用数据目录。每个任务书自己的运行数据保存在: ```text -.ftb-translater/e2e-runs/YYYYMMDD-HHMMSS/ -``` - -其中 `summary.txt` 会列出 `zh_cn.snbt`、`report-latest.json`、`cache.json` 和备份目录的完整路径。 - -指定测试用整合包: - -```powershell -$env:FTB_TRANSLATER_CURSEFORGE_URL="https://edge.forgecdn.net/files/1234/567/your-pack.zip" -$env:FTB_TRANSLATER_LIVE_MAX_MB=500 -``` - -调整真实 DeepSeek 测试抽样条目数: - -```powershell -$env:FTB_TRANSLATER_LIVE_DEEPSEEK_ENTRIES=20 -``` - -安装为可执行脚本后也可以使用: - -```powershell -ftb-test -ftb-test-e2e -``` - -## CI/CD - -GitHub Actions 工作流在 `.github/workflows/build.yml`: - -- PR:在 Ubuntu、Windows、macOS 上运行本地单元测试。 -- 推送到 `main` / `master`:先运行测试,再构建 Windows exe 和 macOS dmg,并上传为 workflow artifacts。 -- 推送 `v*` tag:构建成功后自动把安装包发布到 GitHub Release。 -- 手动触发:可以勾选 `run_live_e2e` 运行真实 CurseForge 下载和 DeepSeek 端到端测试。 - -发布版本示例: - -```powershell -git tag v0.1.1 -git push origin v0.1.1 +config/ftbquests/quests/.ftb-translater/ +├── cache.json +├── report-latest.json +└── backups/YYYYMMDD-HHMMSS/ ``` -手动 live e2e 需要在仓库 Secrets 中配置: +## 翻译安全 -- `DEEPSEEK_API_KEY`:真实 DeepSeek API Key。未配置时 DeepSeek 付费测试会跳过。 -- `FTB_TRANSLATER_CURSEFORGE_URL`:可选,直接指向 CurseForge / ForgeCDN `.zip` 文件;未配置时使用测试默认整合包。 - -本地 CI 同款检查: - -```powershell -uv sync --dev --locked -uv run python -m tests.run_groups unit -``` +程序会先将 Minecraft 格式码和资源标识替换成不可翻译占位符,再提交模型。译文返回后恢复原始 token,并比较控制字符与 token 集合。发现缺失、增加或变化时,该条译文不会写入,源文本会被保留并进入人工检查列表。 diff --git a/ftb_translater/__init__.py b/ftb_translater/__init__.py deleted file mode 100644 index 6bb53b6..0000000 --- a/ftb_translater/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""FTB Translater desktop tool.""" - -__version__ = "0.1.1" diff --git a/ftb_translater/app.py b/ftb_translater/app.py deleted file mode 100644 index 7968714..0000000 --- a/ftb_translater/app.py +++ /dev/null @@ -1,1539 +0,0 @@ -from __future__ import annotations - -import queue -import threading -from collections import OrderedDict -from pathlib import Path -from tkinter import filedialog, messagebox -from typing import Literal, TypeAlias, TypedDict, cast - -import customtkinter as ctk - -from ftb_translater import credential_store -from ftb_translater.config import ( - BASE_URL_KEY, - BATCH_SIZE_KEY, - CONCURRENCY_KEY, - MODEL_KEY, - STYLE_KEY, - load_config_values, - migrate_api_key_from_env, - save_config_values, -) -from ftb_translater.deepseek_client import DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_STYLE, DeepSeekTranslator -from ftb_translater.format_guard import protect_text, repair_translation_format, restore_text, preserved_token_warnings -from ftb_translater.history_db import FileRecord, HistoryDB, RunSummary -from ftb_translater.report import TranslationReport -from ftb_translater.chapters import count_chapter_segments, replace_chapter_segments -from ftb_translater.logger import get_logger, setup_logging -from ftb_translater.paths import detect_source_mode, resolve_quests_dir, source_lang_path -from ftb_translater.snbt import load_lang_snbt, write_lang_snbt -from ftb_translater.translator import AUTO_BATCH_MAX_ENTRIES, AUTO_MAX_WORKERS, estimate_batches, translate_quests_auto - -_log = get_logger(__name__) - - -class _RunSettings(TypedDict): - api_key: str - batch_size: int | None - model: str - style: str - base_url: str - max_workers: int | None - - -class _ReviewEntryData(TypedDict): - source: str - failed: str - textbox: ctk.CTkTextbox - status_label: ctk.CTkLabel - retrans_btn: ctk.CTkButton - frame: ctk.CTkFrame - - -_ProgressPayload: TypeAlias = tuple[str, int, int] -_AppQueueItem: TypeAlias = ( - tuple[Literal["progress"], _ProgressPayload] - | tuple[Literal["log"], str] - | tuple[Literal["done"], TranslationReport] - | tuple[Literal["error"], Exception] - | tuple[Literal["history_saved"], int] - | tuple[Literal["history_save_failed"], str] -) - - -class FtbTranslaterApp(ctk.CTk): - def __init__(self): - super().__init__() - setup_logging() - _log.info("FTB Translater starting up") - self.title("FTB Translater") - self.geometry("1120x780") - self.minsize(980, 700) - - migrate_api_key_from_env() - config_values = load_config_values() - self.selected_dir = ctk.StringVar() - self.api_key = ctk.StringVar(value=credential_store.load_api_key()) - self.base_url = ctk.StringVar(value=config_values.get(BASE_URL_KEY) or DEFAULT_BASE_URL) - self.model = ctk.StringVar(value=config_values.get(MODEL_KEY) or DEFAULT_MODEL) - self.style = ctk.StringVar(value=config_values.get(STYLE_KEY) or DEFAULT_STYLE) - self.batch_size = ctk.StringVar(value=config_values.get(BATCH_SIZE_KEY) or "auto") - self.max_workers = ctk.StringVar(value=config_values.get(CONCURRENCY_KEY) or "auto") - self.status = ctk.StringVar(value="请选择整合包目录,或它下面的 config/ftbquests/quests/lang/chapters 任一目录。") - self.summary = ctk.StringVar(value="未扫描") - self.stage = ctk.StringVar(value="准备就绪") - self.progress_text = ctk.StringVar(value="等待扫描") - self.settings_status = ctk.StringVar( - value=f"API Key 通过 {credential_store.storage_backend_label()} 保存。" - ) - self._quests_dir: Path | None = None - self._queue: queue.Queue[_AppQueueItem] = queue.Queue() - self._key_visible = False - self._step_labels: dict[str, ctk.CTkLabel] = {} - self._nav_buttons: dict[str, ctk.CTkButton] = {} - self._run_settings: _RunSettings | None = None - self._review_report: TranslationReport | None = None - self._review_data: dict[str, _ReviewEntryData] = {} - self.history_db = HistoryDB() - self._history_cards: dict[int, ctk.CTkFrame] = {} - self._build_ui() - self._set_stage("idle") - self.after(150, self._drain_queue) - - def _build_ui(self) -> None: - ctk.set_appearance_mode("System") - ctk.set_default_color_theme("blue") - - self.configure(fg_color=("#F8FAFB", "#0D1117")) - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(1, weight=1) - - topbar = ctk.CTkFrame( - self, - height=56, - corner_radius=0, - fg_color=("#FFFFFF", "#161B22"), - border_width=0, - ) - topbar.grid(row=0, column=0, sticky="ew") - topbar.grid_columnconfigure(1, weight=1) - topbar.grid_rowconfigure(0, weight=1) - topbar.grid_propagate(False) - - brand = ctk.CTkFrame(topbar, fg_color="transparent") - brand.grid(row=0, column=0, sticky="w", padx=24) - ctk.CTkLabel( - brand, - text="FTB Translater", - anchor="w", - font=ctk.CTkFont(size=18, weight="bold"), - text_color=("#1F2328", "#E6EDF3"), - ).grid(row=0, column=0, sticky="w") - ctk.CTkLabel( - brand, - text="任务书汉化", - anchor="w", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=12), - ).grid(row=0, column=1, sticky="w", padx=(10, 0)) - - right_bar = ctk.CTkFrame(topbar, fg_color="transparent") - right_bar.grid(row=0, column=2, sticky="e", padx=24) - self._nav_buttons["history"] = ctk.CTkButton( - right_bar, - text="📚 历史", - width=72, - height=32, - corner_radius=8, - command=lambda: self._show_view("history"), - font=ctk.CTkFont(size=13), - fg_color=("#F3F4F6", "#21262D"), - hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ) - self._nav_buttons["history"].grid(row=0, column=0, padx=(0, 8)) - self._nav_buttons["settings"] = ctk.CTkButton( - right_bar, - text="⚙ 设置", - width=72, - height=32, - corner_radius=8, - command=lambda: self._show_view("settings"), - font=ctk.CTkFont(size=13), - fg_color=("#F3F4F6", "#21262D"), - hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ) - self._nav_buttons["settings"].grid(row=0, column=1, padx=(0, 10)) - self.appearance_segment = ctk.CTkSegmentedButton( - right_bar, - values=["系统", "浅色", "深色"], - command=self._change_appearance, - selected_color="#2563EB", - selected_hover_color="#1D4ED8", - height=30, - font=ctk.CTkFont(size=12), - ) - self.appearance_segment.grid(row=0, column=2) - self.appearance_segment.set("系统") - - main = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") - main.grid(row=1, column=0, sticky="nsew", padx=32, pady=(20, 24)) - main.grid_columnconfigure(0, weight=1) - main.grid_rowconfigure(4, weight=1) - self.workbench_frame = main - - header = ctk.CTkFrame(main, fg_color="transparent") - header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) - header.grid_columnconfigure(0, weight=1) - - title_row = ctk.CTkFrame(header, fg_color="transparent") - title_row.grid(row=0, column=0, sticky="ew") - title_row.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - title_row, - text="工作台", - anchor="w", - font=ctk.CTkFont(size=24, weight="bold"), - text_color=("#1F2328", "#E6EDF3"), - ).grid(row=0, column=0, sticky="w") - self.stage_badge = ctk.CTkLabel( - title_row, - textvariable=self.stage, - height=28, - corner_radius=14, - padx=14, - font=ctk.CTkFont(size=12, weight="bold"), - ) - self.stage_badge.grid(row=0, column=1, sticky="e") - - ctk.CTkLabel( - header, - textvariable=self.status, - anchor="w", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=13), - ).grid(row=1, column=0, sticky="ew", pady=(6, 0)) - - stepper = ctk.CTkFrame(header, fg_color="transparent", height=36) - stepper.grid(row=2, column=0, sticky="ew", pady=(12, 0)) - for col in range(4): - stepper.grid_columnconfigure(col, weight=0) - stepper.grid_columnconfigure(4, weight=1) - for col, (key, text) in enumerate( - [ - ("idle", "① 选择"), - ("scanned", "② 扫描"), - ("running", "③ 翻译"), - ("done", "④ 完成"), - ] - ): - self._step_labels[key] = ctk.CTkLabel( - stepper, - text=text, - height=28, - corner_radius=6, - padx=10, - font=ctk.CTkFont(size=12), - text_color=("#656D76", "#8B949E"), - fg_color=("#F3F4F6", "#1C2128"), - ) - self._step_labels[key].grid(row=0, column=col, padx=(0, 6)) - - source_panel = self._panel(main, "选择整合包", "选择整合包根目录,或直接选择 quests / lang / chapters 目录") - source_panel.grid(row=1, column=0, sticky="ew", pady=(0, 10)) - source_panel.grid_columnconfigure(0, weight=1) - ctk.CTkEntry( - source_panel, - textvariable=self.selected_dir, - height=36, - corner_radius=8, - placeholder_text="选择整合包目录...", - border_width=1, - border_color=("#D0D7DE", "#30363D"), - ).grid(row=2, column=0, sticky="ew", padx=16, pady=(0, 14)) - ctk.CTkButton( - source_panel, text="选择目录", width=90, height=36, - corner_radius=8, command=self._choose_dir, - fg_color=("#2563EB", "#2563EB"), hover_color=("#1D4ED8", "#1D4ED8"), - ).grid(row=2, column=1, padx=(0, 8), pady=(0, 14)) - self.scan_button = ctk.CTkButton( - source_panel, text="扫描", width=64, height=36, - corner_radius=8, command=self._scan, - fg_color=("#F3F4F6", "#21262D"), hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ) - self.scan_button.grid(row=2, column=2, padx=(0, 16), pady=(0, 14)) - - summary_panel = self._panel(main, "扫描结果", "扫描后显示目录、模式、条目数和预计批次") - summary_panel.grid(row=2, column=0, sticky="ew", pady=(0, 10)) - summary_panel.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - summary_panel, - textvariable=self.summary, - anchor="w", - justify="left", - wraplength=700, - font=ctk.CTkFont(size=13), - text_color=("#374151", "#C9D1D9"), - ).grid(row=2, column=0, sticky="ew", padx=16, pady=(0, 14)) - - run_panel = ctk.CTkFrame( - main, - corner_radius=12, - border_width=1, - fg_color=("#FFFFFF", "#161B22"), - border_color=("#D0D7DE", "#30363D"), - ) - run_panel.grid(row=3, column=0, sticky="ew", pady=(0, 10)) - run_panel.grid_columnconfigure(0, weight=1) - self.progress = ctk.CTkProgressBar( - run_panel, height=8, corner_radius=4, - progress_color="#2563EB", - ) - self.progress.grid(row=0, column=0, sticky="ew", padx=16, pady=(16, 6)) - self.progress.set(0) - ctk.CTkLabel( - run_panel, textvariable=self.progress_text, anchor="w", - text_color=("#656D76", "#8B949E"), font=ctk.CTkFont(size=12), - ).grid(row=1, column=0, sticky="ew", padx=16, pady=(0, 16)) - self.translate_button = ctk.CTkButton( - run_panel, - text="开始汉化", - width=110, - height=36, - corner_radius=8, - command=self._start_translate, - state="disabled", - font=ctk.CTkFont(size=14, weight="bold"), - fg_color=("#16A34A", "#238636"), - hover_color=("#15803D", "#2EA043"), - ) - self.translate_button.grid(row=0, column=1, rowspan=2, sticky="e", padx=16, pady=16) - - bottom = ctk.CTkFrame(main, corner_radius=0, fg_color="transparent") - bottom.grid(row=4, column=0, sticky="nsew") - bottom.grid_columnconfigure(0, weight=1) - bottom.grid_rowconfigure(0, weight=1) - - log_panel = self._panel(bottom, "日志", "扫描、备份、翻译和写入的实时输出") - log_panel.grid(row=0, column=0, sticky="nsew") - log_panel.grid_columnconfigure(0, weight=1) - log_panel.grid_rowconfigure(2, weight=1) - self.log = ctk.CTkTextbox( - log_panel, height=140, corner_radius=8, - font=ctk.CTkFont(size=12, family="Menlo"), - fg_color=("#F6F8FA", "#0D1117"), - text_color=("#1F2328", "#C9D1D9"), - ) - self.log.grid(row=2, column=0, sticky="nsew", padx=16, pady=(0, 14)) - - review_panel = ctk.CTkFrame( - bottom, corner_radius=12, border_width=1, - fg_color=("#FFFBEB", "#1C1917"), - border_color=("#F59E0B", "#92400E"), - ) - review_panel.grid(row=1, column=0, sticky="nsew", pady=(10, 0)) - review_panel.grid_columnconfigure(0, weight=1) - review_panel.grid_columnconfigure(1, weight=0) - review_panel.grid_rowconfigure(3, weight=1) - review_panel.grid_remove() - self.review_panel = review_panel - - self._review_title = ctk.StringVar(value="人工处理") - ctk.CTkLabel( - review_panel, textvariable=self._review_title, anchor="w", - font=ctk.CTkFont(size=15, weight="bold"), - text_color=("#92400E", "#FCD34D"), - ).grid(row=0, column=0, sticky="ew", padx=16, pady=(14, 2)) - - self._review_badge = ctk.CTkLabel( - review_panel, text="", height=24, corner_radius=12, padx=10, - fg_color=("#F59E0B", "#B45309"), text_color="#FFFFFF", - font=ctk.CTkFont(size=11, weight="bold"), - ) - self._review_badge.grid(row=0, column=1, sticky="e", padx=(0, 16), pady=(14, 2)) - - self._review_subtitle = ctk.CTkLabel( - review_panel, text="", anchor="w", - text_color=("#78716C", "#A8A29E"), - wraplength=760, font=ctk.CTkFont(size=12), - ) - self._review_subtitle.grid(row=1, column=0, columnspan=2, sticky="ew", padx=16, pady=(0, 6)) - - action_bar = ctk.CTkFrame(review_panel, fg_color="transparent") - action_bar.grid(row=2, column=0, columnspan=2, sticky="ew", padx=16, pady=(0, 6)) - action_bar.grid_columnconfigure(0, weight=1) - self._retranslate_all_btn = ctk.CTkButton( - action_bar, text="全部重新翻译", width=120, height=30, - corner_radius=8, command=self._retranslate_all_review, - fg_color=("#F59E0B", "#B45309"), hover_color=("#D97706", "#92400E"), - ) - self._retranslate_all_btn.grid(row=0, column=1, padx=(0, 6), sticky="e") - self._ignore_all_btn = ctk.CTkButton( - action_bar, text="全部忽略", width=90, height=30, - corner_radius=8, - fg_color=("#6B7280", "#4B5563"), hover_color=("#4B5563", "#374151"), - command=self._ignore_all_review, - ) - self._ignore_all_btn.grid(row=0, column=2, sticky="e") - - self._review_scroll = ctk.CTkScrollableFrame( - review_panel, corner_radius=8, border_width=1, - fg_color=("#FFFFFF", "#0D1117"), - border_color=("#E5E7EB", "#30363D"), - ) - self._review_scroll.grid(row=3, column=0, columnspan=2, sticky="nsew", padx=16, pady=(0, 14)) - self._review_scroll.grid_columnconfigure(0, weight=1) - - self._build_settings_view() - self._build_history_view() - self._show_view("workbench") - - def _build_settings_view(self) -> None: - settings = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") - settings.grid(row=1, column=0, sticky="nsew", padx=32, pady=(20, 24)) - settings.grid_columnconfigure(0, weight=1) - settings.grid_rowconfigure(1, weight=1) - self.settings_frame = settings - - header = ctk.CTkFrame(settings, fg_color="transparent") - header.grid(row=0, column=0, sticky="ew", pady=(0, 16)) - header.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - header, - text="设置", - anchor="w", - font=ctk.CTkFont(size=24, weight="bold"), - text_color=("#1F2328", "#E6EDF3"), - ).grid(row=0, column=0, sticky="ew") - ctk.CTkButton( - header, text="← 返回工作台", width=110, height=32, - corner_radius=8, command=lambda: self._show_view("workbench"), - fg_color=("#F3F4F6", "#21262D"), hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ).grid(row=0, column=1, sticky="e") - ctk.CTkLabel( - header, - text="修改后会影响之后的扫描和汉化任务", - anchor="w", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=13), - ).grid(row=1, column=0, columnspan=2, sticky="ew", pady=(6, 0)) - - content = ctk.CTkScrollableFrame(settings, corner_radius=0, fg_color="transparent") - content.grid(row=1, column=0, sticky="nsew") - content.grid_columnconfigure(0, weight=1) - - api_panel = self._panel( - content, - "API Key", - f"调用翻译接口的凭证,会保存到{credential_store.storage_backend_label()}", - ) - api_panel.grid(row=0, column=0, sticky="ew", pady=(0, 10)) - api_panel.grid_columnconfigure(0, weight=1) - self.key_entry = ctk.CTkEntry( - api_panel, textvariable=self.api_key, show="*", height=36, - corner_radius=8, border_width=1, border_color=("#D0D7DE", "#30363D"), - ) - self.key_entry.grid(row=2, column=0, sticky="ew", padx=16, pady=(0, 10)) - self.toggle_key_button = ctk.CTkButton( - api_panel, text="显示", width=60, height=36, corner_radius=8, - command=self._toggle_key_visibility, - fg_color=("#F3F4F6", "#21262D"), hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ) - self.toggle_key_button.grid(row=2, column=1, padx=(0, 8), pady=(0, 10)) - ctk.CTkButton( - api_panel, text="保存全部", width=80, height=36, corner_radius=8, - command=self._save_settings, - fg_color=("#2563EB", "#2563EB"), hover_color=("#1D4ED8", "#1D4ED8"), - ).grid(row=2, column=2, padx=(0, 16), pady=(0, 10)) - ctk.CTkLabel( - api_panel, - textvariable=self.settings_status, - anchor="w", - justify="left", - wraplength=660, - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=12), - ).grid(row=3, column=0, columnspan=3, sticky="ew", padx=16, pady=(0, 14)) - - service_panel = self._panel(content, "翻译服务", "默认使用 DeepSeek 兼容 OpenAI 接口,可替换为其他兼容服务") - service_panel.grid(row=1, column=0, sticky="ew", pady=(0, 10)) - service_panel.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - service_panel, text="API 地址", anchor="w", - font=ctk.CTkFont(size=12), text_color=("#374151", "#C9D1D9"), - ).grid(row=2, column=0, sticky="ew", padx=16, pady=(0, 4)) - ctk.CTkEntry( - service_panel, textvariable=self.base_url, height=36, - corner_radius=8, border_width=1, border_color=("#D0D7DE", "#30363D"), - ).grid(row=3, column=0, columnspan=2, sticky="ew", padx=16, pady=(0, 12)) - ctk.CTkLabel( - service_panel, text="模型", anchor="w", - font=ctk.CTkFont(size=12), text_color=("#374151", "#C9D1D9"), - ).grid(row=4, column=0, sticky="ew", padx=16, pady=(0, 4)) - ctk.CTkEntry( - service_panel, textvariable=self.model, height=36, - corner_radius=8, border_width=1, border_color=("#D0D7DE", "#30363D"), - ).grid(row=5, column=0, sticky="ew", padx=16, pady=(0, 14)) - ctk.CTkButton( - service_panel, text="恢复默认", width=80, height=32, corner_radius=8, - command=self._reset_service_settings, - fg_color=("#F3F4F6", "#21262D"), hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ).grid(row=5, column=1, sticky="e", padx=(0, 16), pady=(0, 14)) - - translate_panel = self._panel(content, "翻译参数", "批大小和并发可填 auto 使用自动策略,或填正整数手动控制") - translate_panel.grid(row=2, column=0, sticky="ew", pady=(0, 10)) - translate_panel.grid_columnconfigure(0, weight=1) - translate_panel.grid_columnconfigure(1, weight=1) - ctk.CTkLabel( - translate_panel, text="翻译风格", anchor="w", - font=ctk.CTkFont(size=12), text_color=("#374151", "#C9D1D9"), - ).grid(row=2, column=0, columnspan=2, sticky="ew", padx=16, pady=(0, 4)) - self.style_text = ctk.CTkTextbox( - translate_panel, height=68, corner_radius=8, - font=ctk.CTkFont(size=13), - fg_color=("#F6F8FA", "#0D1117"), - border_width=1, border_color=("#D0D7DE", "#30363D"), - ) - self.style_text.grid(row=3, column=0, columnspan=2, sticky="ew", padx=16, pady=(0, 12)) - self.style_text.insert("1.0", self.style.get()) - ctk.CTkLabel( - translate_panel, text="批大小", anchor="w", - font=ctk.CTkFont(size=12), text_color=("#374151", "#C9D1D9"), - ).grid(row=4, column=0, sticky="ew", padx=16, pady=(0, 4)) - ctk.CTkLabel( - translate_panel, text="并发数", anchor="w", - font=ctk.CTkFont(size=12), text_color=("#374151", "#C9D1D9"), - ).grid(row=4, column=1, sticky="ew", padx=(0, 16), pady=(0, 4)) - ctk.CTkEntry( - translate_panel, textvariable=self.batch_size, height=36, - corner_radius=8, placeholder_text="auto", - border_width=1, border_color=("#D0D7DE", "#30363D"), - ).grid(row=5, column=0, sticky="ew", padx=16, pady=(0, 14)) - ctk.CTkEntry( - translate_panel, textvariable=self.max_workers, height=36, - corner_radius=8, placeholder_text="auto", - border_width=1, border_color=("#D0D7DE", "#30363D"), - ).grid(row=5, column=1, sticky="ew", padx=(0, 16), pady=(0, 14)) - ctk.CTkLabel( - translate_panel, - text=f"auto: 最多 {AUTO_BATCH_MAX_ENTRIES} 条/批,{AUTO_MAX_WORKERS} 并发", - anchor="w", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=11), - ).grid(row=6, column=0, columnspan=2, sticky="ew", padx=16, pady=(0, 14)) - - def _build_history_view(self) -> None: - history = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") - history.grid(row=1, column=0, sticky="nsew", padx=32, pady=(20, 24)) - history.grid_columnconfigure(0, weight=1) - history.grid_rowconfigure(1, weight=1) - self.history_frame = history - - header = ctk.CTkFrame(history, fg_color="transparent") - header.grid(row=0, column=0, sticky="ew", pady=(0, 16)) - header.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - header, text="翻译历史", anchor="w", - font=ctk.CTkFont(size=24, weight="bold"), - text_color=("#1F2328", "#E6EDF3"), - ).grid(row=0, column=0, sticky="ew") - - button_row = ctk.CTkFrame(header, fg_color="transparent") - button_row.grid(row=0, column=1, sticky="e") - ctk.CTkButton( - button_row, text="🔄 刷新", width=72, height=32, corner_radius=8, - command=self._refresh_history_list, - fg_color=("#F3F4F6", "#21262D"), hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ).grid(row=0, column=0, padx=(0, 8)) - ctk.CTkButton( - button_row, text="← 返回工作台", width=110, height=32, corner_radius=8, - command=lambda: self._show_view("workbench"), - fg_color=("#F3F4F6", "#21262D"), hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ).grid(row=0, column=1) - - ctk.CTkLabel( - header, - text="保存的汉化记录,可以导出 ZIP 分享给其他玩家,或者删除清理空间", - anchor="w", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=13), - ).grid(row=1, column=0, columnspan=2, sticky="ew", pady=(6, 0)) - - self._history_list_frame = ctk.CTkScrollableFrame( - history, corner_radius=0, fg_color="transparent", - ) - self._history_list_frame.grid(row=1, column=0, sticky="nsew") - self._history_list_frame.grid_columnconfigure(0, weight=1) - - self._history_empty_label = ctk.CTkLabel( - self._history_list_frame, - text="暂无翻译历史。完成一次汉化后会自动出现在这里。", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=14), - ) - - def _refresh_history_list(self) -> None: - for widget in self._history_list_frame.winfo_children(): - widget.destroy() - self._history_cards.clear() - runs = self.history_db.list_runs() - if not runs: - self._history_empty_label = ctk.CTkLabel( - self._history_list_frame, - text="暂无翻译历史。完成一次汉化后会自动出现在这里。", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=14), - ) - self._history_empty_label.grid(row=0, column=0, pady=60) - return - for idx, summary in enumerate(runs): - self._build_history_card(idx, summary) - - def _build_history_card(self, idx: int, summary: RunSummary) -> None: - card = ctk.CTkFrame( - self._history_list_frame, corner_radius=12, border_width=1, - fg_color=("#FFFFFF", "#161B22"), - border_color=("#D0D7DE", "#30363D"), - ) - card.grid(row=idx, column=0, sticky="ew", pady=(0, 10)) - card.grid_columnconfigure(0, weight=1) - self._history_cards[summary.id] = card - - top_row = ctk.CTkFrame(card, fg_color="transparent") - top_row.grid(row=0, column=0, sticky="ew", padx=16, pady=(14, 4)) - top_row.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - top_row, - text=f"{summary.pack_name or '未命名'} · #{summary.id}", - anchor="w", - font=ctk.CTkFont(size=15, weight="bold"), - text_color=("#1F2328", "#E6EDF3"), - ).grid(row=0, column=0, sticky="w") - ctk.CTkLabel( - top_row, text=summary.created_at, anchor="e", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=12), - ).grid(row=0, column=1, sticky="e") - - mode_text = "lang 模式" if summary.mode == "lang" else "chapters 模式" - meta_text = ( - f"{mode_text} · {summary.total_entries} 条 · {summary.model}" - ) - ctk.CTkLabel( - card, text=meta_text, anchor="w", - text_color=("#374151", "#C9D1D9"), - font=ctk.CTkFont(size=12), - ).grid(row=1, column=0, sticky="ew", padx=16, pady=(0, 2)) - - stats_text = ( - f"成功 {summary.translated_entries} · " - f"失败 {summary.failed_count} · " - f"告警 {summary.warning_count} · " - f"缓存命中 {summary.cache_hits}" - ) - ctk.CTkLabel( - card, text=stats_text, anchor="w", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=11), - ).grid(row=2, column=0, sticky="ew", padx=16, pady=(0, 2)) - - path_text = summary.quests_dir - if len(path_text) > 90: - path_text = "..." + path_text[-87:] - ctk.CTkLabel( - card, text=path_text, anchor="w", - text_color=("#94A3B8", "#6E7681"), - font=ctk.CTkFont(size=11, family="Menlo"), - ).grid(row=3, column=0, sticky="ew", padx=16, pady=(0, 8)) - - btn_row = ctk.CTkFrame(card, fg_color="transparent") - btn_row.grid(row=4, column=0, sticky="ew", padx=16, pady=(0, 12)) - btn_row.grid_columnconfigure(0, weight=1) - ctk.CTkButton( - btn_row, text="导出 ZIP", width=90, height=30, corner_radius=6, - fg_color=("#2563EB", "#2563EB"), hover_color=("#1D4ED8", "#1D4ED8"), - command=lambda sid=summary.id: self._export_history_zip(sid), - ).grid(row=0, column=1, padx=(0, 6), sticky="e") - ctk.CTkButton( - btn_row, text="查看", width=70, height=30, corner_radius=6, - fg_color=("#F3F4F6", "#21262D"), hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - command=lambda sid=summary.id: self._show_history_detail(sid), - ).grid(row=0, column=2, padx=(0, 6), sticky="e") - ctk.CTkButton( - btn_row, text="删除", width=70, height=30, corner_radius=6, - fg_color=("#DC2626", "#991B1B"), hover_color=("#B91C1C", "#7F1D1D"), - command=lambda sid=summary.id: self._delete_history_run(sid), - ).grid(row=0, column=3, sticky="e") - - def _export_history_zip(self, run_id: int) -> None: - summary = self.history_db.get_run(run_id) - if summary is None: - messagebox.showerror("导出失败", f"记录 #{run_id} 不存在。") - return - default_name = f"{summary.pack_name or 'ftb-translation'}-{summary.id}.zip" - dest = filedialog.asksaveasfilename( - title="导出翻译 ZIP", - defaultextension=".zip", - initialfile=default_name, - filetypes=[("ZIP 文件", "*.zip")], - ) - if not dest: - return - try: - written = self.history_db.export_zip(run_id, Path(dest)) - messagebox.showinfo("导出成功", f"已写入:\n{written}") - self._log(f"已导出历史 #{run_id} -> {written}") - except Exception as exc: # noqa: BLE001 - messagebox.showerror("导出失败", str(exc)) - _log.error("Export failed: %s", exc, exc_info=True) - - def _delete_history_run(self, run_id: int) -> None: - if not messagebox.askyesno("确认删除", f"确定要删除历史记录 #{run_id} 吗?此操作不可撤销。"): - return - try: - self.history_db.delete_run(run_id) - self._log(f"已删除历史 #{run_id}") - self._refresh_history_list() - except Exception as exc: # noqa: BLE001 - messagebox.showerror("删除失败", str(exc)) - - def _show_history_detail(self, run_id: int) -> None: - summary = self.history_db.get_run(run_id) - if summary is None: - messagebox.showerror("查看失败", f"记录 #{run_id} 不存在。") - return - files = self.history_db.get_files(run_id) - - window = ctk.CTkToplevel(self) - window.title(f"历史 #{run_id} - {summary.pack_name or '未命名'}") - window.geometry("760x560") - window.grab_set() - window.grid_columnconfigure(0, weight=1) - window.grid_rowconfigure(1, weight=1) - - meta_text = ( - f"模式: {summary.mode} 模型: {summary.model} 创建于: {summary.created_at}\n" - f"总条目: {summary.total_entries} 成功: {summary.translated_entries} " - f"失败: {summary.failed_count} 告警: {summary.warning_count}\n" - f"风格: {summary.style}\n" - f"路径: {summary.quests_dir}" - ) - ctk.CTkLabel( - window, text=meta_text, anchor="w", justify="left", - font=ctk.CTkFont(size=12), - text_color=("#374151", "#C9D1D9"), - ).grid(row=0, column=0, sticky="ew", padx=16, pady=(14, 8)) - - textbox = ctk.CTkTextbox( - window, corner_radius=8, - font=ctk.CTkFont(size=12, family="Menlo"), - fg_color=("#F6F8FA", "#0D1117"), - ) - textbox.grid(row=1, column=0, sticky="nsew", padx=16, pady=(0, 12)) - - for record in files: - textbox.insert("end", f"=== {record.filename} ({len(record.mapping)} 条) ===\n") - for idx, (key, pair) in enumerate(list(record.mapping.items())[:20]): - en = pair.get("en", "").replace("\n", " ⏎ ") - zh = pair.get("zh", "").replace("\n", " ⏎ ") - textbox.insert("end", f" [{key}]\n EN: {en[:120]}\n ZH: {zh[:120]}\n") - if len(record.mapping) > 20: - textbox.insert("end", f" ... 还有 {len(record.mapping) - 20} 条\n") - textbox.insert("end", "\n") - textbox.configure(state="disabled") - - ctk.CTkButton( - window, text="关闭", width=80, height=32, corner_radius=8, - command=window.destroy, - ).grid(row=2, column=0, pady=(0, 14)) - - def _show_review_entries(self) -> None: - for widget in self._review_scroll.winfo_children(): - widget.destroy() - self._review_data.clear() - - report = self._review_report - if report is None: - self._hide_review_panel() - return - - entries = self._review_entries(report) - if not entries: - self._hide_review_panel() - return - - self.review_panel.grid() - self.review_panel.master.grid_rowconfigure(1, weight=1) - self._review_title.set(f"人工处理 — {len(entries)} 条需要确认的映射") - self._review_badge.configure(text=f"待处理 {len(entries)}") - - self._review_subtitle.configure( - text="这些条目因为 API 失败或格式保护被保留为原文。可以逐条编辑后保存,也可以重新翻译或忽略。" - ) - - for idx, (key, warning_list) in enumerate(entries): - ft = report.failed_translations.get(key, {}) - source = ft.get("source", key) - failed = ft.get("failed", "") - - card = ctk.CTkFrame( - self._review_scroll, corner_radius=10, - fg_color=("#FFFFFF", "#161B22"), - border_width=1, border_color=("#D0D7DE", "#30363D"), - ) - card.grid(row=idx, column=0, sticky="ew", pady=(0, 8)) - card.grid_columnconfigure(0, weight=1) - card.grid_columnconfigure(1, weight=1) - - key_row = ctk.CTkFrame(card, fg_color="transparent") - key_row.grid(row=0, column=0, columnspan=2, sticky="ew", padx=12, pady=(10, 6)) - key_row.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - key_row, text=key, anchor="w", - font=ctk.CTkFont(size=11, family="Menlo"), - text_color=("#656D76", "#8B949E"), - ).grid(row=0, column=0, sticky="ew") - ctk.CTkLabel( - key_row, text="待确认", height=20, corner_radius=10, padx=8, - fg_color=("#FEF3C7", "#451A03"), text_color=("#92400E", "#FCD34D"), - font=ctk.CTkFont(size=10, weight="bold"), - ).grid(row=0, column=1, sticky="e") - - ctk.CTkLabel( - card, text="原文", anchor="w", - font=ctk.CTkFont(size=12, weight="bold"), - text_color=("#374151", "#C9D1D9"), - ).grid(row=1, column=0, sticky="w", padx=12, pady=(0, 2)) - ctk.CTkLabel( - card, text="翻译(可编辑)", anchor="w", - font=ctk.CTkFont(size=12, weight="bold"), - text_color=("#374151", "#C9D1D9"), - ).grid(row=1, column=1, sticky="w", padx=12, pady=(0, 2)) - - src_tb = ctk.CTkTextbox( - card, height=90, corner_radius=6, - fg_color=("#F6F8FA", "#0D1117"), - border_width=1, border_color=("#D0D7DE", "#30363D"), - font=ctk.CTkFont(size=12), - ) - src_tb.grid(row=2, column=0, sticky="nsew", padx=(12, 6), pady=(0, 4)) - src_tb.insert("1.0", source) - src_tb.configure(state="disabled") - - trans_tb = ctk.CTkTextbox( - card, height=90, corner_radius=6, - border_width=1, border_color=("#2563EB", "#2563EB"), - font=ctk.CTkFont(size=12), - ) - trans_tb.grid(row=2, column=1, sticky="nsew", padx=(6, 12), pady=(0, 4)) - trans_tb.insert("1.0", failed or source) - - warn_text = "\n".join(f"\u26a0 {w}" for w in warning_list) - warn_box = ctk.CTkFrame(card, corner_radius=6, fg_color=("#FEF2F2", "#2D1B1B")) - warn_box.grid(row=3, column=0, columnspan=2, sticky="ew", padx=12, pady=(4, 4)) - warn_box.grid_columnconfigure(0, weight=1) - ctk.CTkLabel( - warn_box, text=warn_text, anchor="w", wraplength=700, - text_color=("#DC2626", "#FCA5A5"), font=ctk.CTkFont(size=11), - justify="left", - ).grid(row=0, column=0, sticky="ew", padx=8, pady=6) - - status_lbl = ctk.CTkLabel( - card, text="", anchor="w", - text_color=("#656D76", "#8B949E"), font=ctk.CTkFont(size=11), - ) - status_lbl.grid(row=4, column=0, columnspan=2, sticky="ew", padx=12, pady=(2, 4)) - - btn_frame = ctk.CTkFrame(card, fg_color="transparent") - btn_frame.grid(row=5, column=0, columnspan=2, sticky="ew", padx=12, pady=(0, 8)) - btn_frame.grid_columnconfigure(0, weight=1) - - ctk.CTkButton( - btn_frame, text="保存", width=60, height=28, corner_radius=6, - fg_color=("#2563EB", "#2563EB"), hover_color=("#1D4ED8", "#1D4ED8"), - command=lambda k=key, tb=trans_tb, sl=status_lbl: - self._save_review_entry(k, tb, sl), - ).grid(row=0, column=1, padx=(0, 6), sticky="e") - - retrans_btn = ctk.CTkButton( - btn_frame, text="重新翻译", width=80, height=28, corner_radius=6, - fg_color=("#F59E0B", "#B45309"), hover_color=("#D97706", "#92400E"), - ) - retrans_btn.configure( - command=lambda rb=retrans_btn, k=key, s=source, tb=trans_tb, sl=status_lbl: - self._retranslate_single(k, s, tb, sl, rb), - ) - retrans_btn.grid(row=0, column=2, padx=(0, 6), sticky="e") - - ctk.CTkButton( - btn_frame, text="忽略", width=60, height=28, corner_radius=6, - fg_color=("#6B7280", "#4B5563"), hover_color=("#4B5563", "#374151"), - command=lambda k=key, c=card: self._ignore_review_entry(k, c), - ).grid(row=0, column=3, sticky="e") - - self._review_data[key] = { - "source": source, - "failed": failed, - "textbox": trans_tb, - "status_label": status_lbl, - "retrans_btn": retrans_btn, - "frame": card, - } - - def _review_entries(self, report: TranslationReport) -> list[tuple[str, list[str]]]: - entries: OrderedDict[str, list[str]] = OrderedDict() - for key, warning_list in report.warnings.items(): - entries[key] = list(warning_list) - for failed_entry in report.failed_entries: - key, _, error = failed_entry.partition(":") - key = key.strip() - if not key: - continue - if key in entries: - continue - message = error.strip() or failed_entry - entries.setdefault(key, []).append(f"API 调用失败:{message}") - return list(entries.items()) - - def _hide_review_panel(self) -> None: - for widget in self._review_scroll.winfo_children(): - widget.destroy() - self._review_data.clear() - self.review_panel.grid_remove() - self.review_panel.master.grid_rowconfigure(1, weight=0) - - def _save_review_entry(self, key: str, textbox: ctk.CTkTextbox, status_label: ctk.CTkLabel) -> None: - report = self._review_report - if report is None: - status_label.configure(text="错误:无翻译报告。") - return - new_text = textbox.get("1.0", "end").strip() - if not new_text: - status_label.configure(text="错误:翻译内容为空。") - return - try: - target = Path(report.target_file) - if not target.exists(): - status_label.configure(text=f"错误:目标文件不存在 {target}") - return - if target.suffix == ".snbt": - values = load_lang_snbt(target) - values[key] = new_text - write_lang_snbt(target, values) - elif target.is_dir(): - self._save_chapter_review_entry(target, key, new_text) - else: - status_label.configure(text=f"错误:无法识别目标文件 {target}") - return - status_label.configure(text="已保存 \u2713") - except Exception as exc: - status_label.configure(text=f"保存失败:{exc}") - - def _save_chapter_review_entry(self, chapters_dir: Path, key: str, new_text: str) -> None: - parts = key.split(":", 2) - if len(parts) != 3: - raise ValueError(f"章节映射 key 格式无效:{key}") - filename, index_text, _segment_key = parts - try: - segment_index = int(index_text) - except ValueError as exc: - raise ValueError(f"章节映射序号无效:{key}") from exc - chapter_path = chapters_dir / filename - if not chapter_path.exists(): - raise FileNotFoundError(f"章节文件不存在:{chapter_path}") - replaced = replace_chapter_segments(chapter_path, {segment_index: new_text}) - if replaced != 1: - raise ValueError(f"未能定位章节文本段:{key}") - - def _retranslate_single( - self, key: str, source: str, - textbox: ctk.CTkTextbox, status_label: ctk.CTkLabel, - button: ctk.CTkButton | None, - ) -> None: - api_key = self.api_key.get().strip() - if not api_key: - status_label.configure(text="错误:请先配置 API Key。") - return - if button: - button.configure(state="disabled", text="翻译中...") - - def worker(): - try: - selected_api_key, model, style, base_url = self._current_translator_settings(api_key) - translator = DeepSeekTranslator( - api_key=selected_api_key, - model=model, - base_url=base_url, - ) - protected_source, protections = protect_text(source) - batch = OrderedDict([(key, protected_source)]) - result = translator.translate_batch(batch, style=style) - translated = result.get(key, protected_source) - restored = restore_text(translated, protections) - restored = repair_translation_format(source, restored) - warnings = preserved_token_warnings(source, restored) - if warnings: - self.after(0, lambda t=restored: self._on_retranslate_result( - textbox, status_label, button, t, - f"仍有 {len(warnings)} 个告警,可手动编辑后保存。", - )) - else: - self.after(0, lambda: self._on_retranslate_result( - textbox, status_label, button, restored, "翻译成功 \u2713", - )) - except Exception as exc: - self.after(0, lambda e=exc: self._on_retranslate_result( - textbox, status_label, button, "", - f"翻译失败:{e}", - )) - - thread = threading.Thread(target=worker, daemon=True) - thread.start() - - def _on_retranslate_result( - self, - textbox: ctk.CTkTextbox, - status_label: ctk.CTkLabel, - button: ctk.CTkButton | None, - text: str, - status: str, - ) -> None: - if text: - textbox.delete("1.0", "end") - textbox.insert("1.0", text) - status_label.configure(text=status) - if button: - button.configure(state="normal", text="重新翻译") - - def _retranslate_all_review(self) -> None: - if not self._review_data: - return - api_key = self.api_key.get().strip() - if not api_key: - self._review_subtitle.configure(text="错误:请先配置 API Key。") - return - self._retranslate_all_btn.configure(state="disabled", text="翻译中...") - pending: list[tuple[str, str]] = [] - for key, data in self._review_data.items(): - pending.append((key, data["source"])) - total = len(pending) - self._review_subtitle.configure(text=f"正在逐条重新翻译 0/{total}。每条会独立处理,不会互相影响。") - - def worker(): - try: - selected_api_key, model, style, base_url = self._current_translator_settings(api_key) - translator = DeepSeekTranslator( - api_key=selected_api_key, - model=model, - base_url=base_url, - ) - except Exception as exc: - self.after(0, lambda e=exc: self._on_retranslate_all_error(e)) - return - - ok = 0 - warning_count = 0 - failed = 0 - for index, (key, source) in enumerate(pending, start=1): - self.after(0, lambda k=key: self._set_review_entry_status(k, "正在重新翻译...")) - try: - protected_source, protections = protect_text(source) - batch = OrderedDict([(key, protected_source)]) - result = translator.translate_batch(batch, style=style) - translated = result.get(key, protected_source) - restored = restore_text(translated, protections) - restored = repair_translation_format(source, restored) - warnings = preserved_token_warnings(source, restored) - if warnings: - warning_count += 1 - self.after(0, lambda k=key, t=restored, w=warnings: - self._on_batch_retranslate(k, t, w)) - else: - ok += 1 - self.after(0, lambda k=key, r=restored: - self._on_batch_retranslate(k, r, [])) - except Exception as exc: - failed += 1 - self.after(0, lambda k=key, e=exc: self._on_batch_retranslate_error(k, e)) - self.after(0, lambda i=index, o=ok, w=warning_count, f=failed: - self._update_retranslate_all_progress(i, total, o, w, f)) - self.after(0, lambda o=ok, w=warning_count, f=failed: - self._on_retranslate_all_done(o, w, f)) - - thread = threading.Thread(target=worker, daemon=True) - thread.start() - - def _set_review_entry_status(self, key: str, text: str) -> None: - data = self._review_data.get(key) - if data is None: - return - label = data.get("status_label") - if label: - label.configure(text=text) - - def _on_batch_retranslate(self, key: str, text: str, warnings: list[str]) -> None: - data = self._review_data.get(key) - if data is None: - return - tb = data.get("textbox") - sl = data.get("status_label") - if tb: - tb.delete("1.0", "end") - tb.insert("1.0", text) - if sl: - if warnings: - sl.configure(text=f"API 已返回,但仍有 {len(warnings)} 个格式告警,可手动编辑后保存。") - else: - sl.configure(text="重新翻译成功,确认无误后点击保存。") - - def _on_batch_retranslate_error(self, key: str, exc: Exception) -> None: - self._set_review_entry_status(key, f"API 重试失败:{exc}") - - def _update_retranslate_all_progress(self, done: int, total: int, ok: int, warning_count: int, failed: int) -> None: - self._review_subtitle.configure( - text=f"正在逐条重新翻译 {done}/{total}。成功 {ok},仍有格式告警 {warning_count},API 失败 {failed}。" - ) - - def _on_retranslate_all_done(self, ok: int, warning_count: int, failed: int) -> None: - self._retranslate_all_btn.configure(state="normal", text="全部重新翻译") - self._review_subtitle.configure( - text=f"重新翻译完成:成功 {ok} 条,仍需人工处理 {warning_count} 条,API 失败 {failed} 条。成功项仍需确认后点击保存。" - ) - - def _on_retranslate_all_error(self, exc: Exception) -> None: - self._retranslate_all_btn.configure(state="normal", text="全部重新翻译") - self._review_subtitle.configure(text=f"无法开始全部重新翻译:{exc}") - - def _ignore_review_entry(self, key: str, card: ctk.CTkFrame) -> None: - card.grid_remove() - self._review_data.pop(key, None) - if not self._review_data: - self._review_subtitle.configure(text="所有条目已处理。") - self._review_badge.configure(text="待处理 0") - else: - self._review_badge.configure(text=f"待处理 {len(self._review_data)}") - self._review_title.set(f"人工处理 — {len(self._review_data)} 条需要确认的映射") - self._refresh_review_layout() - - def _refresh_review_layout(self) -> None: - for idx, data in enumerate(self._review_data.values()): - frame = data.get("frame") - if frame and frame.winfo_exists(): - frame.grid(row=idx, column=0, sticky="ew", pady=(0, 12)) - - def _ignore_all_review(self) -> None: - for widget in self._review_scroll.winfo_children(): - widget.destroy() - self._review_data.clear() - self._review_subtitle.configure(text="已忽略所有条目。") - self._review_badge.configure(text="待处理 0") - - def _panel( - self, - parent: ctk.CTkFrame | ctk.CTkScrollableFrame, - title: str, - description: str, - ) -> ctk.CTkFrame: - panel = ctk.CTkFrame( - parent, - corner_radius=12, - border_width=1, - fg_color=("#FFFFFF", "#161B22"), - border_color=("#D0D7DE", "#30363D"), - ) - ctk.CTkLabel( - panel, - text=title, - anchor="w", - font=ctk.CTkFont(size=14, weight="bold"), - text_color=("#1F2328", "#E6EDF3"), - ).grid( - row=0, column=0, columnspan=3, sticky="ew", padx=16, pady=(14, 2) - ) - ctk.CTkLabel( - panel, text=description, anchor="w", - text_color=("#656D76", "#8B949E"), - font=ctk.CTkFont(size=12), - ).grid( - row=1, column=0, columnspan=3, sticky="ew", padx=16, pady=(0, 10) - ) - return panel - - def _show_view(self, view: str) -> None: - self.workbench_frame.grid_remove() - self.settings_frame.grid_remove() - self.history_frame.grid_remove() - if view == "settings": - self.settings_frame.grid() - elif view == "history": - self.history_frame.grid() - self._refresh_history_list() - else: - self.workbench_frame.grid() - view = "workbench" - self._set_nav_state(view) - - def _set_nav_state(self, active_view: str) -> None: - for view, button in self._nav_buttons.items(): - if view == active_view: - button.configure(fg_color="#2563EB", hover_color="#1D4ED8", text_color="#FFFFFF") - else: - button.configure( - fg_color=("#F3F4F6", "#21262D"), - hover_color=("#E5E7EB", "#30363D"), - text_color=("#374151", "#C9D1D9"), - ) - - def _change_appearance(self, value: str) -> None: - modes = {"系统": "System", "浅色": "Light", "深色": "Dark"} - ctk.set_appearance_mode(modes.get(value, "System")) - - def _toggle_key_visibility(self) -> None: - self._key_visible = not self._key_visible - self.key_entry.configure(show="" if self._key_visible else "*") - self.toggle_key_button.configure(text="隐藏" if self._key_visible else "显示") - - def _reset_service_settings(self) -> None: - self.base_url.set(DEFAULT_BASE_URL) - self.model.set(DEFAULT_MODEL) - self.settings_status.set("已恢复默认 DeepSeek 服务参数,点击保存全部后生效。") - - def _current_style(self) -> str: - text = self.style_text.get("1.0", "end").strip() - return text or DEFAULT_STYLE - - def _current_translator_settings(self, fallback_api_key: str) -> tuple[str, str, str, str]: - settings = self._run_settings - if settings is not None: - return ( - settings["api_key"] or fallback_api_key, - settings["model"], - settings["style"], - settings["base_url"], - ) - return ( - fallback_api_key, - self.model.get() or DEFAULT_MODEL, - self._current_style(), - self.base_url.get() or DEFAULT_BASE_URL, - ) - - def _parse_optional_positive_int(self, value: str, label: str) -> int | None: - stripped = value.strip() - if not stripped or stripped.lower() == "auto": - return None - try: - parsed = int(stripped) - except ValueError as exc: - raise ValueError(f"{label} 必须填写 auto 或正整数。") from exc - if parsed <= 0: - raise ValueError(f"{label} 必须大于 0。") - return parsed - - def _effective_batch_size(self) -> int | None: - return self._parse_optional_positive_int(self.batch_size.get(), "批大小") - - def _effective_max_workers(self) -> int | None: - return self._parse_optional_positive_int(self.max_workers.get(), "并发数") - - def _save_settings(self) -> None: - try: - self._effective_batch_size() - self._effective_max_workers() - except ValueError as exc: - self.settings_status.set(str(exc)) - messagebox.showerror("配置无效", str(exc)) - return - - self.style.set(self._current_style()) - credential_backend = credential_store.save_api_key(self.api_key.get()) - save_config_values( - { - BASE_URL_KEY: self.base_url.get() or DEFAULT_BASE_URL, - MODEL_KEY: self.model.get() or DEFAULT_MODEL, - STYLE_KEY: self.style.get() or DEFAULT_STYLE, - BATCH_SIZE_KEY: self.batch_size.get() or "auto", - CONCURRENCY_KEY: self.max_workers.get() or "auto", - } - ) - self.settings_status.set( - f"已保存全部设置。API Key 已存入 {credential_store.storage_backend_label(credential_backend)}。" - ) - _log.info("Saved app settings") - self._log("已保存全部设置。") - - def _set_stage(self, stage: str) -> None: - labels = { - "idle": ("准备就绪", ("#DBEAFE", "#1E3A5F"), ("#1D4ED8", "#93C5FD")), - "scanned": ("已完成扫描", ("#DCFCE7", "#14532D"), ("#16A34A", "#86EFAC")), - "running": ("正在汉化", ("#FEF3C7", "#451A03"), ("#D97706", "#FCD34D")), - "done": ("汉化完成", ("#DCFCE7", "#14532D"), ("#16A34A", "#86EFAC")), - "error": ("需要处理", ("#FEE2E2", "#450A0A"), ("#DC2626", "#FCA5A5")), - } - text, bg_color, text_color = labels.get(stage, labels["idle"]) - self.stage.set(text) - self.stage_badge.configure(fg_color=bg_color, text_color=text_color) - - active_order = ["idle", "scanned", "running", "done"] - active_index = active_order.index(stage) if stage in active_order else 0 - for index, key in enumerate(active_order): - label = self._step_labels.get(key) - if label is None: - continue - if index <= active_index and stage != "error": - label.configure( - fg_color=("#2563EB", "#2563EB"), - text_color="#FFFFFF", - ) - else: - label.configure( - fg_color=("#F3F4F6", "#1C2128"), - text_color=("#656D76", "#8B949E"), - ) - - def _choose_dir(self) -> None: - directory = filedialog.askdirectory() - if directory: - self.selected_dir.set(directory) - _log.info("User selected directory: %s", directory) - self._scan() - - def _scan(self) -> None: - self._review_report = None - self._hide_review_panel() - try: - _log.info("Starting scan for: %s", self.selected_dir.get()) - batch_size = self._effective_batch_size() - quests_dir = resolve_quests_dir(Path(self.selected_dir.get())) - mode = detect_source_mode(quests_dir) - if mode == "lang": - values = load_lang_snbt(source_lang_path(quests_dir)) - entry_count = len(values) - source_label = str(source_lang_path(quests_dir)) - mode_label = "新版 lang/en_us.snbt" - _log.info("Scan result: lang mode, %d entries from %s", entry_count, source_label) - else: - file_count, entry_count = count_chapter_segments(quests_dir) - source_label = f"{quests_dir / 'chapters'}({file_count} 个章节文件)" - mode_label = "章节式 chapters/*.snbt" - _log.info("Scan result: chapters mode, %d files, %d entries", file_count, entry_count) - effective_batch_size = batch_size or AUTO_BATCH_MAX_ENTRIES - batches = estimate_batches(entry_count, effective_batch_size) - except Exception as exc: # noqa: BLE001 - _log.error("Scan failed: %s", exc, exc_info=True) - self._quests_dir = None - self.translate_button.configure(state="disabled") - self.summary.set("扫描失败") - self.status.set(str(exc)) - self.progress_text.set("扫描失败") - self._set_stage("error") - self._log(f"扫描失败:{exc}") - return - - self._quests_dir = quests_dir - self.translate_button.configure(state="normal") - self.summary.set( - f"任务书目录:{quests_dir}\n" - f"模式:{mode_label}\n" - f"源:{source_label}\n" - f"可翻译条目数:{entry_count},预计 {batches} 批,模型:{self.model.get() or DEFAULT_MODEL}" - ) - self.status.set("扫描完成,可以开始汉化。") - self.progress_text.set(f"扫描完成:{entry_count} 条,预计 {batches} 批") - self._set_stage("scanned") - self._log("扫描完成。") - - def _start_translate(self) -> None: - _log.info("Translation requested by user") - if self._quests_dir is None: - self._scan() - if self._quests_dir is None: - return - try: - batch_size = self._effective_batch_size() - max_workers = self._effective_max_workers() - except ValueError as exc: - self._show_view("settings") - self.settings_status.set(str(exc)) - messagebox.showerror("配置无效", str(exc)) - return - if not self.api_key.get().strip(): - self._show_view("settings") - self.settings_status.set("请先填写 API Key,然后点击保存全部。") - messagebox.showerror("缺少 API Key", "请填写 DeepSeek API Key。") - return - mode = detect_source_mode(self._quests_dir) - target = "lang/zh_cn.snbt" if mode == "lang" else "chapters/*.snbt" - if not messagebox.askyesno( - "确认覆盖写入", - f"将先创建备份,然后覆盖写入 {target}。\n\n任务书目录:{self._quests_dir}\n\n是否继续?", - ): - self._log("已取消:未执行覆盖写入。") - _log.info("User cancelled the overwrite confirmation") - return - self._save_settings() - self._run_settings = { - "api_key": self.api_key.get(), - "batch_size": batch_size, - "model": self.model.get() or DEFAULT_MODEL, - "style": self._current_style(), - "base_url": self.base_url.get() or DEFAULT_BASE_URL, - "max_workers": max_workers, - } - self.scan_button.configure(state="disabled") - self.translate_button.configure(state="disabled") - self._review_report = None - self._hide_review_panel() - self.progress.set(0) - self.status.set("正在汉化...") - self.progress_text.set("正在准备翻译任务...") - self._set_stage("running") - _log.info("Starting translate worker thread (quests_dir=%s, mode=%s)", self._quests_dir, mode) - thread = threading.Thread(target=self._translate_worker, daemon=True) - thread.start() - - def _translate_worker(self) -> None: - assert self._quests_dir is not None - settings = self._run_settings - if settings is None: - self._queue.put(("error", RuntimeError("翻译配置未初始化。"))) - return - - def progress(stage: str, done: int, total: int) -> None: - self._queue.put(("progress", (stage, done, total))) - - def logger(message: str) -> None: - self._queue.put(("log", message)) - - try: - _log.info("Calling translate_quests_auto with quests_dir=%s", self._quests_dir) - report = translate_quests_auto( - quests_dir=self._quests_dir, - api_key=settings["api_key"], - batch_size=settings["batch_size"], - model=settings["model"], - style=settings["style"], - base_url=settings["base_url"], - max_workers=settings["max_workers"], - progress=progress, - logger=logger, - ) - _log.info("Translation completed successfully: target=%s", report.target_file) - self._queue.put(("done", report)) - except Exception as exc: # noqa: BLE001 - _log.error("Translation worker failed: %s", exc, exc_info=True) - self._queue.put(("error", exc)) - - def _drain_queue(self) -> None: - try: - while True: - kind, payload = self._queue.get_nowait() - if kind == "progress": - stage, done, total = cast(_ProgressPayload, payload) - ratio = 1 if total == 0 else min(1, done / total) - self.progress.set(ratio) - self.status.set(f"{stage}: {done}/{total}") - self.progress_text.set(f"{stage}:{done}/{total}") - elif kind == "done": - report = cast(TranslationReport, payload) - self.progress.set(1) - self.status.set("汉化完成。") - self.progress_text.set("汉化完成,可以查看日志和输出文件。") - self._set_stage("done") - self._log(f"完成:写入 {report.target_file}") - self._log(f"备份:{report.backup_dir}") - self._log(f"缓存命中:{report.cache_hits},失败:{len(report.failed_entries)}") - self._review_report = report - self._save_history(report) - if report.warnings or report.failed_entries: - self._log(f"告警:{len(report.warnings)} 条翻译存在格式 token 问题。") - if report.failed_entries: - self._log(f"失败映射:{len(report.failed_entries)} 条需要人工处理。") - self._show_review_entries() - else: - self._hide_review_panel() - self.scan_button.configure(state="normal") - self.translate_button.configure(state="normal") - elif kind == "log": - self._log(str(payload)) - elif kind == "history_saved": - run_id = cast(int, payload) - self._log(f"已保存到历史记录 #{run_id}") - if getattr(self, "_history_list_frame", None) is not None \ - and self.history_frame.winfo_ismapped(): - self._refresh_history_list() - elif kind == "history_save_failed": - self._log(f"历史记录保存失败:{payload}") - elif kind == "error": - exc = cast(Exception, payload) - self.status.set(str(exc)) - self.progress_text.set("汉化失败,请查看日志。") - self._set_stage("error") - self._log(f"汉化失败:{exc}") - self.scan_button.configure(state="normal") - self.translate_button.configure(state="normal") - except queue.Empty: - pass - self.after(150, self._drain_queue) - - def _save_history(self, report: TranslationReport) -> None: - """在后台线程把翻译结果写入 SQLite,避免阻塞 GUI。""" - if not report.output_files: - return - settings = self._run_settings - quests_dir = self._quests_dir - if quests_dir is None or settings is None: - return - try: - mode = detect_source_mode(quests_dir) - except Exception: # noqa: BLE001 - mode = "lang" if report.target_file.endswith(".snbt") else "chapters" - - files: list[FileRecord] = [] - for filename, content in report.output_files.items(): - mapping = report.mapping.get(filename, {}) - files.append(FileRecord(filename=filename, mapping=mapping, output_content=content)) - - payload = { - "quests_dir": str(quests_dir), - "mode": mode, - "model": settings["model"], - "style": settings["style"], - "base_url": settings["base_url"], - "total_entries": report.total_entries, - "translated_entries": report.translated_entries, - "cache_hits": report.cache_hits, - "failed_count": len(report.failed_entries), - "warning_count": len(report.warnings), - "files": files, - } - - def worker() -> None: - try: - run_id = self.history_db.insert_run(**payload) - self._queue.put(("history_saved", run_id)) - except Exception as exc: # noqa: BLE001 - _log.error("Failed to save history: %s", exc, exc_info=True) - self._queue.put(("history_save_failed", str(exc))) - - threading.Thread(target=worker, daemon=True).start() - - def _log(self, text: str) -> None: - """Log to both the GUI textbox and the file logger.""" - self.log.insert("end", text + "\n") - self.log.see("end") - _log.info("[GUI] %s", text) - - -def main() -> None: - app = FtbTranslaterApp() - app.mainloop() diff --git a/ftb_translater/backup.py b/ftb_translater/backup.py deleted file mode 100644 index 08ce51f..0000000 --- a/ftb_translater/backup.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import shutil -from datetime import datetime -from pathlib import Path - -from ftb_translater.logger import get_logger - -_log = get_logger(__name__) - - -def create_backup(quests_dir: Path, directories: tuple[str, ...] = ("lang",)) -> Path: - timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") - backup_root = quests_dir / ".ftb-translater" / "backups" / timestamp - copied = False - for directory in directories: - source = quests_dir / directory - if not source.is_dir(): - _log.warning("Backup source not found, skipping: %s", source) - continue - destination = backup_root / directory - destination.parent.mkdir(parents=True, exist_ok=True) - shutil.copytree(source, destination) - _log.info("Backed up %s -> %s", source, destination) - copied = True - if not copied: - names = ", ".join(directories) - _log.error("No backup source directories found under %s: %s", quests_dir, names) - raise FileNotFoundError(f"Missing backup source directories under {quests_dir}: {names}") - return backup_root diff --git a/ftb_translater/cache.py b/ftb_translater/cache.py deleted file mode 100644 index e7d9180..0000000 --- a/ftb_translater/cache.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import annotations - -import hashlib -import json -from pathlib import Path -from typing import Any - -from ftb_translater.logger import get_logger - -_log = get_logger(__name__) - - -class TranslationCache: - def __init__(self, path: Path): - self.path = path - self._data: dict[str, str] = {} - - def load(self) -> None: - if not self.path.exists(): - _log.debug("Cache file not found, starting empty: %s", self.path) - self._data = {} - return - _log.debug("Loading cache from %s", self.path) - with self.path.open("r", encoding="utf-8") as file: - raw = json.load(file) - if not isinstance(raw, dict): - _log.error("Invalid cache file (not a JSON object): %s", self.path) - raise ValueError(f"Invalid cache file: {self.path}") - self._data = {str(key): str(value) for key, value in raw.items()} - _log.debug("Cache loaded: %d entries", len(self._data)) - - def save(self) -> None: - self.path.parent.mkdir(parents=True, exist_ok=True) - _log.debug("Saving cache to %s (%d entries)", self.path, len(self._data)) - with self.path.open("w", encoding="utf-8") as file: - json.dump(self._data, file, ensure_ascii=False, indent=2, sort_keys=True) - - def get(self, source_text: str, model: str, target_locale: str, style: str) -> str | None: - result = self._data.get(self._key(source_text, model, target_locale, style)) - if result is not None: - _log.debug("Cache hit for text (len=%d)", len(source_text)) - return result - - def set(self, source_text: str, model: str, target_locale: str, style: str, translation: str) -> None: - self._data[self._key(source_text, model, target_locale, style)] = translation - - @staticmethod - def _key(source_text: str, model: str, target_locale: str, style: str) -> str: - payload: dict[str, Any] = { - "source_text": source_text, - "model": model, - "target_locale": target_locale, - "style": style, - } - encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8") - return hashlib.sha256(encoded).hexdigest() diff --git a/ftb_translater/chapters.py b/ftb_translater/chapters.py deleted file mode 100644 index caa28b6..0000000 --- a/ftb_translater/chapters.py +++ /dev/null @@ -1,226 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path - -from ftb_translater.logger import get_logger - -_log = get_logger(__name__) - -TRANSLATABLE_KEYS = frozenset({"title", "subtitle", "description", "text", "name"}) - - -@dataclass(frozen=True) -class ChapterTextSegment: - path: Path - key: str - source_text: str - start: int - end: int - quote: str - index: int - - @property - def cache_id(self) -> str: - return f"{self.path.name}:{self.index}:{self.key}" - - -def chapter_files(quests_dir: Path) -> list[Path]: - chapters_dir = quests_dir / "chapters" - if not chapters_dir.is_dir(): - _log.debug("No chapters directory at %s", chapters_dir) - return [] - files = sorted(chapters_dir.glob("*.snbt")) - _log.debug("Found %d chapter files in %s", len(files), chapters_dir) - return files - - -def extract_chapter_segments(path: Path) -> list[ChapterTextSegment]: - _log.debug("Extracting segments from %s", path) - text = path.read_text(encoding="utf-8-sig") - segments: list[ChapterTextSegment] = [] - i = 0 - segment_index = 0 - while i < len(text): - if text.startswith("//", i) or text[i] == "#": - i = _skip_line_comment(text, i) - continue - if text[i] in {'"', "'"}: - key, end, _ = _parse_string_literal(text, i) - value_start = _position_after_colon(text, end) - if value_start is None or key not in TRANSLATABLE_KEYS: - i = end - continue - next_i, found = _extract_value_segments(text, value_start, path, key, segment_index) - segments.extend(found) - segment_index += len(found) - i = max(end, next_i) - continue - if _is_key_start(text[i]): - key_start = i - while i < len(text) and _is_key_part(text[i]): - i += 1 - key = text[key_start:i] - value_start = _position_after_colon(text, i) - if value_start is None: - continue - if key not in TRANSLATABLE_KEYS: - i = value_start - continue - next_i, found = _extract_value_segments(text, value_start, path, key, segment_index) - segments.extend(found) - segment_index += len(found) - i = max(i, next_i) - continue - i += 1 - _log.debug("Extracted %d translatable segments from %s", len(segments), path) - return segments - - -def replace_chapter_segments(path: Path, translations: dict[int, str]) -> int: - _log.debug("Replacing %d segments in %s", len(translations), path) - text = path.read_text(encoding="utf-8-sig") - segments = extract_chapter_segments(path) - replacements = [segment for segment in segments if segment.index in translations] - for segment in sorted(replacements, key=lambda item: item.start, reverse=True): - literal = _quote_string(translations[segment.index], segment.quote) - text = text[: segment.start] + literal + text[segment.end :] - path.write_text(text, encoding="utf-8") - _log.debug("Replaced %d segments in %s", len(replacements), path) - return len(replacements) - - -def count_chapter_segments(quests_dir: Path) -> tuple[int, int]: - files = chapter_files(quests_dir) - counts = {path: len(extract_chapter_segments(path)) for path in files} - total = sum(counts.values()) - _log.debug("Chapter segments count: %d files, %d segments total", len(files), total) - return len(files), total - - -def _extract_value_segments( - text: str, - value_start: int, - path: Path, - key: str, - segment_index: int, -) -> tuple[int, list[ChapterTextSegment]]: - i = _skip_ws(text, value_start) - if i >= len(text): - return i, [] - if text[i] in {'"', "'"}: - value, end, quote = _parse_string_literal(text, i) - if _should_translate(value): - return end, [ChapterTextSegment(path, key, value, i, end, quote, segment_index)] - return end, [] - if text[i] == "[": - return _extract_list_strings(text, i, path, key, segment_index) - return i, [] - - -def _extract_list_strings( - text: str, - start: int, - path: Path, - key: str, - segment_index: int, -) -> tuple[int, list[ChapterTextSegment]]: - i = start + 1 - depth = 1 - segments: list[ChapterTextSegment] = [] - next_index = segment_index - while i < len(text) and depth > 0: - if text.startswith("//", i) or text[i] == "#": - i = _skip_line_comment(text, i) - continue - char = text[i] - if char in {'"', "'"}: - value, end, quote = _parse_string_literal(text, i) - if _should_translate(value): - segments.append(ChapterTextSegment(path, key, value, i, end, quote, next_index)) - next_index += 1 - i = end - continue - if char == "[": - depth += 1 - elif char == "]": - depth -= 1 - i += 1 - return i, segments - - -def _position_after_colon(text: str, position: int) -> int | None: - i = _skip_ws(text, position) - if i < len(text) and text[i] == ":": - return i + 1 - return None - - -def _parse_string_literal(text: str, start: int) -> tuple[str, int, str]: - quote = text[start] - i = start + 1 - chars: list[str] = [] - while i < len(text): - char = text[i] - i += 1 - if char == quote: - return "".join(chars), i, quote - if char == "\\": - if i >= len(text): - chars.append("\\") - break - esc = text[i] - i += 1 - chars.append(_decode_escape(esc)) - else: - chars.append(char) - raise ValueError(f"Unterminated string in {start}") - - -def _quote_string(value: str, quote: str) -> str: - escaped = ( - value.replace("\\", "\\\\") - .replace(quote, f"\\{quote}") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t") - ) - return f"{quote}{escaped}{quote}" - - -def _decode_escape(esc: str) -> str: - mapping = { - "n": "\n", - "r": "\r", - "t": "\t", - "\\": "\\", - '"': '"', - "'": "'", - } - return mapping.get(esc, esc) - - -def _skip_line_comment(text: str, start: int) -> int: - end = text.find("\n", start) - return len(text) if end == -1 else end + 1 - - -def _skip_ws(text: str, position: int) -> int: - while position < len(text) and text[position].isspace(): - position += 1 - return position - - -def _is_key_start(char: str) -> bool: - return char.isalpha() or char == "_" - - -def _is_key_part(char: str) -> bool: - return char.isalnum() or char in "_-.+" - - -def _should_translate(value: str) -> bool: - stripped = value.strip() - if not stripped: - return False - return any("A" <= char <= "Z" or "a" <= char <= "z" for char in stripped) diff --git a/ftb_translater/config.py b/ftb_translater/config.py deleted file mode 100644 index ae8ed9d..0000000 --- a/ftb_translater/config.py +++ /dev/null @@ -1,168 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from ftb_translater.logger import get_logger -from ftb_translater.user_paths import user_config_dir - -try: - from dotenv import dotenv_values -except ImportError: # pragma: no cover - dependency is declared, fallback keeps imports readable. - dotenv_values = None - - -_log = get_logger(__name__) - -ENV_KEY = "DEEPSEEK_API_KEY" -BASE_URL_KEY = "DEEPSEEK_BASE_URL" -MODEL_KEY = "DEEPSEEK_MODEL" -STYLE_KEY = "FTB_TRANSLATER_STYLE" -BATCH_SIZE_KEY = "FTB_TRANSLATER_BATCH_SIZE" -CONCURRENCY_KEY = "FTB_TRANSLATER_CONCURRENCY" - -# API Key 现在通过 credential_store(keyring)管理,不再写入 .env -APP_CONFIG_KEYS = ( - BASE_URL_KEY, - MODEL_KEY, - STYLE_KEY, - BATCH_SIZE_KEY, - CONCURRENCY_KEY, -) - - -def env_path(base_dir: Path | None = None) -> Path: - if base_dir is not None: - return base_dir / ".env" - return user_config_dir() / ".env" - - -def load_api_key(base_dir: Path | None = None) -> str: - """已废弃:仅用于从旧 .env 文件迁移读取。新代码应使用 credential_store。""" - path = env_path(base_dir) - _log.debug("Loading API key from %s", path) - if dotenv_values is not None and path.exists(): - value = dotenv_values(path).get(ENV_KEY) - if value: - _log.debug("API key loaded via python-dotenv") - return str(value) - if path.exists(): - value = _read_env_file(path).get(ENV_KEY) - if value: - _log.debug("API key loaded from raw app settings file") - return value - return "" - - -def load_config_values(base_dir: Path | None = None) -> dict[str, str]: - path = env_path(base_dir) - values: dict[str, str] = {} - if dotenv_values is not None and path.exists(): - values.update({key: str(value) for key, value in dotenv_values(path).items() if value is not None}) - elif path.exists(): - values.update(_read_env_file(path)) - - for key in APP_CONFIG_KEYS: - if not values.get(key): - values[key] = "" - return values - - -def save_config_values(values: dict[str, str], base_dir: Path | None = None) -> None: - path = env_path(base_dir) - path.parent.mkdir(parents=True, exist_ok=True) - _log.info("Saving app config to %s", path) - - lines: list[str] = [] - if path.exists(): - lines = path.read_text(encoding="utf-8").splitlines() - - pending = {key: value.strip() for key, value in values.items() if key in APP_CONFIG_KEYS} - found: set[str] = set() - next_lines: list[str] = [] - for line in lines: - stripped = line.strip() - if "=" not in stripped or stripped.startswith("#"): - next_lines.append(line) - continue - key, _value = stripped.split("=", 1) - key = key.strip() - if key in pending: - next_lines.append(f"{key}={pending[key]}") - found.add(key) - else: - next_lines.append(line) - - for key in APP_CONFIG_KEYS: - if key in pending and key not in found: - next_lines.append(f"{key}={pending[key]}") - - path.write_text("\n".join(next_lines).rstrip() + "\n", encoding="utf-8") - _log.debug("App config saved successfully") - - -def save_api_key(api_key: str, base_dir: Path | None = None) -> str: - """转发到 credential_store。base_dir 参数仅为向后兼容保留,会被忽略。""" - from ftb_translater import credential_store - - backend = credential_store.save_api_key(api_key) - _log.debug("API key saved via credential_store") - return backend - - -def migrate_api_key_from_env(base_dir: Path | None = None) -> bool: - """把 .env 里的旧 API Key 迁移到 credential_store,迁移成功后从 .env 删除。 - - Returns True 如果完成了迁移。 - """ - from ftb_translater import credential_store - - legacy_key = load_api_key(base_dir) - if not legacy_key: - return False - - stored_key = credential_store.load_api_key() - if stored_key: - if stored_key == legacy_key: - _strip_api_key_from_env(base_dir) - _log.info("Removed duplicate API key from .env after credential migration") - else: - _log.warning( - "Legacy .env API key differs from credential store; keeping .env value for manual review" - ) - return False - - credential_store.save_api_key(legacy_key) - _strip_api_key_from_env(base_dir) - _log.info("Migrated API key from .env to credential store") - return True - - -def _strip_api_key_from_env(base_dir: Path | None = None) -> None: - path = env_path(base_dir) - if not path.exists(): - return - lines = path.read_text(encoding="utf-8").splitlines() - kept = [] - changed = False - for line in lines: - stripped = line.strip() - if "=" in stripped and not stripped.startswith("#"): - key = stripped.split("=", 1)[0].strip() - if key == ENV_KEY: - changed = True - continue - kept.append(line) - if changed: - path.write_text("\n".join(kept).rstrip() + "\n", encoding="utf-8") - - -def _read_env_file(path: Path) -> dict[str, str]: - values: dict[str, str] = {} - for raw_line in path.read_text(encoding="utf-8").splitlines(): - line = raw_line.strip() - if not line or line.startswith("#") or "=" not in line: - continue - key, value = line.split("=", 1) - value = value.strip().strip('"').strip("'") - values[key.strip()] = value - return values diff --git a/ftb_translater/credential_store.py b/ftb_translater/credential_store.py deleted file mode 100644 index 3cf1df0..0000000 --- a/ftb_translater/credential_store.py +++ /dev/null @@ -1,161 +0,0 @@ -"""API Key 凭证存储。 - -优先用系统 keyring(macOS Keychain / Windows Credential Manager / Linux Secret Service), -这才是"加密存储"——密钥由系统级安全组件管理。 - -如果 keyring 后端不可用(例如 headless Linux 没装 Secret Service),会写入一个混淆过的 -本地文件作为兜底。这只是为了避免明文落盘,密钥派生自机器特征,本机能读这个文件的攻击 -者通常也能推导密钥——因此这不是真正的加密,只是让 API Key 不直接 grep 到。强烈建议 -配置可用的 keyring 后端来获得真正的凭证安全。 -""" -from __future__ import annotations - -import base64 -import hashlib -import os -import platform -import uuid -from pathlib import Path - -from ftb_translater.logger import get_logger -from ftb_translater.user_paths import user_config_dir - -_log = get_logger(__name__) - -SERVICE_NAME = "ftb-translater" -ACCOUNT_NAME = "deepseek_api_key" - - -def _fallback_path() -> Path: - return user_config_dir() / ".credential-fallback" - - -def _machine_key() -> bytes: - """从机器特征派生 Fernet 密钥。同一台机器多次启动稳定,跨机器不同。""" - seed_parts = [platform.node(), platform.machine(), str(uuid.getnode())] - seed = "::".join(seed_parts).encode("utf-8") - digest = hashlib.sha256(seed).digest() - return base64.urlsafe_b64encode(digest) - - -def _keyring_available() -> bool: - try: - import keyring - from keyring.errors import NoKeyringError - - backend = keyring.get_keyring() - if backend is None: - return False - if "fail" in type(backend).__name__.lower(): - return False - return True - except (ImportError, Exception) as exc: # noqa: BLE001 - _log.debug("Keyring unavailable: %s", exc) - return False - - -def save_api_key(api_key: str) -> str: - """Save the API key and return the backend actually used.""" - api_key = api_key.strip() - if _keyring_available(): - try: - import keyring - - if not api_key: - try: - keyring.delete_password(SERVICE_NAME, ACCOUNT_NAME) - except Exception: # noqa: BLE001 - pass - else: - keyring.set_password(SERVICE_NAME, ACCOUNT_NAME, api_key) - _log.info("API key saved to system keyring") - _clear_fallback() - return "keyring" - except Exception as exc: # noqa: BLE001 - _log.warning("Keyring save failed, falling back to encrypted file: %s", exc) - _save_fallback(api_key) - return "fallback" - - -def load_api_key() -> str: - if _keyring_available(): - try: - import keyring - - value = keyring.get_password(SERVICE_NAME, ACCOUNT_NAME) - if value: - return value - except Exception as exc: # noqa: BLE001 - _log.warning("Keyring load failed, trying fallback: %s", exc) - return _load_fallback() - - -def delete_api_key() -> None: - if _keyring_available(): - try: - import keyring - - keyring.delete_password(SERVICE_NAME, ACCOUNT_NAME) - except Exception: # noqa: BLE001 - pass - _clear_fallback() - - -def has_api_key() -> bool: - return bool(load_api_key()) - - -def storage_backend_label(backend: str | None = None) -> str: - """返回当前使用的存储后端友好名,用于 UI 文案。""" - if backend == "fallback": - return "本地受限文件(非加密,仅做混淆)" - if backend == "keyring" or _keyring_available(): - system = platform.system() - if system == "Darwin": - return "macOS 钥匙串" - if system == "Windows": - return "Windows 凭据管理器" - if system == "Linux": - return "系统 Secret Service" - return "系统凭证管理器" - return "本地受限文件(非加密,仅做混淆)" - - -def _save_fallback(api_key: str) -> None: - from cryptography.fernet import Fernet - - path = _fallback_path() - path.parent.mkdir(parents=True, exist_ok=True) - if not api_key: - _clear_fallback() - return - token = Fernet(_machine_key()).encrypt(api_key.encode("utf-8")) - path.write_bytes(token) - try: - os.chmod(path, 0o600) - except OSError: - pass - _log.info("API key saved to encrypted fallback file") - - -def _load_fallback() -> str: - from cryptography.fernet import Fernet, InvalidToken - - path = _fallback_path() - if not path.exists(): - return "" - try: - decoded = Fernet(_machine_key()).decrypt(path.read_bytes()) - return decoded.decode("utf-8") - except (InvalidToken, ValueError) as exc: - _log.error("Fallback credential corrupt or from another machine: %s", exc) - return "" - - -def _clear_fallback() -> None: - path = _fallback_path() - if path.exists(): - try: - path.unlink() - except OSError as exc: - _log.warning("Could not delete fallback credential: %s", exc) diff --git a/ftb_translater/deepseek_client.py b/ftb_translater/deepseek_client.py deleted file mode 100644 index 2bcf0db..0000000 --- a/ftb_translater/deepseek_client.py +++ /dev/null @@ -1,170 +0,0 @@ -from __future__ import annotations - -import json -import time -from collections.abc import Callable, Mapping -from typing import Any - -from ftb_translater.logger import get_logger - -DEFAULT_BASE_URL = "https://api.deepseek.com" -DEFAULT_MODEL = "deepseek-v4-flash" -DEFAULT_STYLE = "自然玩家向简体中文汉化" - -_log = get_logger(__name__) - - -class DeepSeekTranslationError(RuntimeError): - pass - - -class DeepSeekTranslator: - def __init__( - self, - api_key: str, - model: str = DEFAULT_MODEL, - base_url: str = DEFAULT_BASE_URL, - client: Any | None = None, - retries: int = 2, - logger: Callable[[str], None] | None = None, - ): - if not api_key.strip() and client is None: - raise ValueError("DeepSeek API Key is required.") - self.model = model - self.retries = retries - self.logger = logger - self.client = client or self._create_client(api_key=api_key, base_url=base_url) - - def translate_batch(self, entries: Mapping[str, str], style: str = DEFAULT_STYLE) -> dict[str, str]: - if not entries: - return {} - prompt = self._build_prompt(entries, style) - last_error: Exception | None = None - for attempt in range(self.retries + 1): - try: - msg = f"Calling DeepSeek {self.model}: {len(entries)} entries, attempt {attempt + 1}." - _log.info(msg) - self._log(msg) - return self._request_json(prompt, expected_keys=set(entries)) - except Exception as exc: # noqa: BLE001 - last_error = exc - msg = f"DeepSeek batch attempt {attempt + 1} failed: {exc}" - _log.warning(msg) - self._log(msg) - if attempt < self.retries: - time.sleep(0.8 * (attempt + 1)) - - recovered: dict[str, str] = {} - failures: list[str] = [] - for key, value in entries.items(): - try: - msg = f"Retrying DeepSeek as single entry: {key}" - _log.info(msg) - self._log(msg) - recovered[key] = self._request_json( - self._build_prompt({key: value}, style), - expected_keys={key}, - )[key] - except Exception as exc: # noqa: BLE001 - _log.error("Single-entry retry failed for key %r: %s", key, exc) - failures.append(f"{key}: {exc}") - if failures: - message = "; ".join(failures) - if last_error: - message = f"batch failed with {last_error}; single-item failures: {message}" - raise DeepSeekTranslationError(message) - return recovered - - def _request_json(self, prompt: str, expected_keys: set[str]) -> dict[str, str]: - _log.debug("Sending request to DeepSeek, expected keys: %s", sorted(expected_keys)) - response = self._request_json_response(prompt) - - content = response.choices[0].message.content - if not content: - raise DeepSeekTranslationError("DeepSeek returned an empty response.") - _log.debug("DeepSeek raw response length: %d chars", len(content)) - try: - raw = json.loads(content) - except json.JSONDecodeError as exc: - _log.error("DeepSeek returned invalid JSON: %s\nRaw content: %s", exc, content[:500]) - raise DeepSeekTranslationError(f"DeepSeek returned invalid JSON: {exc}") from exc - - if not isinstance(raw, dict): - _log.error("DeepSeek JSON response is not a dict, got %s: %s", type(raw).__name__, str(raw)[:200]) - raise DeepSeekTranslationError("DeepSeek JSON response must be an object.") - - missing = expected_keys - set(raw) - if missing: - _log.error("DeepSeek response missed keys: %s. Got keys: %s", sorted(missing), sorted(raw.keys())) - raise DeepSeekTranslationError(f"DeepSeek response missed keys: {sorted(missing)}") - - extra = set(raw) - expected_keys - if extra: - _log.warning("DeepSeek response returned extra keys that will be ignored: %s", sorted(extra)) - - _log.debug("DeepSeek response OK: %d keys returned", len(expected_keys)) - return {key: str(raw[key]) for key in expected_keys} - - def _request_json_response(self, prompt: str) -> Any: - response = self.client.chat.completions.create( - model=self.model, - messages=[ - { - "role": "system", - "content": ( - "You are a Minecraft modpack localization assistant. 你是 Minecraft 整合包任务书汉化助手。 " - "Translate only user-facing English quest text into natural Simplified Chinese. " - "只翻译玩家可见英文为自然简体中文。 " - "Never alter, remove, merge, or invent opaque placeholders such as ⟨P_0⟩. " - "绝不能修改、删除、合并或新增 ⟨P_0⟩ 这类占位符。" - ), - }, - {"role": "user", "content": prompt}, - ], - temperature=0.2, - response_format={"type": "json_object"}, - ) - return response - - @staticmethod - def _build_prompt(entries: Mapping[str, str], style: str) -> str: - payload = json.dumps(entries, ensure_ascii=False, indent=2) - return ( - "Task / 任务:Translate this FTB Quests language map to Simplified Chinese.\n" - f"Style / 风格:{style}.\n" - "Return one JSON object with exactly the same keys and translated string values.\n" - "返回一个 JSON 对象,key 必须与输入完全一致,value 是翻译后的字符串。\n\n" - "Hard rules / 硬性规则:\n" - "1. Translate English player-facing text into Simplified Chinese. 将玩家可见英文翻译为简体中文。\n" - "2. Do not translate JSON keys. 不要翻译 JSON key。\n" - "3. Opaque placeholders like ⟨P_0⟩, ⟨P_1⟩ are formatting/resource tokens, not words. 占位符是格式或资源标记,不是单词。\n" - "4. Every placeholder from the input value must appear in the output value exactly once. 输入 value 中每个占位符在输出 value 中必须出现且只出现一次。\n" - "5. Keep each placeholder byte-for-byte unchanged. Do not remove, rename, duplicate, merge, or reorder characters inside it. 占位符本身必须逐字完全不变。\n" - "6. Placeholders may wrap a word, e.g. ⟨P_0⟩Nether⟨P_1⟩. Translate the word but keep both wrappers: ⟨P_0⟩下界⟨P_1⟩. 占位符包住的英文也必须翻译,不能因为被包住就保留英文。\n" - "7. If Chinese word order moves a highlighted phrase, move the whole placeholder-wrapped phrase together. 中文语序变化时,移动整段被占位符包住的短语。\n" - "8. Preserve item IDs, tags, markdown links, line breaks, escape sequences, numbers, and units. 保留物品 ID、标签、链接、换行、转义、数字和单位。\n\n" - "Examples / 示例:\n" - "Input text: Defeat ⟨P_0⟩Ignis⟨P_1⟩ in the ⟨P_2⟩Burning Arena⟨P_3⟩.\n" - "Good: 在⟨P_2⟩燃烧竞技场⟨P_3⟩中击败⟨P_0⟩伊格尼斯⟨P_1⟩。\n" - "Bad: 在燃烧竞技场中击败⟨P_0⟩伊格尼斯⟨P_1⟩。 (lost ⟨P_2⟩ and ⟨P_3⟩)\n" - "Bad: 在⟨P_2⟩燃烧竞技场中击败⟨P_0⟩伊格尼斯⟨P_1⟩。 (lost closing wrapper ⟨P_3⟩)\n" - "Input text: Found in ⟨P_0⟩Nether⟨P_1⟩.\n" - "Good: 可在⟨P_0⟩下界⟨P_1⟩找到。\n" - "Bad: 可在下界找到。 (lost wrappers ⟨P_0⟩ and ⟨P_1⟩)\n\n" - f"{payload}" - ) - - @staticmethod - def _create_client(api_key: str, base_url: str) -> Any: - try: - from openai import OpenAI - except ImportError as exc: - raise DeepSeekTranslationError( - "Missing dependency 'openai'. Run `python -m pip install -e .` before using DeepSeek translation." - ) from exc - _log.debug("Creating OpenAI client with base_url=%s", base_url) - return OpenAI(api_key=api_key.strip(), base_url=base_url) - - def _log(self, message: str) -> None: - if self.logger: - self.logger(message) diff --git a/ftb_translater/format_guard.py b/ftb_translater/format_guard.py deleted file mode 100644 index 45c1116..0000000 --- a/ftb_translater/format_guard.py +++ /dev/null @@ -1,470 +0,0 @@ -from __future__ import annotations - -import json -import re -import threading -from collections import Counter - -from ftb_translater.logger import get_logger - -_log = get_logger(__name__) - -# ---- All protected token patterns (MUST be exactly preserved) ---- - -# Minecraft colour / formatting codes (& and § variants, including rainbow &z) -_COLOR_PATTERN = re.compile(r"[&§][0-9a-fk-orz]", re.IGNORECASE) - -# printf-style format specifiers e.g. %s, %d, %1$s, %.2f -# The trailing (?!\w) avoids false matches on natural text like "50% faster". -_FORMAT_PATTERN = re.compile( - r"%(?:\d+\$)?[+#\- 0,(]*\d*(?:\.\d+)?[bcdeEufFgGosxX](?!\w)", -) - -# Angle-bracket IDs e.g. , -_ANGLE_PATTERN = re.compile(r"<[^<>\n]+>") - -# FTB/quest text macros and embeds e.g. {@pagebreak}, {image:ftb:textures/...} -_BRACE_MACRO_PATTERN = re.compile(r"\{[@A-Za-z][^{}\n]*\}") - -# Resource identifiers / file paths that can appear outside brace macros. -# -# Keep this intentionally narrow. A broad "word/word" matcher treats normal -# player-facing text such as "and/or", "input/output", "RF/t", and "Up/Down" -# as hard format tokens, which causes safe translations to be discarded. Real -# resource paths usually have a namespace, a known asset/config prefix, or a -# filename extension. -_RESOURCE_PATH_PATTERN = re.compile( - r"\b(?:" - r"[a-z0-9_.-]+:[a-z0-9_.-]+(?:/[a-z0-9_.-]+)+(?:\.[a-z0-9]+)?" - r"|(?:assets|config|data|kubejs|models|recipes|textures|ftbquests|chapters|lang|scripts|shaderpacks)" - r"/[a-z0-9_.-]+(?:/[a-z0-9_.-]+)*(?:\.[a-z0-9]+)?" - r"|[a-z0-9_.-]+(?:/[a-z0-9_.-]+)+\.[a-z0-9]+" - r")\b", - re.IGNORECASE, -) - -# Escape sequences \n \r \t \" \' \\ -_ESCAPE_PATTERN = re.compile(r"\\[nrt\"'\\]") - -# URLs -_URL_PATTERN = re.compile(r"https?://[^\s\"')\]]+") - -# Hex colour codes #RRGGBB (not SNBT comments — those start at line begin) -_HEX_PATTERN = re.compile(r"#[0-9a-fA-F]{6}\b") - -# All token patterns in priority order (JSON handled separately first) -_FLAT_PATTERNS: list[re.Pattern[str]] = [ - _COLOR_PATTERN, - _FORMAT_PATTERN, - _ANGLE_PATTERN, - _BRACE_MACRO_PATTERN, - _RESOURCE_PATH_PATTERN, - _ESCAPE_PATTERN, - _URL_PATTERN, - _HEX_PATTERN, -] - -# JSON-like object / array pattern (used for detection only) -_JSON_OBJECT_PATTERN = re.compile(r"^\s*[\[{].*[\]}]\s*$", re.DOTALL) - - -Protection = tuple[str, str] # (placeholder, original_token) - - -def protect_text(text: str) -> tuple[str, list[Protection]]: - """Replace all protected format tokens with numbered placeholders. - - For JSON text components ({...} or [...]), uses structural protection: - only the human-readable text inside ``"text"`` fields is left exposed - for translation; everything else is replaced with placeholders. - - Returns (protected_text, [(placeholder, original_token), ...]). - """ - stripped = text.strip() - if _looks_like_json(stripped): - return _protect_json_text(text) - return _protect_flat_text(text) - - -def restore_text(text: str, protections: list[Protection]) -> str: - """Replace ``{P_N}`` placeholders with their original tokens.""" - result = text - for placeholder, original in protections: - result = result.replace(placeholder, original) - return result - - -def preserved_token_warnings(source: str, translated: str) -> list[str]: - """Verify that all protected tokens survived translation unchanged. - - Runs a full protect → restore round-trip on the source, then makes - sure every original token appears in the same order in the translated - output. This catches cases where the model ignored placeholders. - """ - warnings: list[str] = [] - - # 1. Control characters — strict count - for char, name in (("\n", "newline"), ("\r", "carriage return"), ("\t", "tab")): - if source.count(char) != translated.count(char): - warnings.append(f"Control character count mismatch for {name}") - - # 2. Token round-trip check. Colour/style codes are allowed to move as - # complete segments because Chinese word order often moves highlighted - # phrases. Non-colour tokens still require strict relative order. - _, src_protections = protect_text(source) - tgt_restored = restore_text(translated, src_protections) - src_tokens = _extract_protected_tokens(source) - tgt_tokens = _extract_protected_tokens(tgt_restored) - - src_fixed = [token for token in src_tokens if not _is_movable_style_token(token)] - tgt_fixed = [token for token in tgt_tokens if not _is_movable_style_token(token)] - if len(src_fixed) != len(tgt_fixed): - warnings.append( - f"Non-colour token count mismatch: {len(src_fixed)} source vs " - f"{len(tgt_fixed)} translated", - ) - else: - for source_token, translated_token in zip(src_fixed, tgt_fixed): - if source_token != translated_token: - warnings.append( - f"Non-colour token mismatch: source={source_token!r} translated={translated_token!r}", - ) - - src_style = Counter(token for token in src_tokens if _is_movable_style_token(token)) - tgt_style = Counter(token for token in tgt_tokens if _is_movable_style_token(token)) - if src_style != tgt_style: - warnings.append(f"Colour/style token count mismatch: source={dict(src_style)} translated={dict(tgt_style)}") - elif src_style: - warnings.extend(_colour_ast_warnings(source, tgt_restored)) - - # 3. Strict occurrence check catches dropped duplicate tokens as well as - # completely missing tokens. - missing = Counter(src_tokens) - Counter(tgt_tokens) - for original, count in sorted(missing.items()): - if count == 1: - warnings.append(f"Missing token in translation: {original!r}") - else: - warnings.append(f"Missing token in translation: {original!r} x{count}") - - if warnings: - _log.warning( - "Format token issues in translation (%d): source=%.50r -> target=%.50r", - len(warnings), source, translated, - ) - return warnings - - -def repair_translation_format(source: str, translated: str) -> str: - """Apply conservative local repairs before rejecting a translation. - - This intentionally handles only edits that are mechanically safe. The main - case is an LLM duplicating colour/style codes to "close" a highlighted - phrase, e.g. ``&f与&d军需官的回响&f对话`` when the source only contained one - ``&f`` and one ``&d``. Extra colour codes change styling but do not carry - content or IDs, so removing extras is safer than discarding the whole - translation. Missing or changed required codes are still rejected. - """ - return _remove_extra_style_tokens(source, translated) - - -# --------------------------------------------------------------------------- -# Internal helpers -# --------------------------------------------------------------------------- - -_PLACEHOLDER_COUNTER = 0 -_PLACEHOLDER_LOCK = threading.Lock() - - -def _next_placeholder() -> str: - """Return a distinctive placeholder the model won't translate. - - Uses Unicode angle brackets (U+27E8 / U+27E9) around ``P_N`` so the - model clearly sees an opaque token to preserve verbatim. - """ - global _PLACEHOLDER_COUNTER - with _PLACEHOLDER_LOCK: - ph = f"\u27e8P_{_PLACEHOLDER_COUNTER}\u27e9" - _PLACEHOLDER_COUNTER += 1 - return ph - - -def _reset_counter() -> None: - global _PLACEHOLDER_COUNTER - with _PLACEHOLDER_LOCK: - _PLACEHOLDER_COUNTER = 0 - - -def _looks_like_json(text: str) -> bool: - """Quick heuristic: does the string look like a JSON object or array?""" - return bool(_JSON_OBJECT_PATTERN.match(text)) - - -def _protect_json_text(text: str) -> tuple[str, list[Protection]]: - """Structural protection for JSON text components. - - Parses the JSON and walks every node. Only ``"text"`` field values and - bare string array elements are processed for MC token protection; all - other keys (color, clickEvent, hoverEvent, …) and their values are left - entirely untouched. This guarantees the JSON structure is never altered. - """ - try: - obj = json.loads(text) - except json.JSONDecodeError: - return _protect_flat_text(text) - - protections: list[Protection] = [] - - def _walk(node: object) -> object: - if isinstance(node, dict): - new: dict[str, object] = {} - for key, value in node.items(): - if key == "text" and isinstance(value, str): - ptext, psub = _protect_flat_text(value) - protections.extend(psub) - new[key] = ptext - elif isinstance(value, (dict, list)): - new[key] = _walk(value) - else: - new[key] = value - return new - if isinstance(node, list): - return [_walk(item) for item in node] - if isinstance(node, str): - ptext, psub = _protect_flat_text(node) - protections.extend(psub) - return ptext - return node - - obj = _walk(obj) - result = json.dumps(obj, ensure_ascii=False) - return result, protections - - -def _protect_flat_text(text: str) -> tuple[str, list[Protection]]: - """Flat regex-based protection for plain text strings. - - Every match of every known token pattern is replaced with a - ``\\x00P_N\\x00`` placeholder. - """ - protections: list[Protection] = [] - result = text - for pattern in _FLAT_PATTERNS: - # Work backwards through matches to preserve positions - for match in reversed(list(pattern.finditer(result))): - original = match.group() - ph = _next_placeholder() - protections.insert(0, (ph, original)) - result = result[:match.start()] + ph + result[match.end():] - return result, protections - - -def _extract_protected_tokens(text: str) -> list[str]: - """Extract protected tokens in human-readable text order.""" - stripped = text.strip() - if _looks_like_json(stripped): - try: - obj = json.loads(text) - except json.JSONDecodeError: - return _extract_flat_tokens(text) - - tokens: list[str] = [] - - def _walk(node: object) -> None: - if isinstance(node, dict): - for key, value in node.items(): - if key == "text" and isinstance(value, str): - tokens.extend(_extract_flat_tokens(value)) - elif isinstance(value, (dict, list)): - _walk(value) - elif isinstance(node, list): - for item in node: - if isinstance(item, str): - tokens.extend(_extract_flat_tokens(item)) - else: - _walk(item) - - _walk(obj) - return tokens - return _extract_flat_tokens(text) - - -def _extract_flat_tokens(text: str) -> list[str]: - matches: list[tuple[int, int, int, str]] = [] - for priority, pattern in enumerate(_FLAT_PATTERNS): - for match in pattern.finditer(text): - matches.append((match.start(), match.end(), priority, match.group())) - - tokens: list[str] = [] - occupied: list[tuple[int, int]] = [] - for start, end, _priority, token in sorted(matches, key=lambda item: (item[0], item[2], item[1])): - if any(start < used_end and end > used_start for used_start, used_end in occupied): - continue - occupied.append((start, end)) - tokens.append(token) - return tokens - - -def _is_movable_style_token(token: str) -> bool: - return bool(_COLOR_PATTERN.fullmatch(token)) - - -def _colour_ast_warnings(source: str, translated: str) -> list[str]: - """Validate legacy colour/style codes with a lightweight style AST. - - Counter checks intentionally allow complete styled phrases to move. This - second pass catches sequences that have the same tokens but impossible or - semantically different style structure, such as ``&rtext&c`` or changing - ``&c&l`` into ``&l&c``. - """ - source_spans, source_anomalies = _extract_colour_ast(source) - translated_spans, translated_anomalies = _extract_colour_ast(translated) - warnings: list[str] = [] - - source_span_counts = Counter(source_spans) - translated_span_counts = Counter(translated_spans) - if source_span_counts != translated_span_counts: - warnings.append( - "Colour/style AST mismatch: " - f"source={_format_style_counter(source_span_counts)} " - f"translated={_format_style_counter(translated_span_counts)}", - ) - - extra_anomalies = Counter(translated_anomalies) - Counter(source_anomalies) - if extra_anomalies: - warnings.append( - "Colour/style AST issue: " - f"translated has {_format_anomaly_counter(extra_anomalies)}", - ) - - return warnings - - -def _extract_colour_ast(text: str) -> tuple[list[tuple[str, ...]], list[str]]: - """Return styled text span signatures and syntax anomalies. - - The "AST" here is intentionally small: each node is a styled text span - represented by the active legacy codes that apply to that text. - """ - stripped = text.strip() - if _looks_like_json(stripped): - try: - obj = json.loads(text) - except json.JSONDecodeError: - return _extract_flat_colour_ast(text) - - spans: list[tuple[str, ...]] = [] - anomalies: list[str] = [] - - def _walk(node: object) -> None: - if isinstance(node, dict): - for key, value in node.items(): - if key == "text" and isinstance(value, str): - node_spans, node_anomalies = _extract_flat_colour_ast(value) - spans.extend(node_spans) - anomalies.extend(node_anomalies) - elif isinstance(value, (dict, list)): - _walk(value) - elif isinstance(node, list): - for item in node: - if isinstance(item, str): - node_spans, node_anomalies = _extract_flat_colour_ast(item) - spans.extend(node_spans) - anomalies.extend(node_anomalies) - else: - _walk(item) - - _walk(obj) - return spans, anomalies - return _extract_flat_colour_ast(text) - - -def _extract_flat_colour_ast(text: str) -> tuple[list[tuple[str, ...]], list[str]]: - spans: list[tuple[str, ...]] = [] - anomalies: list[str] = [] - active: list[str] = [] - pos = 0 - last_style_without_text: str | None = None - - for match in _COLOR_PATTERN.finditer(text): - chunk = text[pos:match.start()] - if chunk and active: - spans.append(tuple(active)) - last_style_without_text = None - elif chunk: - last_style_without_text = None - - token = match.group() - code = token[1].lower() - if code == "r": - if not active: - anomalies.append("reset without active style") - active = [] - last_style_without_text = None - elif _is_colour_code(code): - active = [token] - last_style_without_text = token - else: - if token not in active: - active.append(token) - last_style_without_text = token - pos = match.end() - - tail = text[pos:] - if tail and active: - spans.append(tuple(active)) - elif tail: - last_style_without_text = None - - if last_style_without_text is not None: - anomalies.append("style code without styled text") - - return spans, anomalies - - -def _is_colour_code(code: str) -> bool: - return code in "0123456789abcdefz" - - -def _format_style_counter(counter: Counter[tuple[str, ...]]) -> dict[str, int]: - return {"+".join(style): count for style, count in sorted(counter.items())} - - -def _format_anomaly_counter(counter: Counter[str]) -> dict[str, int]: - return {anomaly: count for anomaly, count in sorted(counter.items())} - - -def _remove_extra_style_tokens(source: str, translated: str) -> str: - """Remove colour/style tokens that appear only in the translation. - - The repair is deliberately one-way: it only removes surplus style tokens. - It never invents a missing source token and never edits JSON component - strings, where changing raw text can accidentally alter structure. - """ - if _looks_like_json(source.strip()) or _looks_like_json(translated.strip()): - return translated - - source_style = Counter(match.group() for match in _COLOR_PATTERN.finditer(source)) - target_matches = list(_COLOR_PATTERN.finditer(translated)) - target_style = Counter(match.group() for match in target_matches) - - if not target_matches or source_style == target_style: - return translated - if source_style - target_style: - return translated - - remaining = Counter(source_style) - remove_spans: list[tuple[int, int]] = [] - for match in target_matches: - token = match.group() - if remaining[token] > 0: - remaining[token] -= 1 - else: - remove_spans.append(match.span()) - - if not remove_spans: - return translated - - result = translated - for start, end in reversed(remove_spans): - result = result[:start] + result[end:] - return result diff --git a/ftb_translater/history_db.py b/ftb_translater/history_db.py deleted file mode 100644 index a4ea150..0000000 --- a/ftb_translater/history_db.py +++ /dev/null @@ -1,303 +0,0 @@ -"""翻译历史 SQLite 存储。 - -每次翻译完成后保存一条 run + 对应文件的完整中英映射 + 输出文件原文。 -支持列表查询、删除、ZIP 导出,后续可用于词表挖掘。 -""" -from __future__ import annotations - -import json -import sqlite3 -import zipfile -from contextlib import contextmanager -from dataclasses import dataclass, field -from datetime import datetime -from pathlib import Path, PurePosixPath -from typing import Iterator - -from ftb_translater.logger import get_logger -_log = get_logger(__name__) -DEFAULT_HISTORY_DB_NAME = "history.sqlite3" - - -_SCHEMA = """ -CREATE TABLE IF NOT EXISTS translation_runs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - quests_dir TEXT NOT NULL, - pack_name TEXT, - mode TEXT NOT NULL, - model TEXT NOT NULL, - style TEXT NOT NULL, - base_url TEXT, - total_entries INTEGER NOT NULL, - translated_entries INTEGER NOT NULL, - cache_hits INTEGER NOT NULL, - failed_count INTEGER NOT NULL, - warning_count INTEGER NOT NULL, - created_at TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS translation_files ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - run_id INTEGER NOT NULL, - filename TEXT NOT NULL, - source_hash TEXT, - mapping TEXT NOT NULL, - output_content TEXT NOT NULL, - FOREIGN KEY(run_id) REFERENCES translation_runs(id) ON DELETE CASCADE, - UNIQUE(run_id, filename) -); - -CREATE INDEX IF NOT EXISTS idx_runs_created ON translation_runs(created_at DESC); -CREATE INDEX IF NOT EXISTS idx_files_run ON translation_files(run_id); -""" - - -@dataclass -class FileRecord: - filename: str - mapping: dict[str, dict[str, str]] - output_content: str - source_hash: str = "" - - -@dataclass -class RunSummary: - id: int - pack_name: str - quests_dir: str - mode: str - model: str - style: str - total_entries: int - translated_entries: int - cache_hits: int - failed_count: int - warning_count: int - created_at: str - - -@dataclass -class RunDetail: - summary: RunSummary - files: list[FileRecord] = field(default_factory=list) - - -class HistoryDB: - def __init__(self, path: Path | None = None): - self.path = path or (Path.cwd() / DEFAULT_HISTORY_DB_NAME) - self.path.parent.mkdir(parents=True, exist_ok=True) - self.init_schema() - - @contextmanager - def _connect(self) -> Iterator[sqlite3.Connection]: - conn = sqlite3.connect(self.path) - conn.row_factory = sqlite3.Row - try: - conn.execute("PRAGMA foreign_keys = ON") - yield conn - conn.commit() - except Exception: - conn.rollback() - raise - finally: - conn.close() - - def init_schema(self) -> None: - with self._connect() as conn: - conn.executescript(_SCHEMA) - - def insert_run( - self, - *, - quests_dir: str, - mode: str, - model: str, - style: str, - base_url: str, - total_entries: int, - translated_entries: int, - cache_hits: int, - failed_count: int, - warning_count: int, - files: list[FileRecord], - pack_name: str | None = None, - created_at: str | None = None, - ) -> int: - pack_name = pack_name or _derive_pack_name(quests_dir) - created_at = created_at or datetime.now().isoformat(timespec="seconds") - with self._connect() as conn: - cursor = conn.execute( - """ - INSERT INTO translation_runs( - quests_dir, pack_name, mode, model, style, base_url, - total_entries, translated_entries, cache_hits, - failed_count, warning_count, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - quests_dir, pack_name, mode, model, style, base_url, - total_entries, translated_entries, cache_hits, - failed_count, warning_count, created_at, - ), - ) - run_id = cursor.lastrowid - assert run_id is not None - for record in files: - conn.execute( - """ - INSERT INTO translation_files( - run_id, filename, source_hash, mapping, output_content - ) VALUES (?, ?, ?, ?, ?) - """, - ( - run_id, - record.filename, - record.source_hash, - json.dumps(record.mapping, ensure_ascii=False), - record.output_content, - ), - ) - _log.info("Inserted history run #%d with %d file(s)", run_id, len(files)) - return run_id - - def list_runs(self, limit: int = 100) -> list[RunSummary]: - with self._connect() as conn: - rows = conn.execute( - """ - SELECT id, pack_name, quests_dir, mode, model, style, - total_entries, translated_entries, cache_hits, - failed_count, warning_count, created_at - FROM translation_runs - ORDER BY created_at DESC, id DESC - LIMIT ? - """, - (limit,), - ).fetchall() - return [_row_to_summary(row) for row in rows] - - def get_run(self, run_id: int) -> RunSummary | None: - with self._connect() as conn: - row = conn.execute( - """ - SELECT id, pack_name, quests_dir, mode, model, style, - total_entries, translated_entries, cache_hits, - failed_count, warning_count, created_at - FROM translation_runs WHERE id = ? - """, - (run_id,), - ).fetchone() - return _row_to_summary(row) if row else None - - def get_files(self, run_id: int) -> list[FileRecord]: - with self._connect() as conn: - rows = conn.execute( - """ - SELECT filename, source_hash, mapping, output_content - FROM translation_files WHERE run_id = ? - ORDER BY filename - """, - (run_id,), - ).fetchall() - return [ - FileRecord( - filename=row["filename"], - source_hash=row["source_hash"] or "", - mapping=json.loads(row["mapping"]), - output_content=row["output_content"], - ) - for row in rows - ] - - def delete_run(self, run_id: int) -> None: - with self._connect() as conn: - conn.execute("DELETE FROM translation_files WHERE run_id = ?", (run_id,)) - conn.execute("DELETE FROM translation_runs WHERE id = ?", (run_id,)) - _log.info("Deleted history run #%d", run_id) - - def export_zip(self, run_id: int, dest: Path) -> Path: - summary = self.get_run(run_id) - if summary is None: - raise ValueError(f"Run #{run_id} not found") - files = self.get_files(run_id) - if not files: - raise ValueError(f"Run #{run_id} has no files") - - dest = Path(dest) - dest.parent.mkdir(parents=True, exist_ok=True) - archive_files = [_archive_name(record.filename, summary.mode) for record in files] - manifest = { - "run_id": summary.id, - "pack_name": summary.pack_name, - "quests_dir": summary.quests_dir, - "mode": summary.mode, - "model": summary.model, - "style": summary.style, - "total_entries": summary.total_entries, - "translated_entries": summary.translated_entries, - "failed_count": summary.failed_count, - "warning_count": summary.warning_count, - "created_at": summary.created_at, - "files": archive_files, - } - with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf: - zf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2)) - for record, archive_name in zip(files, archive_files, strict=True): - zf.writestr(archive_name, record.output_content) - _log.info("Exported run #%d to %s", run_id, dest) - return dest - - -def _row_to_summary(row: sqlite3.Row) -> RunSummary: - return RunSummary( - id=row["id"], - pack_name=row["pack_name"] or "", - quests_dir=row["quests_dir"], - mode=row["mode"], - model=row["model"], - style=row["style"], - total_entries=row["total_entries"], - translated_entries=row["translated_entries"], - cache_hits=row["cache_hits"], - failed_count=row["failed_count"], - warning_count=row["warning_count"], - created_at=row["created_at"], - ) - - -def _derive_pack_name(quests_dir: str) -> str: - path = Path(quests_dir) - parts = path.parts - for marker in ("config", "ftbquests"): - if marker in parts: - idx = parts.index(marker) - if idx > 0: - return parts[idx - 1] - return path.name or "unknown" - - -def _archive_name(filename: str, mode: str) -> str: - normalized = filename.replace("\\", "/").strip("/") - if mode == "lang" and normalized == "zh_cn.snbt": - normalized = "lang/zh_cn.snbt" - - parts = PurePosixPath(normalized).parts - if ( - not normalized - or PurePosixPath(normalized).is_absolute() - or ".." in parts - or (parts and parts[0].endswith(":")) - or normalized.startswith("/") - ): - raise ValueError(f"Unsafe history export path: {filename!r}") - - if mode == "lang": - allowed = normalized == "lang/zh_cn.snbt" - else: - allowed = ( - normalized.startswith("chapters/") - and len(parts) == 2 - and parts[1].endswith(".snbt") - ) - if not allowed: - raise ValueError(f"Unexpected history export path for {mode} mode: {filename!r}") - return normalized diff --git a/ftb_translater/logger.py b/ftb_translater/logger.py deleted file mode 100644 index 26dc2d3..0000000 --- a/ftb_translater/logger.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import annotations - -import logging -import logging.handlers -from pathlib import Path - -from ftb_translater.user_paths import user_config_dir - - -LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s" -DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - -_initialized = False - - -def setup_logging(log_dir: Path | None = None, level: int = logging.DEBUG) -> Path: - """Configure file + console logging. Call once at startup. Returns the log file path.""" - global _initialized - if _initialized: - return _get_log_path(log_dir) - - log_path = _get_log_path(log_dir) - log_path.parent.mkdir(parents=True, exist_ok=True) - - root = logging.getLogger() - root.setLevel(level) - - # Rotating file handler — keep 5 files of 1 MB each - file_handler = logging.handlers.RotatingFileHandler( - log_path, - maxBytes=1024 * 1024, - backupCount=5, - encoding="utf-8", - ) - file_handler.setLevel(logging.DEBUG) - file_handler.setFormatter(logging.Formatter(LOG_FORMAT, DATE_FORMAT)) - root.addHandler(file_handler) - - # Console handler shows INFO and above - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.INFO) - console_handler.setFormatter(logging.Formatter(LOG_FORMAT, DATE_FORMAT)) - root.addHandler(console_handler) - - _initialized = True - logging.getLogger(__name__).debug("Logging initialised. Log file: %s", log_path) - return log_path - - -def get_logger(name: str) -> logging.Logger: - return logging.getLogger(name) - - -def _get_log_path(log_dir: Path | None) -> Path: - base = log_dir or user_config_dir() / "logs" - return base / "ftb_translater.log" diff --git a/ftb_translater/paths.py b/ftb_translater/paths.py deleted file mode 100644 index 8ffb283..0000000 --- a/ftb_translater/paths.py +++ /dev/null @@ -1,104 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from ftb_translater.logger import get_logger - -_log = get_logger(__name__) - -MAX_SEARCH_DEPTH = 5 - - -def has_lang_source(quests_dir: Path) -> bool: - return (quests_dir / "lang" / "en_us.snbt").is_file() - - -def has_chapters_source(quests_dir: Path) -> bool: - chapters_dir = quests_dir / "chapters" - return chapters_dir.is_dir() and any(chapters_dir.glob("*.snbt")) - - -def resolve_quests_dir(selected_dir: Path) -> Path: - selected_dir = selected_dir.expanduser().resolve() - _log.debug("resolve_quests_dir: starting from %s", selected_dir) - candidates = _candidate_quests_dirs(selected_dir) - _log.debug("Trying %d candidate directories", len(candidates)) - for candidate in candidates: - if has_lang_source(candidate): - _log.info("Found lang source at: %s", candidate) - return candidate - if has_chapters_source(candidate): - _log.info("Found chapters source at: %s", candidate) - return candidate - _log.error("No FTB quests directory found under %s. Tried: %s", selected_dir, candidates) - raise FileNotFoundError( - "Could not find FTB Quests lang/en_us.snbt or chapters/*.snbt. " - "Select a modpack root, config folder, ftbquests folder, quests folder, lang folder, or chapters folder." - ) - - -def detect_source_mode(quests_dir: Path) -> str: - if has_lang_source(quests_dir): - _log.debug("Source mode: lang (%s)", quests_dir) - return "lang" - if has_chapters_source(quests_dir): - _log.debug("Source mode: chapters (%s)", quests_dir) - return "chapters" - _log.error("detect_source_mode: no lang or chapters source found at %s", quests_dir) - raise FileNotFoundError("Could not find lang/en_us.snbt or chapters/*.snbt.") - - -def source_lang_path(quests_dir: Path) -> Path: - return quests_dir / "lang" / "en_us.snbt" - - -def target_lang_path(quests_dir: Path) -> Path: - return quests_dir / "lang" / "zh_cn.snbt" - - -def _candidate_quests_dirs(selected_dir: Path) -> list[Path]: - candidates: list[Path] = [] - - def add(path: Path) -> None: - resolved = path.resolve() - if resolved not in candidates: - candidates.append(resolved) - - add(selected_dir) - if selected_dir.name.lower() in {"chapters", "lang"}: - add(selected_dir.parent) - - for parent in [selected_dir, *selected_dir.parents]: - name = parent.name.lower() - if name == "quests": - add(parent) - if name == "ftbquests": - add(parent / "quests") - if name == "config": - add(parent / "ftbquests" / "quests") - - direct_patterns = [ - selected_dir / "config" / "ftbquests" / "quests", - selected_dir / "ftbquests" / "quests", - selected_dir / "quests", - ] - for pattern in direct_patterns: - add(pattern) - - if selected_dir.is_dir(): - for path in selected_dir.rglob("ftbquests"): - if _relative_depth(selected_dir, path) > MAX_SEARCH_DEPTH: - continue - add(path / "quests") - for path in selected_dir.rglob("quests"): - if _relative_depth(selected_dir, path) > MAX_SEARCH_DEPTH: - continue - add(path) - return candidates - - -def _relative_depth(root: Path, child: Path) -> int: - try: - return len(child.relative_to(root).parts) - except ValueError: - return MAX_SEARCH_DEPTH + 1 diff --git a/ftb_translater/report.py b/ftb_translater/report.py deleted file mode 100644 index 40dcf0c..0000000 --- a/ftb_translater/report.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import annotations - -import json -from dataclasses import dataclass, field -from pathlib import Path -from typing import Any - - -@dataclass -class TranslationReport: - source_file: str - target_file: str - backup_dir: str - total_entries: int - translated_entries: int - cache_hits: int - failed_entries: list[str] = field(default_factory=list) - warnings: dict[str, list[str]] = field(default_factory=dict) - failed_translations: dict[str, dict[str, str]] = field(default_factory=dict) - # Stored for in-app history. output_files intentionally stays out of report-latest.json - # because it can duplicate every translated chapter file. - mapping: dict[str, dict[str, dict[str, str]]] = field(default_factory=dict) - output_files: dict[str, str] = field(default_factory=dict) - - def save(self, quests_dir: Path) -> Path: - path = quests_dir / ".ftb-translater" / "report-latest.json" - path.parent.mkdir(parents=True, exist_ok=True) - with path.open("w", encoding="utf-8") as file: - json.dump(self.to_dict(), file, ensure_ascii=False, indent=2) - return path - - def to_dict(self) -> dict[str, Any]: - return { - "source_file": self.source_file, - "target_file": self.target_file, - "backup_dir": self.backup_dir, - "total_entries": self.total_entries, - "translated_entries": self.translated_entries, - "cache_hits": self.cache_hits, - "failed_entries": self.failed_entries, - "warnings": self.warnings, - "failed_translations": self.failed_translations, - "mapping": self.mapping, - } diff --git a/ftb_translater/snbt.py b/ftb_translater/snbt.py deleted file mode 100644 index cb4c8b3..0000000 --- a/ftb_translater/snbt.py +++ /dev/null @@ -1,209 +0,0 @@ -from __future__ import annotations - -from collections import OrderedDict -from pathlib import Path -from typing import TypeAlias - -from ftb_translater.logger import get_logger - -LangValue: TypeAlias = "str | list[str]" -LangMap: TypeAlias = "OrderedDict[str, LangValue]" - -_log = get_logger(__name__) - - -class SnbtParseError(ValueError): - pass - - -class _Parser: - def __init__(self, text: str): - self.text = text - self.index = 0 - - def parse(self) -> LangMap: - self._skip_ws_and_comments() - self._expect("{") - values: LangMap = OrderedDict() - while True: - self._skip_ws_and_comments() - if self._peek() == "}": - self.index += 1 - break - key = self._parse_key() - self._skip_ws_and_comments() - self._expect(":") - self._skip_ws_and_comments() - value = self._parse_value() - values[key] = value - self._skip_ws_and_comments() - if self._peek() == ",": - self.index += 1 - continue - if self._peek() == "}": - continue - if self._peek(): - continue - raise self._error("Expected key or '}'") - self._skip_ws_and_comments() - if self.index != len(self.text): - raise self._error("Unexpected trailing content") - return values - - def _parse_value(self) -> LangValue: - if self._peek() in {'"', "'"}: - return self._parse_string() - if self._peek() == "[": - return self._parse_string_list() - raise self._error("Expected quoted string or string list value") - - def _parse_key(self) -> str: - if self._peek() in {'"', "'"}: - return self._parse_string() - start = self.index - while self.index < len(self.text) and self.text[self.index] not in ":\r\n\t ": - self.index += 1 - key = self.text[start:self.index].strip() - if not key: - raise self._error("Expected key") - return key - - def _parse_string(self) -> str: - quote = self._peek() - if quote not in {'"', "'"}: - raise self._error("Expected quoted string value") - self.index += 1 - chars: list[str] = [] - while self.index < len(self.text): - char = self.text[self.index] - self.index += 1 - if char == quote: - return "".join(chars) - if char == "\\": - if self.index >= len(self.text): - raise self._error("Unfinished escape sequence") - esc = self.text[self.index] - self.index += 1 - chars.append(_decode_escape(esc)) - else: - chars.append(char) - raise self._error("Unterminated string") - - def _parse_string_list(self) -> list[str]: - self._expect("[") - values: list[str] = [] - while True: - self._skip_ws_and_comments() - if self._peek() == "]": - self.index += 1 - return values - values.append(self._parse_string()) - self._skip_ws_and_comments() - if self._peek() == ",": - self.index += 1 - continue - if self._peek() == "]": - continue - if self._peek(): - continue - raise self._error("Expected string or ']'") - - def _skip_ws_and_comments(self) -> None: - while self.index < len(self.text): - char = self.text[self.index] - if char.isspace(): - self.index += 1 - continue - if self.text.startswith("//", self.index): - self.index = self.text.find("\n", self.index) - if self.index == -1: - self.index = len(self.text) - continue - if char == "#": - self.index = self.text.find("\n", self.index) - if self.index == -1: - self.index = len(self.text) - continue - break - - def _expect(self, char: str) -> None: - if self._peek() != char: - raise self._error(f"Expected '{char}'") - self.index += 1 - - def _peek(self) -> str: - if self.index >= len(self.text): - return "" - return self.text[self.index] - - def _error(self, message: str) -> SnbtParseError: - return SnbtParseError(f"{message} at offset {self.index}") - - -def parse_lang_snbt(text: str) -> LangMap: - return _Parser(text).parse() - - -def load_lang_snbt(path: Path) -> LangMap: - _log.debug("Loading lang SNBT: %s", path) - try: - result = parse_lang_snbt(path.read_text(encoding="utf-8-sig")) - _log.debug("Loaded %d entries from %s", len(result), path) - return result - except OSError as exc: - _log.error("Could not read %s: %s", path, exc) - raise SnbtParseError(f"Could not read {path}: {exc}") from exc - except SnbtParseError as exc: - _log.error("Parse error in %s: %s", path, exc) - raise - - -def dump_lang_snbt(values: LangMap | dict[str, LangValue]) -> str: - lines = ["{"] - items = list(values.items()) - for index, (key, value) in enumerate(items): - suffix = "," if index < len(items) - 1 else "" - if isinstance(value, list): - lines.append(f' "{_escape(key)}": [') - for item_index, item in enumerate(value): - item_suffix = "," if item_index < len(value) - 1 else "" - lines.append(f' "{_escape(item)}"{item_suffix}') - lines.append(f" ]{suffix}") - else: - lines.append(f' "{_escape(key)}": "{_escape(value)}"{suffix}') - lines.append("}") - return "\n".join(lines) + "\n" - - -def write_lang_snbt(path: Path, values: LangMap | dict[str, LangValue]) -> None: - _log.debug("Writing lang SNBT: %s (%d entries)", path, len(values)) - text = dump_lang_snbt(values) - parsed = parse_lang_snbt(text) - if list(parsed.keys()) != list(values.keys()): - _log.error("SNBT round-trip key mismatch when writing %s", path) - raise SnbtParseError("Written SNBT key set did not validate.") - path.write_text(text, encoding="utf-8") - _log.debug("Wrote %s OK", path) - - -def _decode_escape(esc: str) -> str: - mapping = { - "n": "\n", - "r": "\r", - "t": "\t", - "\\": "\\", - '"': '"', - "'": "'", - } - return mapping.get(esc, esc) - - -def _escape(value: str) -> str: - return ( - str(value) - .replace("\\", "\\\\") - .replace('"', '\\"') - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t") - ) diff --git a/ftb_translater/translator.py b/ftb_translater/translator.py deleted file mode 100644 index 90a2b57..0000000 --- a/ftb_translater/translator.py +++ /dev/null @@ -1,582 +0,0 @@ -from __future__ import annotations - -from concurrent.futures import ThreadPoolExecutor, as_completed -from collections import OrderedDict -import json -from collections.abc import Callable, Mapping -import os -from pathlib import Path -import threading -from dataclasses import dataclass -from typing import Protocol, cast - -from ftb_translater.backup import create_backup -from ftb_translater.cache import TranslationCache -from ftb_translater.chapters import chapter_files, extract_chapter_segments, replace_chapter_segments -from ftb_translater.deepseek_client import DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_STYLE, DeepSeekTranslator -from ftb_translater.format_guard import preserved_token_warnings, protect_text, repair_translation_format, restore_text -from ftb_translater.logger import get_logger -from ftb_translater.paths import detect_source_mode, source_lang_path, target_lang_path -from ftb_translater.report import TranslationReport -from ftb_translater.snbt import LangValue, load_lang_snbt, write_lang_snbt - -_log = get_logger(__name__) - - -ProgressCallback = Callable[[str, int, int], None] -LogCallback = Callable[[str], None] -AUTO_BATCH_MAX_ENTRIES = 25 -AUTO_BATCH_MAX_CHARS = 6000 -AUTO_MAX_WORKERS = 6 -MAX_WORKERS_ENV = "FTB_TRANSLATER_CONCURRENCY" - - -class TranslatorClient(Protocol): - def translate_batch(self, entries: Mapping[str, str], style: str) -> dict[str, str]: ... - - -@dataclass(frozen=True) -class _BatchResult: - batch_index: int - batch: OrderedDict[str, str] - result: dict[str, str] - error: Exception | None = None - - -def estimate_batches(total_entries: int, batch_size: int) -> int: - if batch_size <= 0: - raise ValueError("Batch size must be greater than zero.") - return (total_entries + batch_size - 1) // batch_size - - -def build_translation_batches( - entries: Mapping[str, str], - batch_size: int | None = None, - max_chars: int = AUTO_BATCH_MAX_CHARS, -) -> list[OrderedDict[str, str]]: - if batch_size is not None and batch_size <= 0: - raise ValueError("Batch size must be greater than zero.") - max_entries = batch_size or AUTO_BATCH_MAX_ENTRIES - batches: list[OrderedDict[str, str]] = [] - current: OrderedDict[str, str] = OrderedDict() - current_chars = 0 - - for key, value in entries.items(): - estimated_chars = len(json.dumps({key: value}, ensure_ascii=False)) - if current and (len(current) >= max_entries or current_chars + estimated_chars > max_chars): - batches.append(current) - current = OrderedDict() - current_chars = 0 - current[key] = value - current_chars += estimated_chars - - if current: - batches.append(current) - return batches - - -def translate_quests_lang( - quests_dir: Path, - api_key: str, - batch_size: int | None = None, - model: str = DEFAULT_MODEL, - style: str = DEFAULT_STYLE, - progress: ProgressCallback | None = None, - logger: LogCallback | None = None, - translator: TranslatorClient | None = None, - max_workers: int | None = None, - base_url: str = DEFAULT_BASE_URL, -) -> TranslationReport: - if batch_size is not None and batch_size <= 0: - raise ValueError("Batch size must be greater than zero.") - - source_path = source_lang_path(quests_dir) - target_path = target_lang_path(quests_dir) - _log.info("translate_quests_lang: quests_dir=%s source=%s", quests_dir, source_path) - - source_values = load_lang_snbt(source_path) - _log.debug("Loaded source lang: %d entries", len(source_values)) - - cache = TranslationCache(quests_dir / ".ftb-translater" / "cache.json") - cache.load() - - translated_values: OrderedDict[str, LangValue] = OrderedDict() - pending: OrderedDict[str, str] = OrderedDict() - pending_sources: OrderedDict[str, str] = OrderedDict() - protections_by_key: dict[str, list[tuple[str, str]]] = {} - cache_hits = 0 - warnings: dict[str, list[str]] = {} - failed_translations: dict[str, dict[str, str]] = {} - - for key, value in source_values.items(): - source_text = _lang_value_to_text(value) - cached = cache.get(source_text, model, "zh_cn", style) - if cached is not None: - translated_values[key] = _text_to_lang_value(cached, value) - cache_hits += 1 - else: - protected_text, protections = protect_text(source_text) - pending[key] = protected_text - pending_sources[key] = source_text - protections_by_key[key] = protections - - _log.info("Cache hits: %d, pending translation: %d", cache_hits, len(pending)) - - total_pending = len(pending) - failed_entries: list[str] = [] - batches = build_translation_batches(pending, batch_size=batch_size) - _log.info("Built %d batches for %d pending entries", len(batches), total_pending) - - batch_results = _translate_batches( - batches=batches, - api_key=api_key, - model=model, - style=style, - base_url=base_url, - logger=logger, - progress=progress, - progress_total=total_pending, - label="lang entries", - translator=translator, - max_workers=max_workers, - ) - for batch_result in sorted(batch_results, key=lambda item: item.batch_index): - batch = batch_result.batch - result = batch_result.result - if batch_result.error is not None: - for key in batch: - error_text = f"API batch failed: {batch_result.error}" - failed_entries.append(f"{key}: {batch_result.error}") - warnings[key] = [error_text] - failed_translations[key] = {"source": pending_sources[key], "failed": "", "error": error_text} - for key, protected_text in batch.items(): - source_text = pending_sources[key] - translated_text = result.get(key, protected_text) - model_raw = translated_text - translated_text, token_warnings = _guard_translation(source_text, translated_text, protections_by_key[key]) - translated_values[key] = _text_to_lang_value(translated_text, source_values[key]) - if translated_text != source_text: - cache.set(source_text, model, "zh_cn", style, translated_text) - if token_warnings: - _log.warning("Format token mismatch for key %r: %s", key, token_warnings) - warnings[key] = token_warnings - failed_translations[key] = {"source": source_text, "failed": restore_text(model_raw, protections_by_key[key])} - - ordered_output: OrderedDict[str, LangValue] = OrderedDict() - for key in source_values: - ordered_output[key] = translated_values[key] - - msg = "Creating backup for lang directory before overwrite." - _log.info(msg) - if logger: - logger(msg) - backup_dir = create_backup(quests_dir, directories=("lang",)) - _log.info("Backup created at: %s", backup_dir) - - msg = f"Overwriting target lang file: {target_path}" - _log.info(msg) - if logger: - logger(msg) - write_lang_snbt(target_path, ordered_output) - - parsed_target = load_lang_snbt(target_path) - if list(parsed_target.keys()) != list(source_values.keys()): - _log.error( - "Key mismatch after write! source keys=%d written keys=%d", - len(source_values), len(parsed_target), - ) - raise ValueError("Written zh_cn.snbt does not contain the same keys as en_us.snbt.") - - cache.save() - _log.info( - "Lang translation done: total=%d translated=%d cache_hits=%d failed=%d warnings=%d", - len(source_values), len(source_values) - len(failed_entries), - cache_hits, len(failed_entries), len(warnings), - ) - if failed_entries: - _log.warning("Failed entries:\n%s", "\n".join(failed_entries)) - - mapping_data: dict[str, dict[str, str]] = {} - for key in source_values: - src_text = _lang_value_to_text(source_values[key]) - tgt_text = _lang_value_to_text(translated_values[key]) - mapping_data[key] = {"en": src_text, "zh": tgt_text} - # 用 lang/zh_cn.snbt 而不是 zh_cn.snbt,这样导出的 ZIP 解压后能直接覆盖整合包目录 - relative_target = f"lang/{target_path.name}" - - report = TranslationReport( - source_file=str(source_path), - target_file=str(target_path), - backup_dir=str(backup_dir), - total_entries=len(source_values), - translated_entries=len(source_values) - len(failed_entries), - cache_hits=cache_hits, - failed_entries=failed_entries, - warnings=warnings, - failed_translations=failed_translations, - mapping={relative_target: mapping_data}, - output_files={relative_target: target_path.read_text(encoding="utf-8")}, - ) - report.save(quests_dir) - if progress: - progress("done", total_pending, total_pending) - return report - - -def translate_quests_chapters( - quests_dir: Path, - api_key: str, - batch_size: int | None = None, - model: str = DEFAULT_MODEL, - style: str = DEFAULT_STYLE, - progress: ProgressCallback | None = None, - logger: LogCallback | None = None, - translator: TranslatorClient | None = None, - max_workers: int | None = None, - base_url: str = DEFAULT_BASE_URL, -) -> TranslationReport: - if batch_size is not None and batch_size <= 0: - raise ValueError("Batch size must be greater than zero.") - - files = chapter_files(quests_dir) - if not files: - _log.error("No chapter SNBT files found under %s", quests_dir / "chapters") - raise FileNotFoundError(f"No chapter SNBT files found under {quests_dir / 'chapters'}") - - _log.info("translate_quests_chapters: quests_dir=%s, files=%d", quests_dir, len(files)) - - cache = TranslationCache(quests_dir / ".ftb-translater" / "cache.json") - cache.load() - segments_by_file = {path: extract_chapter_segments(path) for path in files} - all_segments = [segment for segments in segments_by_file.values() for segment in segments] - _log.debug("Extracted %d total text segments from %d chapter files", len(all_segments), len(files)) - - pending: OrderedDict[str, str] = OrderedDict() - pending_sources: OrderedDict[str, str] = OrderedDict() - protections_by_key: dict[str, list[tuple[str, str]]] = {} - translations_by_file: dict[Path, dict[int, str]] = {path: {} for path in files} - cache_hits = 0 - warnings: dict[str, list[str]] = {} - failed_translations: dict[str, dict[str, str]] = {} - - for segment in all_segments: - cached = cache.get(segment.source_text, model, "zh_cn", style) - if cached is not None: - translations_by_file[segment.path][segment.index] = cached - cache_hits += 1 - else: - protected_text, protections = protect_text(segment.source_text) - pending[segment.cache_id] = protected_text - pending_sources[segment.cache_id] = segment.source_text - protections_by_key[segment.cache_id] = protections - - _log.info("Cache hits: %d, pending translation: %d", cache_hits, len(pending)) - - segment_by_id = {segment.cache_id: segment for segment in all_segments} - batches = build_translation_batches(pending, batch_size=batch_size) - _log.info("Built %d batches for %d pending segments", len(batches), len(pending)) - failed_entries: list[str] = [] - - batch_results = _translate_batches( - batches=batches, - api_key=api_key, - model=model, - style=style, - base_url=base_url, - logger=logger, - progress=progress, - progress_total=len(pending), - label="chapter text entries", - translator=translator, - max_workers=max_workers, - ) - for batch_result in sorted(batch_results, key=lambda item: item.batch_index): - batch = batch_result.batch - result = batch_result.result - if batch_result.error is not None: - for key in batch: - error_text = f"API batch failed: {batch_result.error}" - failed_entries.append(f"{key}: {batch_result.error}") - warnings[key] = [error_text] - failed_translations[key] = {"source": pending_sources[key], "failed": "", "error": error_text} - for cache_id, protected_text in batch.items(): - segment = segment_by_id[cache_id] - source_text = pending_sources[cache_id] - translated_text = result.get(cache_id, protected_text) - model_raw = translated_text - translated_text, token_warnings = _guard_translation(source_text, translated_text, protections_by_key[cache_id]) - translations_by_file[segment.path][segment.index] = translated_text - if translated_text != source_text: - cache.set(source_text, model, "zh_cn", style, translated_text) - if token_warnings: - _log.warning("Format token mismatch for segment %r: %s", cache_id, token_warnings) - warnings[cache_id] = token_warnings - failed_translations[cache_id] = {"source": source_text, "failed": restore_text(model_raw, protections_by_key[cache_id])} - - msg = "Creating backup for chapters directory before overwrite." - _log.info(msg) - if logger: - logger(msg) - backup_dir = create_backup(quests_dir, directories=("chapters",)) - _log.info("Backup created at: %s", backup_dir) - - replaced_count = 0 - for path, replacements in translations_by_file.items(): - if replacements: - msg = f"Overwriting chapter file: {path} ({len(replacements)} text segments)." - _log.info(msg) - if logger: - logger(msg) - replaced_count += replace_chapter_segments(path, replacements) - - cache.save() - _log.info( - "Chapters translation done: total=%d replaced=%d cache_hits=%d failed=%d warnings=%d", - len(all_segments), replaced_count, cache_hits, len(failed_entries), len(warnings), - ) - if failed_entries: - _log.warning("Failed entries:\n%s", "\n".join(failed_entries)) - - mapping_by_file: dict[str, dict[str, dict[str, str]]] = {} - output_by_file: dict[str, str] = {} - for path, replacements in translations_by_file.items(): - rel_name = f"chapters/{path.name}" - file_map: dict[str, dict[str, str]] = {} - for segment in segments_by_file[path]: - translated = replacements.get(segment.index, segment.source_text) - file_map[segment.cache_id] = {"en": segment.source_text, "zh": translated} - mapping_by_file[rel_name] = file_map - try: - output_by_file[rel_name] = path.read_text(encoding="utf-8") - except OSError as exc: - _log.warning("Could not read chapter file %s for history: %s", path, exc) - - report = TranslationReport( - source_file=str(quests_dir / "chapters"), - target_file=str(quests_dir / "chapters"), - backup_dir=str(backup_dir), - total_entries=len(all_segments), - translated_entries=replaced_count - len(failed_entries), - cache_hits=cache_hits, - failed_entries=failed_entries, - warnings=warnings, - failed_translations=failed_translations, - mapping=mapping_by_file, - output_files=output_by_file, - ) - report.save(quests_dir) - if progress: - progress("done", len(pending), len(pending)) - return report - - -def translate_quests_auto( - quests_dir: Path, - api_key: str, - batch_size: int | None = None, - model: str = DEFAULT_MODEL, - style: str = DEFAULT_STYLE, - progress: ProgressCallback | None = None, - logger: LogCallback | None = None, - translator: TranslatorClient | None = None, - max_workers: int | None = None, - base_url: str = DEFAULT_BASE_URL, -) -> TranslationReport: - mode = detect_source_mode(quests_dir) - _log.info("translate_quests_auto: mode=%s quests_dir=%s", mode, quests_dir) - if mode == "lang": - return translate_quests_lang( - quests_dir=quests_dir, - api_key=api_key, - batch_size=batch_size, - model=model, - style=style, - base_url=base_url, - progress=progress, - logger=logger, - translator=translator, - max_workers=max_workers, - ) - return translate_quests_chapters( - quests_dir=quests_dir, - api_key=api_key, - batch_size=batch_size, - model=model, - style=style, - base_url=base_url, - progress=progress, - logger=logger, - translator=translator, - max_workers=max_workers, - ) - - -def _translate_batches( - batches: list[OrderedDict[str, str]], - api_key: str, - model: str, - style: str, - base_url: str, - logger: LogCallback | None, - progress: ProgressCallback | None, - progress_total: int, - label: str, - translator: TranslatorClient | None, - max_workers: int | None, -) -> list[_BatchResult]: - worker_count = _resolve_max_workers(max_workers, len(batches), progress_total) - if not batches: - if progress: - progress("done", 0, progress_total) - return [] - - if logger: - logger(f"DeepSeek concurrency: {worker_count} worker(s), {len(batches)} batches.") - _log.info("DeepSeek concurrency: %d worker(s), %d batches", worker_count, len(batches)) - - completed_entries = 0 - results: list[_BatchResult] = [] - - if worker_count == 1: - for batch_index, batch in enumerate(batches, start=1): - if progress: - progress("translating", completed_entries, progress_total) - batch_result = _translate_one_batch( - batch_index=batch_index, - batch_count=len(batches), - batch=batch, - api_key=api_key, - model=model, - style=style, - base_url=base_url, - logger=logger, - label=label, - translator=translator, - ) - results.append(batch_result) - completed_entries += len(batch) - if progress: - progress("translating", completed_entries, progress_total) - return results - - thread_state = threading.local() - - def worker(batch_index: int, batch: OrderedDict[str, str]) -> _BatchResult: - worker_translator = translator - if worker_translator is None: - worker_translator = cast(TranslatorClient | None, getattr(thread_state, "translator", None)) - if worker_translator is None: - worker_translator = DeepSeekTranslator(api_key=api_key, model=model, base_url=base_url, logger=logger) - thread_state.translator = worker_translator - return _translate_one_batch( - batch_index=batch_index, - batch_count=len(batches), - batch=batch, - api_key=api_key, - model=model, - style=style, - base_url=base_url, - logger=logger, - label=label, - translator=worker_translator, - ) - - with ThreadPoolExecutor(max_workers=worker_count) as executor: - futures = { - executor.submit(worker, batch_index, batch): batch - for batch_index, batch in enumerate(batches, start=1) - } - for future in as_completed(futures): - batch_result = future.result() - results.append(batch_result) - completed_entries += len(batch_result.batch) - if progress: - progress("translating", completed_entries, progress_total) - return results - - -def _translate_one_batch( - batch_index: int, - batch_count: int, - batch: OrderedDict[str, str], - api_key: str, - model: str, - style: str, - base_url: str, - logger: LogCallback | None, - label: str, - translator: TranslatorClient | None, -) -> _BatchResult: - client = translator or DeepSeekTranslator(api_key=api_key, model=model, base_url=base_url, logger=logger) - msg = f"DeepSeek batch {batch_index}/{batch_count}: {len(batch)} {label}." - _log.info(msg) - if logger: - logger(msg) - try: - return _BatchResult(batch_index=batch_index, batch=batch, result=client.translate_batch(batch, style=style)) - except Exception as exc: # noqa: BLE001 - msg = f"Batch {batch_index} failed, preserving source text for this batch: {exc}" - _log.error(msg) - if logger: - logger(msg) - return _BatchResult(batch_index=batch_index, batch=batch, result=dict(batch), error=exc) - - -def _resolve_max_workers(max_workers: int | None, batch_count: int, entry_count: int = 0) -> int: - if batch_count <= 0: - return 1 - value = max_workers - if value is None: - raw_value = os.getenv(MAX_WORKERS_ENV) - if raw_value: - if raw_value.strip().lower() == "auto": - return _auto_max_workers(batch_count, entry_count) - try: - value = int(raw_value) - except ValueError: - _log.warning("%s must be an integer or 'auto', falling back to automatic concurrency", MAX_WORKERS_ENV) - return _auto_max_workers(batch_count, entry_count) - else: - return _auto_max_workers(batch_count, entry_count) - if value <= 0: - raise ValueError("max_workers must be greater than zero.") - return min(value, batch_count) - - -def _auto_max_workers(batch_count: int, entry_count: int) -> int: - if batch_count <= 1: - return 1 - if entry_count <= 25: - return min(2, batch_count) - if entry_count <= 150: - return min(3, batch_count) - if entry_count <= 800: - return min(4, batch_count) - return min(AUTO_MAX_WORKERS, batch_count) - - -def _guard_translation( - source_text: str, - translated_text: str, - protections: list[tuple[str, str]], -) -> tuple[str, list[str]]: - # Restore the exact tokens that were removed before sending text to the model. - restored = restore_text(translated_text, protections) - restored = repair_translation_format(source_text, restored) - - token_warnings = preserved_token_warnings(source_text, restored) - if token_warnings: - return source_text, [*token_warnings, "Unsafe translation discarded; source text preserved."] - return restored, [] - - -def _lang_value_to_text(value: LangValue) -> str: - if isinstance(value, list): - return "\n".join(value) - return value - - -def _text_to_lang_value(text: str, template: LangValue) -> LangValue: - if isinstance(template, list): - return text.split("\n") - return text diff --git a/ftb_translater/user_paths.py b/ftb_translater/user_paths.py deleted file mode 100644 index 933c140..0000000 --- a/ftb_translater/user_paths.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path - - -APP_DIR_NAME = "FTB-Translater" - - -def user_config_dir() -> Path: - override = os.getenv("FTB_TRANSLATER_CONFIG_DIR") - if override: - return Path(override).expanduser() - - if os.name == "nt": - base = os.getenv("LOCALAPPDATA") or os.getenv("APPDATA") - if base: - return Path(base) / APP_DIR_NAME - - xdg_config_home = os.getenv("XDG_CONFIG_HOME") - if xdg_config_home: - return Path(xdg_config_home) / "ftb-translater" - - return Path.home() / ".config" / "ftb-translater" diff --git a/index.html b/index.html new file mode 100644 index 0000000..4360098 --- /dev/null +++ b/index.html @@ -0,0 +1,5 @@ + + + FTB Translater +
+ diff --git a/main.py b/main.py deleted file mode 100644 index c3f4652..0000000 --- a/main.py +++ /dev/null @@ -1,5 +0,0 @@ -from ftb_translater.app import main - - -if __name__ == "__main__": - main() diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3202ee7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2072 @@ +{ + "name": "ftb-translater-desktop", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ftb-translater-desktop", + "version": "0.2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0", + "@tauri-apps/plugin-dialog": "^2.4.0", + "lucide-react": "^0.468.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.8.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "typescript": "^5.7.0", + "vite": "^6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.1.tgz", + "integrity": "sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", + "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e7a709 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "ftb-translater-desktop", + "private": true, + "version": "0.2.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "^2.8.0", + "@tauri-apps/plugin-dialog": "^2.4.0", + "lucide-react": "^0.468.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.8.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "typescript": "^5.7.0", + "vite": "^6.0.0" + } +} diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 86efd36..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,21 +0,0 @@ -[project] -name = "ftb-translater" -version = "0.1.1" -requires-python = ">=3.11" -dependencies = [ - "cryptography>=49.0.0", - "customtkinter>=5.2.2", - "keyring>=25.7.0", - "openai>=1.0.0", - "python-dotenv>=1.0.0", -] - -[project.scripts] -ftb-translater = "ftb_translater.app:main" -ftb-test = "tests.run_groups:main" -ftb-test-e2e = "tests.run_groups:main" - -[dependency-groups] -dev = [ - "pyinstaller>=6.21.0", -] diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..f315ed2 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,5137 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "dbus", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.118", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.2+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "ftb-translater" +version = "0.2.0" +dependencies = [ + "chrono", + "futures", + "hex", + "keyring", + "regex", + "reqwest 0.12.28", + "rusqlite", + "serde", + "serde_json", + "sha2", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tempfile", + "tokio", + "walkdir", + "zip", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "log", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zeroize", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.0", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.12+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "regex" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.13.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.118", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.0", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.0", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest 0.13.4", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.118", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.2+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.118", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.14.0", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..66ec2de --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "ftb-translater" +version = "0.2.0" +description = "FTB Quests translation workbench" +authors = ["FTB Translater contributors"] +edition = "2021" + +[lib] +name = "ftb_translater_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tauri = { version = "2", features = [] } +tauri-plugin-dialog = "2" +chrono = "0.4" +futures = "0.3" +hex = "0.4" +keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] } +regex = "1" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +rusqlite = { version = "0.32", features = ["bundled"] } +sha2 = "0.10" +tokio = { version = "1", features = ["rt-multi-thread", "time"] } +walkdir = "2" +zip = { version = "2", default-features = false, features = ["deflate"] } + +[dev-dependencies] +tempfile = "3" diff --git a/src-tauri/app-icon.svg b/src-tauri/app-icon.svg new file mode 100644 index 0000000..878f603 --- /dev/null +++ b/src-tauri/app-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..e875c0f --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,7 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Default desktop permissions", + "windows": ["main"], + "permissions": ["core:default", "dialog:default", "dialog:allow-open", "dialog:allow-save"] +} diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 0000000..f2f210a --- /dev/null +++ b/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}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 0000000..a023285 --- /dev/null +++ b/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{"default":{"identifier":"default","description":"Default desktop permissions","local":true,"windows":["main"],"permissions":["core:default","dialog:default","dialog:allow-open","dialog:allow-save"]}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/desktop-schema.json b/src-tauri/gen/schemas/desktop-schema.json new file mode 100644 index 0000000..24c9001 --- /dev/null +++ b/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,2358 @@ +{ + "$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": [ + { + "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." + } + ] + }, + "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" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/gen/schemas/macOS-schema.json b/src-tauri/gen/schemas/macOS-schema.json new file mode 100644 index 0000000..24c9001 --- /dev/null +++ b/src-tauri/gen/schemas/macOS-schema.json @@ -0,0 +1,2358 @@ +{ + "$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": [ + { + "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." + } + ] + }, + "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" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..feb85500d14d0f0cfcb9026e33778b240ce46912 GIT binary patch literal 2252 zcmbW3`9BkmAICS0nQM#CoD=07auhP7VIs7m`i@MKaucGQu}`_@K60!ul=vjcB=TKR zZc(fuM{;a)CFdL;pMT-=!~6MuJYGM(9`DEF`A&4Sw-$lGApihC#0HDO9q!nF2h4xS z4=DF;005k2gE7Ax&RlfG`${-T?^jB>@{A${d9+lD`F(lyahg~ao@y}g7Y;+j6M4j3 z{uwrqSVd;%z?*Axuw#MS4miyI)TP$)j?lM(shq8M}j)m9GVyJG8g-P zRG64dxBc)gkHi=P$l%B`LOHn=l;KhKR+g?Nf!JNum;z z8sLlb&ITd?5)_@K8&~u(*i^gitiPryNr}(+)TIL@V5WFhNa~%}+i-{WZ#*fPl@g|i z&`Pj`z35r!YEpBdQ>z3OcU!DgLN#W*jeH!vNqoJ^mgYW#5i@EDRbpFWw8~(HWJZe zaUv%XP!mtyLAEb&4EmSfls$WXRKS=fbP~m8n3}eLzDK?Ayr>u&VJLdG?f!jP!ZamV zSXci|fls{$B7ygKTKdl%r^r?>jq$xkDmzSTq5Y3)Pmq0NbJCK*w{^#8w-fI@oI$+> zU*zUfrE_5;4RxF|s;^&zq>HnTPJA6Ss`|vP9DIVB2wUBf>}^~tL-h(>MiB=&4X#e~ z{f^;&KZEZD769af4)J$FysMZl&bs`@Lg?JaUDH>C4cY9*#g;p}d2=rW|EK$vTtgZ4 zepSEd4%H}*N1Y&{2D)RYbmvzI&7jG6a=6mKflrDv{{GzX^olj**nAG;2PeaC0E>M~*Z(8x3Mf@|( zI14wi66LOqp__c$jX%igwKlIaa}4f2o)-y6`qsy644e#Mv+77Lh3kbD6Rv1B>biT? zkKXN-S#8UoGY`CzXlj>bm!w66jV6f{g`i7Si9Yq3z}-oExPS5N+psAbk5=WZ>{i#h zam|sAnxQcrn>ueHsk#Tlk|mvfQ`@e&)6v1V@HCyx!}#=JA1VMeO%DtJ0-%8Zg4l#~ zCD=Ggnj<*eLNmFFQ-qA-&@etnbHJ#D69Gs7eikxkNM28jnHF`An6AZXghpGYTm}U( z`gX5`Mp>t0a4LK^8YNzyn}Y*o_4D2I&uP9f53cMp0Y6c_P66{1K%7f}=N`rE))UBa z-Y*Q$_}e8VbQZ1*?8$i;m>Ipf?CC17U5ZUTmH^6RY_heYyeE?l-QxQf;J=^_lwz2sXcR!K5QdVpB)n*rtW6?%cNSOU2a(3F2era_Z0M7T8l}5RD8W6?ikTvdopT{C$AA(L#S8@l*Hv Z!ICfu>S@s~d-$aQY%J|DwHAc9e*j(}3-bT~ literal 0 HcmV?d00001 diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6160144e1e6306fa85ce8c0afb0ec9e6dd83e280 GIT binary patch literal 4629 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 Kr(ULN`}AKa!EpEh literal 0 HcmV?d00001 diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..d73ead4dccebbf475ba247dc100cdbc6acc9133c GIT binary patch literal 647 zcmV;20(kw2P)4h7N*rUri*{;mHH zbO!?s0now+%nVF{PamAV0Cxb$@;?m!&NI*vurM$gf*ipJ3iclipU>0U=h#AmgMrtO znNvU{188{!1O0)(KZf5gS%8)YFbog?+07`fVEmt9009fb0E1Bnj5=Vz9l(T7vLru^ z;q>863^yopKq}%%Z>!0byWG#~>i8$nfI+ zNroAd+86?S-NAZ5dY$a87(RXe!f^KdWzs^x!_6K;%+t+*At)k|LB+s_;mwnaV6hV$ zY8d|f`N5!O5&)J1sUgNJqM`t&CKfgZUI7_~-Sc7@q}A*gB$cgzwF@J4)4+~h2QkHV zA7og!Vm`x_BMTYWd4w5UlNK|`YPo^st{z(smIJ9F#w>zq02F7SxC6yID2^8`Tgy<_ z)XVVm=P!m=56^<*o`Xk(;rGuk47W}#XSi}`7Fgf@gU1;-IM^9fl@%FYy?)EkJ8?F{ zta;0DSIeW7(WnDP9WdaP5ipM*94N~{b^{&2uwmdFz_5Xdoq=ZoknwN;EPn{KdqBEo zAAwyn2T<1xTp58FpuQSCLIPw5%yMvL1e5su;n@oj0rdr-?h;TLCj*uHir@stz&H`u hAIy0C;MfD01OSMJy>4<80xJLj002ovPDHLkV1iVD6hQz0 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..0504b8ccf777481c00712d1c82623c7224e1c651 GIT binary patch literal 1247 zcmV<51R(o~P)?B{poL#HB$6+oXa?jRvqpOzCPgwkgO<3r2l13fjlE7Lz8W z(pZg&NTSIa2t`dSeKG!#Vq;C9Nm&D8x1{Z`KqYSCde2>UWng#qrv+i}{a$8g_THKE zeRJlVGe5_O2w<@kn8z^uo);)ppqRbz=CI9X%r2?W?xM|lclnm zJisQf2iS}d*aL{W5rRVsAO(;DNCBh(QUED{6hI0f1&9G#Hy7ajvSPj@^z{#*`C=;? zzi+~jdpP7g>v}sfZN)IBS^2u^>PGjiCiMQ&8gkBpS5tAIx`H2Tc}6}_Mu0fK(>%g zGa>+;jfxNLVaJid2Wu){xP9KvkmP&swV>teZTRzHCm)k(E76FpN8S&zX|mwc5(CP2 z6h}Um#Ro_GEsXyA1R3kgL%RLX*G_2M{>{RV@p{TIQsyUP%exyRK3?TVb{skW<@5@$ zdR10be(4cRek-BnK3PBJgr{}Q^2Eg z*qoLXAl`kuNpt3d)fE*i&6SsxuzbIiZ)?By6Hc8z|8h!Uc{3yhkOD{nqySO?DS#9} z3LpiL0?cX9-QbYG{D%zGW&&Y9lT*6bXL3*>>}PUN$pah$dw|1`gcM-~Anpe0p<$nl zo)z#j*|Wrb{Fz{4op|Tp$C2lt=Fr{sdJs+f$81ElX-*Y)T zbAmn=XB`v;G$5YIk8=$EUVwr^9E-roPi~ylX`SV5bk*^5&3D#B^=61BP;1#-PLX6g zaR@~|Z1*@c?en9h)MGt)walFo=jyTt^`l?Ki46DGMca zfF!d>qgEqIlMJZoh)`1ld~9-dBPl3UwoZ@ACM2s&uJzxe=xM0QT9xIKYek>Z6V9}g zD|6VZZ30nK0uTGyD^~}yQWg^xqv%2SacYrAw60$Xb?PQ-H1wT^>+BU0UCD}CCXa?b zAV(o_n4(9xsMC?N?j1yq$}Pn7NdAw84rU~4zFgCa^4vBE++T1=+PRcq^Ac2LulzNW z+?DY;SVCE1(u?+y4fEG1FJsDrs_d<WVpf+T@8P}NM7}Dg(I*q zA3ku5w)`N=K>%4G3jkRl3jkRF$O2ga$O1qX$O1qX0J1<90I~p(1+oB;1%ND&1%NC@ z@oYCOezc07TvnafcINvK{ru}ix_)D5Qnm{U-Q+2$qInC)Z2x&VB+lx;NOx`x*>>Ev zBdem^OKmN$)A|=0)9&--$sldrwU4e`?Kfn5md8s=S8k#*Uqjk``Yr_Nhg19L-$=jV zaqrx+iB_*lZ~O3PUDUq+L&M|F22zP!9kcQa+FF{at+iSA{_PzH#K9lXweMq3HLDN3 z`01Mi>Lb_V&K7w^9`AOmjPHME8#Qly#n5Ls)KrwurxT~TGwxT{xQ%>u#%yzloK-xZ z`Yv|oY}~dai~lN(9&kc2i+t*3ew-K5Dat5Oam*?8CGy!+kK2}EkYjz%x5p-Jr*`?; zq+Gx`s_{+zw(pAK;;K;6yah|u&&JX9-rA%bX63lefXgaXnSotfHz#ddx@xl_qQ3s* z4LW;lJAL)htK-N?mFrS{UoMZ|a2m+yBT8{SM0)S0c(!W?UFz)` zM~-%GL{Go=0nO0lCXl6p*>U>!zH4L7k6lY9Ye}OzL8hc$;lxB~?G65Q z!`6PVWS+%U)rDU3?ttf8vLewe)t%+WOTwIcFtEL{7CT);+%J`3X9H+0kFhh9Ej54{_e~+R;Oqi|B zQ5^cxDN6T^w%EW-a?9u+T37WIlG{HKQM#0+g;%Z)3=PW!@(sO7Mxy>AK zh>Vit7uBO*kSB5+Et$s5^zu0q>QcN|;WZNKGU0z%a>2nl1Vz070000W7JWVcU&M-70$@2%pL^7pex@$lKigQRZ@eul1R#@-c zfJHBlYkKdHPmn%xD=egT#J=K}Pp3dnMRSw>!*Ah(ts!`Sl3X><0nGeEWLjyeL2A5E z<}ce00>ebKybz17E|lF5z6ZQ%ZPxFp%cxHc8;f9DPgi0PLmV=wXccPVLj!nO%W6X8d#~QB zFDGpelj?-UPlGXk`Z?RiDbdsODp)umVH67i zq%@{J#tHzg*K`{TF#RRZ!r&JzEWi}444?qev;s^3AU%W!;0I`@0n+~i{LB0U{LB11 z`VV8)N0IO;Em`W_K0CMwM{&RRS{T3Xzy1CKVQ=+_^>1+GJw4ld_pYhzB-?{(BPx56 zRBzWRPuJItV!bt|q@tA3Yra6INJ%IGoo{QJTesChZVP+n@vNkie3VS6RN0UjMq!6K z=2HTZ2l6nTE==1%<6UwO4SDT$m7jKW)WxXwo=vCdPTNB*dj4?CKprSts{3XPB^(DsmQFXmORT`=7-h!Hu&d?XE~_HeD5NeA7)wRI^1KrPVFZI*+pH% z-=?E{B98ESpNx$XOG#d9Eu&glIY__LqLbeTT^mF0gaos<44iz{W-6Lw4hAQtS2xZ)&+OrWZSZv) zat(Y4bv`2)m-(=z_qc5YnVXO&6)3wRASd=Ee`fx(mk3hpGpb{+8%qA}$h^}IHi7!H zW-ejQ^yDC+`h;il`}m?xu7fbsAY9ozN;^QhByIT&@{A)up*PxYgU5uNIU`HcpzWI6 zyKVY?+hML?H&e;7x$L9KhUZZYvs>S!6{u0JMx1R0{U#TNlxXmD&|L4US~AqK#f(;0 zmkF#ig4hbD(`t*<4|hmSEctELS-x zTHk*`y~_c4(+ap&WpxM$?8kh5xL3P3-IH_j?d5C6HZ`4}trxvh@vE<`Do*V&;JQjj zUcv{vr^geERlPIbFJQ;PVTVLwWxQhKIpeo|D^K$?kO3Si8AINq{8?D87pPmshSa!q zJu`i@`o6|?f)4Ht;ocSuNmQmc#sXtlAOCpMKs|*3%4fq-M1hr)NEAv>wXOJF zYFes*Q{EHRjdbZW=*Kp;hzL5Lu+dlkm9Habv;BECZd?w95WJ?33a>6ir1N=_XLBZrZE2)+)`Xp2SdXHN590djW z`q6LGhuO(`JC|phw3cK7`CsfYcTx8l7fl_fi<$Z(n_BYT_J*oX|ZX zUiGu0ZJ8FQ`}_0U#KI-xC;YHj?~&bSClTBex#cw*!GJcwE1V6d)MCX_uQst2`m)oH z?_SlJ@0+yt#N*0L9J6cGCHmC}U_)CC<>rEo%?ExBpG6#%GpQws4}%=SnKOP9*kKu{ zUCQ+Gd~LK4nClRZ>~no0XR+VmK^=gj6Ucll{ehL~UAqI|8d^lN6wa@3pz>B+^5MpA ztfyKXT@6AYN7Hv*CFCX7BvLbPBBq0?*w%Ev-)5cSWZG%@LJia%P=2WpS@54ITF!CT zGp5{fj$7a4@&mZQuU#77|2*=t`G4FKVn|_`O9^6h0o>>0K#^b;Kze7o_@Yvveys9G zq4M83=Lx#t<&nbI&Cmk4mB zm^qC&*?@L*4Wj>yz>s|6S7!T2tKeIFq#n48#G}tQ#xFlv~Q~R4=Lxm&!ics94!v zkBYf2^g~AtB<}vJ@>JK|l~f>YysqB>(aOF+?*~r|DJ@DCXPNBHDO|-bHm3Q0ny?k$ ze_QR5r-g?1gqZ)?o@_lff4_rn1Uyr+KO6bpMgsv{fONycauE(xoWQ`Sa&t8%_$ie zj*tikT)7%DMUjly)mLdIX0nGV((Z%OKWeM7A0mYtyR$>R*6*i zEIJgmQpBoNo`b+<+A*00aO4tVV`##Gi}%XP7Sh*_w3s z<^cfaKqL4~n};*&xiS7+%ltj~`$C*PhjYLS9T&;ovLpGoVY=rBjTZuaCw=7~d4C!W zL?|Q*SEwX7t_VAc(2Wzg>*r8_Krz(UaZAym5goBX^_V7(qX>#z7g|lPS zGQQ^-lAPE7PLTW7HF&w6iZL3lQiOw`)|$leE5;_IB+Z?c`4X#QXF2EUt;|00A=jJ} z)KfXxF5rYYY>?eo^x{&bMGX(dSl%R&C8P*rsck0S<;U;e8wMKF$#0f@%b)zSOazfF zrBrc_OEo6M=*KNj3AFnBFd{t!#_f->$%;jk{QX_3zO-<1vOr#GR|-nTHZnzYr^$F8 zV3(VXhms3OloB}%S>s6d7s4wL-n&v7A?T-kfcAYZBT79#Pnh0d4y^LPoMg6Y7`haaje@xa)! zgb67at;^PJu|c9ury#i(&{c_pKXvB(g|i5g{E^&OXg`sZNw*M?u;%a_Ryb)nm0%fn zz~`Il;D+&xL%spbKhEWMRsqSnF+3;4ItVC}ffIJfQ~Cif*+8pj9L!%s(j;AtB4C-w z0^9t4MEnJDT&=fHCX$o2z>A@>oy^sq<0|F-gr34w{vO8Y(#nCcSEi?j=!W!#=N`2r zFc67D+rfhyOfTYVf3{N(3qDDm!n^71mSwTl#Z|iJ>5-a3ICau9=Cofq*Ke7}(@mwl z_tb^lp$xFQ(mBN<0j_ij3|Dyo?94h4015Cv0O+Fs7hL7}7yJ?a0f2v;f55-aF8cqz z+EFDTy`^a0S7V*Z&e>M;CFK~9Dy@0+lLh{@x`FVOpT(WD+L0cwp&8UA@S6#x5Ko52 zlJwDlF;DBzz}BPR9Tg|L6N~q`8zOP7nzKf{!*$cBu# zjv^Ktp0?EQxA?1>_;ipKMsG&FyiAKnLdNA{oV}(kn{0X)p%DYbTi`d&A`?N9l&qHz z<;iYeZq)0KyDYO%$OK4tl<9_>m5 zw(53s74%p(;z2VQ@yzq6egk~A+_OT}sg9cq68!2sU$=Vz+Sw>!&WIQ{GA|DA+K`DBI*)1{(D)VtsIJEo40+a9v2bVlvDSsqs}mHRt0bG* z5oVHxW%*MExTUx>XnjEGopEcw6=4I5_>;ixTobMr)RRw>-l0@wW*?eG+5Y?B#I~3i zkI}p{Y{pqyXxov#XM%@)M>+Y!cDPconB1+@HFc#jF*cWek!RO`Z#{NeIhxp%fVgZs z5aDQ))|-cyzW!A!H~)I$bsEWh)iQJKn#Pz`c;(g9KC82n-_?YYPIO*zeZ6-6q}AR1 zmR3(wuP8*|l_*+iHT<&U(_(tqvU`{rbdD@7*<17gqc_h?83<8^2DD0jGAy`)wMh~6 zC_N4zl*~$u?t@rRtaUh}x{y-n%60)#M{uxcAr#8I+G%~|Iiqs~;gZ_X#nVI7KWx3w`F=m#9+rpZ#UePx}ll7^#;u;Q!eBibd$ zW-6(etl_3X`%tXHzd7nbz{Ese^xT|@-N3lx{EF`n)ki3{8@9HN7CGRz)VCVVijk!l zq~;PWhH@G<+ARw${MJKB>}!{1z^!gAYs}+}3D>Y2sS&e#xy|dtcB0*H%c>DgO&uHU zREG55)~~JB9tlI=bqJ^@l6D5k?`iJ6Iiapw$*qB%o}Yn}W=^|`oN{3!f}Dfx*ghC@OuT6_nyEvp zYwg?JVbq~^5U<}v#rRhhPrvfIC_h#YY1zh8X(}OUyV=Y|e#!J??S7l>J|E{EXmpCT z!u371{OB5sOXZRP#w!LFfCOC*3F-7T&%0ajaF4gbvQ#Z>dUFU|#OluD8@3}@ZaGEt z)sern38V7-UbU&s7xiyw4t@)*A)h8hIjuDYk!8$o7fSUvhwH-^=2Gp|H#hrWd08xa z`t?(v;_Ie{B}1PQEWfV}+*5-@*a$KDlNjUK80h}*B;f%>sC9YeT+%nGGtQ3=IV`ZjhCRq0>+#;#66k87-M|Hv`u%!nvn9!=;B+ zoFob7w|bHtgpmQOS+THVJ%bNZEa#h=w{LGa%=ak7b2EkB#zCFs(?)jrnRaV*uCu== zf8X&v1h&8(F8z}ms3O*yrG9+4$c0`g>qzPBbSsiKt3ntyBE`7T(v(0;;!W;@S|m*S%5{n*bM>)M zi=mt>ImzFGok9I&I4S6_sJ)X!L19|SDh}%8uW62Ans8N(cTdW+LS(5C{SLgCymE9n rgW#9qZ{Qzm8;ejfyw}}dot;Z~Bfzc}-+$##0S6fAnZv7e?>zbspP{AX literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3276a2dd2a83baf7fa9a8b2653ee79da8ac872ca GIT binary patch literal 5208 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 ZNlssgTqpVGo^=d?$B(oh7AZe}^&hH2^~3-G literal 0 HcmV?d00001 diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b5b806e6c7c41099d11d4c5841d8bb0873010ee7 GIT binary patch literal 632 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 O0000 + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..07b2b669c82a170724bc4e9c7292879243db1ff4 GIT binary patch literal 1647 zcmV-#29WuQP)fWJic2monLAe(;fF1V1%Ne&Ugywb(Ic*+EOCB*`c*Gr?NcoGjx#O4 z)g_xatPpNbL9utt#P?*yvC^`v^s871n3}fP|Ig{SL!r*IF{SG%gD5VktW-R?pR1aG zhJY{!(2F#PZ^a(^VM$I$PLml7Ed7rscJ6+h{WimGy#Y!ON!b; zM8ax!x#aPD?3$CWdtbhB=#^Mxcc0Oxg#Sq39ZLXH?_WV|cOn;qzF z-<{N*#!a4gpCydlqyZxl$m=w0_Yy)#is_zzre$y4u*$Z!zW&ColE(JuC;0wK5= zu%D=hnv}mUYY_SS0LxoUIEaRMNX$e;KUs<$<+R^N zg<~2WS|k*ej%XBT7R^=YU$dW&8i^nr3fqFqih^tp(@^JXTGtK)3#_UtoDK)(Rn0qW-#SFa=NzX-#UFniWatbg}a%&Dxfd1#u3k3Zdx^&7Xs?+-$j zrIdlB+#fO10J+d}>uRyO_6ZvRYmFCYK|a~C9zL%JnjxcV*-I#^T1ww^s6n9P%n90H zjZ0^MD3y0XcIg7|LR3P`eoin|_#&!wW~p~bjo$->Kc z@H*p)#^RT~f7tFhlv@S5&%HTfcd}hcH;_-l$2BC$|KV52r#YzSRac|K=s?H~H2%5+ zr~cdnL4tV;CZb|dEkb0Kipm8ORJ$GVlD2j}(?w3Lp!#QCw}c^zIa z)tE@h60Ef>N~1>`kK^k--(&6Sm9`eympRR|*;`uMu<^sq=FH-)Lu=c4D3XDW(+zNWCQyoj%_-`#zms^dY1g z*jIN5w^z(SX-RQ3CP?5-TR+1G8@475>cKH_`Sx^5l{H*!u;y6!nKR1Zi`9u{suS%A z>O@{q8M%{#Tt!3A1*!qzUdRqNn-&q`Gmh#+S*%WUQ=Mp}I>9c+${Nm<+W$Tzt{6l- z@Hj34!BEnQtzSXKW`4b^4!mIB`OO;xr)-)ceX1M_V0ALy&l>+ zR;TgOvHyaT2f5HDI_|0M>O9#t;@i`K6G|s9yi*tIBcv6&#|GbHXJ5m6Y_0?KoP{`u t&$k`iuj{Hc6o4C{I*)d-qae~f{spmW)7Zd`=~Vy#002ovPDHLkV1i(t8P)&* literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..d2c093bdf05c89bc1752ad3b1ddd998d1e235fa9 GIT binary patch literal 3006 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..10b3a4f9f6f5199d27ffa4e1783e79741a6c8343 GIT binary patch literal 1592 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 LNkvXXu0mjfDbLSd literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..42c50792fd08ba0bafa7ecd29399c48ebbe92088 GIT binary patch literal 1318 zcmV+>1=;$EP)WTbZ z#u4;Hd@e2}>Y02hY_$_)1%t-)I6d?!# zqR|KPr}eMVN#gjYhNQLMD?5`}Xce~)_ z{02Pk5iGl>8S9!}g2iS#jAcP+e7l zUC-~~%1R|BlDd$@%uO13Y|F!VwDCdyPSZ3Vyy39MpNYt}rp>rMu44827Ff!x%#FYW zxHj+$zCQ2-ZVdNh&4!)KYChgJPM$Cbi!kYG-r9&w%%y_CAa}KSTR>nS0X4`#>!Gk9 zi(4cXh!lhxWcIwHr3KQG_3T~^pIb#=_Yu@GYkb$y3UiSSWlPt>BnR-qD~Iq@!^#+W z>MO^CJN8>xU`Ep;o6vcx3ud!9sTIUb6);f}85x-;O$yfG!5`6fuo>Z?2lbEdg{(;2 zon(WOP(Vg2@oWo+DARz+Y{a+S=Me}5XSk3&Nv6HM6JLDzI>uc6Fk2LCc=}D;dV2#l z?syvoC3Z;6cw?>seEvZzYkM+geoCX$T*&8F(fLgm3d|Xb0gZtWSbOIgeARX;;X;(h zH8){2^xDc|Gs0nnn zb(}_5_c<7i#*_q@V#^|{{ngp>KMmA<_5vd8|DwV|uCal?uJ8f(zx5u5M@CUtRDzL9 zeemD73WHLLfKNrhJC3m-2l~3+Woh&zi-%HHxV*Ud^RGOn6h+}h%sF@&pB+1a*8PY1 z7|H5^&pq_oj5Tp8aWKZTyRxnpb<1n;yTgeqS6$o{sEU$Mq9pU8rI;)%8{NkHct!_V zR`ReKP|Pw@m}@%}3h^MeS5;y~T@4(6IPsg~PuN&}NoTDrq&tWuy-_#;AWN;ddXgCGv+{4kqOUeR5%`4Bb90#7eG#TDnXhfAxPu&91p&!fv@q& zoFp6nnao9|wu=cIOQQxuVFG*JJ9Bzp4@}RPH|Kgl*GtMwY3kM7R~fHVlK{hGl_q5o)hQIN&Ww` c(F|OF19BKo!Nb0o>Hq)$07*qoM6N<$f|AdEDF6Tf literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8ea5d0ce04f52e1ef3c0eaee23904fe588ad2349 GIT binary patch literal 3525 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 PRuZ7DtOKo7G!Ok3PiyIz literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97ea145f25f85df487a5eafefcbd0426ca3f2302 GIT binary patch literal 6086 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 NDvBBk<#I3H{SPRIKNSD~ literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..00e803bc2fee85cce06f456c5290d2936b6dc3df GIT binary patch literal 4143 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..040902b4ee8e00c891ebf696cd78cd1532f0fe47 GIT binary patch literal 5814 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..d9391ee329305cbb12ec26d90f7151a7c21645c3 GIT binary patch literal 53612 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

2<*5v5(=mLKMvXvZ8x+ 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 Uc14Fk@B~~oFu7QG!Qtuu0*ex(^Z)<= literal 0 HcmV?d00001 diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..1b86aa3c3f1e55123612d3f2835ce971f4d5f736 GIT binary patch literal 389 zcmV;00eb$4P)SDM$nJ89@d*dqtCM5IXR1wP#o`tCxY9 zM?k861Pp_a1_%|I81beQx;q>lXvH0He5q@qY#?0u}};gCS^Oc!)1Uc(4z{ z<0sD;>YDo)n7E`E3|))Ba_4vVG5q`XjG?Kvh(T6Hiecs2EetEyY{nfa{CvC&H!tpo zOU_=jf#L8y2?joKJ%&4H*Dy#b=rUNzJ!D9Y3xKOp*LP(2_~|q8UgqQxVR(A;0E488 zH3J*Vf6}}RvMIl`5$J7@m!C8A^iG6(`OEt!3|9`#VmJWwG9Nb!(94oQ?`}bH-Doo;#11|#6* zqYfN(;HU#fgadVeEsL;VZ!it2erC^K z&hYWmXB0JxdOi$d@{so2tA}S9?qAx1BG1pq%aEEF32yUU0X9xnt=$Ty2?2rywWpf{ z!~B^&C}JST1xIGUEwBykVUSjLKvDDb<^hJot4rV>T)TV**pbK}F};Lg`>q3c9mqt; z2NSyMF~mR?P3W$Jgq*%FisOF&_{^|xaXMHGBnL604#RP{%ti;yJOWZU>taxJ#775X zl@k*cVPNEyXAn{KX86mzjra$$Ig53nPn1}SFdVJT*SA&FAVKplrt z%p^gInXA`s0lT$Zs8h_0)+D129ChHR1BbZ-nY1)j2W>eBavPH_beL-}0#L2P1RU99 z9IOuXa&u&40=W`6vN`CY8RRCA+rR^upa!FpSLFZCpT7)>(B$Ri`M+=5az^+7CWx_N z#Y{$!#9(k7$StsOYIu7PG$ssk^*}~9$Zn7uw2?9{Bd%$PZM*jW2hrEA-)5wBQ36V9 epbUX+8Ug_P&@19W?WFYp0000o;#11|#6* zqYfN(;HU#fgadVeEsL;VZ!it2erC^K z&hYWmXB0JxdOi$d@{so2tA}S9?qAx1BG1pq%aEEF32yUU0X9xnt=$Ty2?2rywWpf{ z!~B^&C}JST1xIGUEwBykVUSjLKvDDb<^hJot4rV>T)TV**pbK}F};Lg`>q3c9mqt; z2NSyMF~mR?P3W$Jgq*%FisOF&_{^|xaXMHGBnL604#RP{%ti;yJOWZU>taxJ#775X zl@k*cVPNEyXAn{KX86mzjra$$Ig53nPn1}SFdVJT*SA&FAVKplrt z%p^gInXA`s0lT$Zs8h_0)+D129ChHR1BbZ-nY1)j2W>eBavPH_beL-}0#L2P1RU99 z9IOuXa&u&40=W`6vN`CY8RRCA+rR^upa!FpSLFZCpT7)>(B$Ri`M+=5az^+7CWx_N z#Y{$!#9(k7$StsOYIu7PG$ssk^*}~9$Zn7uw2?9{Bd%$PZM*jW2hrEA-)5wBQ36V9 epbUX+8Ug_P&@19W?WFYp0000!*4f2>Zr%3w^}O~yc5 z>8O9K5!5j-6U0A^tWsg7Oxo4iioZIVshe|6=l)sSq+^>%cE2}$kG3yu-cwSVmwO

z^qO-cz3MDas3T1U3{da!Q~Wbq0JYoz_s(km$p>k zbEe4$8k@cJW|b#pVYzp*zJuY|@o%OFK7GbdjbC`lD6;=xBOj7+UHU?MfOdH5$cS>* z*Q&TT2S$Key}mM~-8j*hzIypQ4`ke>4>f-oH7cw;rkQJVPnL#)k4<+v|?o&fa~WP#as{>J_ssXMwKO5{hBI zlP#2KwW`kH*rxJz>^8<~+v8!^y6VYnQS0E?qT&h~9Ui2ihe72yKtKMlX3BQna5<^7 zyN56Nl4{`}zdxtA@pQ6g!#3`IaAVhd=Tw%3{CU5Jf>(U}W)Q}- zCutUjD{p`MElt%TzV=Y=^To7s!!GuH zH@7CND+jS9g{vs*1sm7Z(4XJrCoqA~IsWWp!Sf{1E-KLq}Cl>@dG#p3Pgjn$5czD$8DN`&^ZHU>-;D+_9=8 zZacg8d_atO>@gwJa*z2r%B)6#bx(>TaDaMZ8?_`|8@1wDoYkD8s0rU>Tk&Sg4FF)t*j79w6^KKLPFANQj zpFU@xBlyoKu3-G1o;HFK7YhTO0Vr3{-BJ)3wUnlS37;fCA1{NBmO55BF?nOG;vn@P zePmghmK4Qs^WuJnJzJMDoIbn>Zn>hqFGFz2eg>!5nP3`bIY=I)4x}EW52PO#VCE5! zYQW~&`7?V^d@Ls~%W(Y6RR*(wZU$C%9h{K9Z(RWZZ!N7oo`+`I!+ zZv{_8b|0UN@M^t2RY9}}qM4Qh|l!$OcfAp5{&FQ@@bYl~qG zZIo^WQeWo(Hei|g`STZwgB_Gey&!!VM&!N>06ja^V)$$_-~a#s07*qoM6N<$f)TX} AK>z>% literal 0 HcmV?d00001 diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000000000000000000000000000000000000..75f1599bc90b31cdd17613d52580065e3f97b87d GIT binary patch literal 1129 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 epbUX+8Ug_P&@19W?WFYp0000 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 J^N{)N{{@3$=S2Vj literal 0 HcmV?d00001 diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ceabbe98b92a621868f27374a3b453945badcc34 GIT binary patch literal 2409 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 R*-rof002ovPDHLkV1iR*vlajV literal 0 HcmV?d00001 diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..eae473526f7272e2b9f4ac63c14ff5f81572adbf GIT binary patch literal 2981 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 literal 0 HcmV?d00001 diff --git a/src-tauri/src/chapters.rs b/src-tauri/src/chapters.rs new file mode 100644 index 0000000..f2c5429 --- /dev/null +++ b/src-tauri/src/chapters.rs @@ -0,0 +1,136 @@ +use regex::Regex; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Segment { + pub path: PathBuf, + pub key: String, + pub source: String, + pub start: usize, + pub end: usize, + pub quote: char, + pub index: usize, + pub cache_id: String, +} +pub fn files(q: &Path) -> Vec { + let mut v = fs::read_dir(q.join("chapters")) + .ok() + .into_iter() + .flatten() + .flatten() + .map(|e| e.path()) + .filter(|p| p.extension().is_some_and(|x| x == "snbt")) + .collect::>(); + v.sort(); + v +} +fn decode(s: &str) -> String { + let mut o = String::new(); + let mut it = s.chars(); + while let Some(c) = it.next() { + if c == '\\' { + o.push(match it.next().unwrap_or('\\') { + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + x => x, + }) + } else { + o.push(c) + } + } + o +} +fn quote(s: &str, q: char) -> String { + format!( + "{q}{}{q}", + s.replace('\\', "\\\\") + .replace(q, &format!("\\{q}")) + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") + ) +} +pub fn extract(path: &Path) -> Result, String> { + let text = fs::read_to_string(path).map_err(|e| e.to_string())?; + let re=Regex::new(r#"(?s)(?:\b(title|subtitle|description|text|name)|[\"'](title|subtitle|description|text|name)[\"'])\s*:\s*(\[[^\]]*\]|\"(?:\\.|[^\"])*\"|'(?:\\.|[^'])*')"#).unwrap(); + let strings = Regex::new(r#"\"((?:\\.|[^\"])*)\"|'((?:\\.|[^'])*)'"#).unwrap(); + let mut out = vec![]; + for cap in re.captures_iter(&text) { + let match_start = cap.get(0).unwrap().start(); + let line_start = text[..match_start].rfind('\n').map_or(0, |i| i + 1); + let prefix = &text[line_start..match_start]; + if prefix.trim_start().starts_with('#') || prefix.contains("//") { + continue; + } + let key = cap.get(1).or(cap.get(2)).unwrap().as_str(); + let value = cap.get(3).unwrap(); + for sc in strings.captures_iter(value.as_str()) { + let m = sc.get(0).unwrap(); + let raw = sc.get(1).or(sc.get(2)).unwrap().as_str(); + let source = decode(raw); + if !source.chars().any(|c| c.is_ascii_alphabetic()) { + continue; + } + let start = value.start() + m.start(); + let idx = out.len(); + out.push(Segment { + path: path.to_path_buf(), + key: key.into(), + source, + start, + end: value.start() + m.end(), + quote: m.as_str().chars().next().unwrap(), + index: idx, + cache_id: format!( + "{}:{idx}:{key}", + path.file_name().unwrap().to_string_lossy() + ), + }); + } + } + Ok(out) +} +pub fn replace(path: &Path, replacements: &[(usize, String)]) -> Result { + let mut text = fs::read_to_string(path).map_err(|e| e.to_string())?; + let segs = extract(path)?; + let mut matches = segs + .into_iter() + .filter_map(|s| { + replacements + .iter() + .find(|x| x.0 == s.index) + .map(|x| (s, x.1.clone())) + }) + .collect::>(); + matches.sort_by_key(|x| std::cmp::Reverse(x.0.start)); + for (s, t) in &matches { + text.replace_range(s.start..s.end, "e(t, s.quote)); + } + fs::write(path, text).map_err(|e| e.to_string())?; + Ok(matches.len()) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + #[test] + fn extracts_and_replaces() { + let d = tempdir().unwrap(); + let p = d.path().join("a.snbt"); + fs::write( + &p, + "{\n // title: \"Comment\"\n title: \"Hello\", description: [\"Line one\", \"第二行\"]\n}", + ) + .unwrap(); + let s = extract(&p).unwrap(); + assert_eq!(s.len(), 2); + assert_eq!(replace(&p, &[(0, "你好".into())]).unwrap(), 1); + assert!(fs::read_to_string(p).unwrap().contains("你好")); + } +} diff --git a/src-tauri/src/core.rs b/src-tauri/src/core.rs new file mode 100644 index 0000000..928f452 --- /dev/null +++ b/src-tauri/src/core.rs @@ -0,0 +1,584 @@ +use crate as crate_root; +use crate::{ + chapters, + snbt::{self, LangValue}, + storage::{History, Settings}, +}; +use chrono::Local; +use futures::stream::{self, StreamExt}; +use regex::Regex; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Map, Value}; +use sha2::{Digest, Sha256}; +use std::{ + collections::{BTreeMap, HashMap}, + fs, + path::{Path, PathBuf}, +}; +use tauri::{AppHandle, Emitter}; +use walkdir::WalkDir; + +#[derive(Clone, Debug, Serialize)] +pub struct Scan { + quests_dir: String, + pack_name: String, + mode: String, + mode_label: String, + source: String, + entry_count: usize, + file_count: usize, + estimated_batches: usize, +} +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Report { + source_file: String, + target_file: String, + backup_dir: String, + total_entries: usize, + translated_entries: usize, + cache_hits: usize, + failed_entries: Vec, + warnings: BTreeMap>, + failed_translations: BTreeMap, +} +#[derive(Clone)] +struct Item { + id: String, + source: String, + protected: String, + tokens: Vec<(String, String)>, +} + +fn has_lang(p: &Path) -> bool { + p.join("lang/en_us.snbt").is_file() +} +fn has_chapters(p: &Path) -> bool { + !chapters::files(p).is_empty() +} +pub fn resolve(selected: &Path) -> Result { + let s = selected + .canonicalize() + .map_err(|e| format!("无法打开所选目录:{e}"))?; + let mut candidates = vec![s.clone()]; + if s.file_name() + .is_some_and(|x| x == "lang" || x == "chapters") + { + if let Some(p) = s.parent() { + candidates.push(p.into()) + } + } + for a in s.ancestors() { + if a.file_name().is_some_and(|x| x == "quests") { + candidates.push(a.into()) + } + if a.file_name().is_some_and(|x| x == "ftbquests") { + candidates.push(a.join("quests")) + } + if a.file_name().is_some_and(|x| x == "config") { + candidates.push(a.join("ftbquests/quests")) + } + } + candidates.extend([ + s.join("config/ftbquests/quests"), + s.join("ftbquests/quests"), + s.join("quests"), + ]); + for e in WalkDir::new(&s) + .max_depth(5) + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.file_type().is_dir() && e.file_name() == "quests") + { + candidates.push(e.path().into()) + } + candidates + .into_iter() + .find(|p| has_lang(p) || has_chapters(p)) + .ok_or("没有找到 FTB Quests 的 lang/en_us.snbt 或 chapters/*.snbt。".into()) +} +fn mode(q: &Path) -> Result<&'static str, String> { + if has_lang(q) { + Ok("lang") + } else if has_chapters(q) { + Ok("chapters") + } else { + Err("任务书目录中没有可翻译内容".into()) + } +} +fn pack_name(q: &Path) -> String { + q.ancestors() + .find(|p| p.file_name().is_some_and(|n| n == "config")) + .and_then(Path::parent) + .and_then(Path::file_name) + .map(|x| x.to_string_lossy().into_owned()) + .unwrap_or_else(|| { + q.file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned() + }) +} +pub fn scan(payload: &Value) -> Result { + let q = resolve(Path::new(payload["path"].as_str().unwrap_or("")))?; + let m = mode(&q)?; + let (files, count, source) = if m == "lang" { + let p = q.join("lang/en_us.snbt"); + (1, snbt::load(&p)?.len(), p) + } else { + let fs = chapters::files(&q); + let count = fs + .iter() + .map(|p| chapters::extract(p).map(|x| x.len())) + .collect::, _>>()? + .iter() + .sum(); + (fs.len(), count, q.join("chapters")) + }; + let bs = parse_auto(payload["batch_size"].as_str().unwrap_or("auto"), 25)?; + Ok(serde_json::to_value(Scan { + quests_dir: q.display().to_string(), + pack_name: pack_name(&q), + mode: m.into(), + mode_label: if m == "lang" { + "语言文件" + } else { + "章节文件" + } + .into(), + source: source.display().to_string(), + entry_count: count, + file_count: files, + estimated_batches: count.div_ceil(bs), + }) + .unwrap()) +} +fn parse_auto(s: &str, default: usize) -> Result { + if s.trim().is_empty() || s.eq_ignore_ascii_case("auto") { + Ok(default) + } else { + s.parse::() + .ok() + .filter(|x| *x > 0) + .ok_or("批大小与并发数必须是 auto 或正整数".into()) + } +} +fn patterns() -> Vec { + [r"(?i)[&§][0-9a-fk-orz]",r"%(?:\d+\$)?[-+# 0,(]*\d*(?:\.\d+)?[bcdeEufFgGosxX]",r"<[^<>\n]+>",r"\{[@A-Za-z][^{}\n]*\}",r"(?i)\b(?:[a-z0-9_.-]+:[a-z0-9_.-]+(?:/[a-z0-9_.-]+)+|(?:assets|config|data|kubejs|models|recipes|textures|ftbquests|chapters|lang|scripts)/[a-z0-9_./-]+|[a-z0-9_.-]+(?:/[a-z0-9_.-]+)+\.[a-z0-9]+)\b",r#"\\[nrt\"'\\]"#,r#"https?://[^\s\"')\]]+"#,r"#[0-9a-fA-F]{6}\b"].iter().map(|x|Regex::new(x).unwrap()).collect() +} +fn protect(text: &str) -> (String, Vec<(String, String)>) { + let mut found = vec![]; + for re in patterns() { + for m in re.find_iter(text) { + found.push((m.start(), m.end(), m.as_str().to_string())) + } + } + found.sort_by_key(|x| x.0); + found.dedup_by(|a, b| a.0 == b.0 && a.1 == b.1); + let mut out = String::new(); + let mut last = 0; + let mut tokens = vec![]; + for (start, end, t) in found { + if start < last { + continue; + } + out.push_str(&text[last..start]); + let ph = format!("⟨P_{}⟩", tokens.len()); + out.push_str(&ph); + tokens.push((ph, t)); + last = end; + } + out.push_str(&text[last..]); + (out, tokens) +} +fn restore(text: &str, tokens: &[(String, String)]) -> String { + tokens + .iter() + .fold(text.to_string(), |s, (p, t)| s.replace(p, t)) +} +fn warnings(source: &str, target: &str) -> Vec { + let (_, st) = protect(source); + let (_, tt) = protect(target); + let mut w = vec![]; + for (c, n) in [('\n', "换行"), ('\r', "回车"), ('\t', "制表符")] { + if source.matches(c).count() != target.matches(c).count() { + w.push(format!("{n}数量不一致")) + } + } + let mut sc = st.iter().map(|x| x.1.clone()).collect::>(); + let mut tc = tt.iter().map(|x| x.1.clone()).collect::>(); + sc.sort(); + tc.sort(); + if sc != tc { + w.push("格式码、占位符或资源标识不一致".into()) + } + if let Ok(src) = serde_json::from_str::(source) { + match serde_json::from_str::(target) { + Ok(tgt) => { + if json_shape(&src) != json_shape(&tgt) { + w.push("JSON 文本组件结构发生变化".into()) + } + } + Err(_) => w.push("JSON 文本组件不再是有效 JSON".into()), + } + } + w +} +fn json_shape(v: &Value) -> Value { + match v { + Value::Object(m) => Value::Object( + m.iter() + .map(|(k, v)| { + ( + k.clone(), + if k == "text" && v.is_string() { + Value::String("$text".into()) + } else { + json_shape(v) + }, + ) + }) + .collect(), + ), + Value::Array(a) => Value::Array(a.iter().map(json_shape).collect()), + Value::String(s) => Value::String(s.clone()), + x => x.clone(), + } +} +fn cache_key(source: &str, s: &Settings) -> String { + let mut h = Sha256::new(); + h.update( + json!({"source_text":source,"model":s.model,"target_locale":"zh_cn","style":s.style}) + .to_string(), + ); + hex::encode(h.finalize()) +} +fn load_cache(q: &Path) -> HashMap { + fs::read(q.join(".ftb-translater/cache.json")) + .ok() + .and_then(|x| serde_json::from_slice(&x).ok()) + .unwrap_or_default() +} +fn save_cache(q: &Path, c: &HashMap) -> Result<(), String> { + let p = q.join(".ftb-translater/cache.json"); + fs::create_dir_all(p.parent().unwrap()).map_err(|e| e.to_string())?; + fs::write(p, serde_json::to_vec_pretty(c).unwrap()).map_err(|e| e.to_string()) +} +fn backup(q: &Path, m: &str) -> Result { + let root = q + .join(".ftb-translater/backups") + .join(Local::now().format("%Y%m%d-%H%M%S").to_string()); + let name = if m == "lang" { "lang" } else { "chapters" }; + for e in WalkDir::new(q.join(name)) { + let e = e.map_err(|e| e.to_string())?; + let rel = e.path().strip_prefix(q.join(name)).unwrap(); + let dest = root.join(name).join(rel); + if e.file_type().is_dir() { + fs::create_dir_all(&dest).map_err(|e| e.to_string())? + } else { + fs::copy(e.path(), dest).map_err(|e| e.to_string())?; + } + } + Ok(root) +} +async fn request( + client: &Client, + s: &Settings, + batch: &[Item], +) -> Result, String> { + let input = batch + .iter() + .map(|x| (x.id.clone(), Value::String(x.protected.clone()))) + .collect::>(); + let prompt=format!("Task / 任务:Translate this FTB Quests language map to Simplified Chinese.\nStyle / 风格:{}。\nReturn one JSON object with exactly the same keys. Opaque placeholders like ⟨P_0⟩ must remain byte-for-byte unchanged and appear exactly once. Preserve item IDs, tags, line breaks, numbers and units.\n\n{}",s.style,serde_json::to_string_pretty(&input).unwrap()); + let url = format!("{}/chat/completions", s.base_url.trim_end_matches('/')); + let body = json!({"model":s.model,"messages":[{"role":"system","content":"You are a Minecraft modpack localization assistant. Translate only player-facing English into natural Simplified Chinese. Never modify opaque placeholders."},{"role":"user","content":prompt}],"temperature":0.2,"response_format":{"type":"json_object"}}); + let mut last = String::new(); + for attempt in 0..3 { + match client + .post(&url) + .bearer_auth(&s.api_key) + .json(&body) + .send() + .await + { + Ok(r) => { + if !r.status().is_success() { + last = format!( + "HTTP {}: {}", + r.status(), + r.text().await.unwrap_or_default() + ); + } else { + let v: Value = r.json().await.map_err(|e| e.to_string())?; + let content = v + .pointer("/choices/0/message/content") + .and_then(Value::as_str) + .ok_or("DeepSeek 返回内容为空")?; + let map: HashMap = serde_json::from_str(content) + .map_err(|e| format!("DeepSeek 返回的 JSON 无效:{e}"))?; + if batch.iter().all(|x| map.contains_key(&x.id)) { + return Ok(map); + } + last = "DeepSeek 返回内容缺少条目".into(); + } + } + Err(e) => last = e.to_string(), + } + tokio::time::sleep(std::time::Duration::from_millis(800 * (attempt + 1))).await; + } + Err(last) +} +pub async fn translate(app: AppHandle, data_dir: PathBuf, payload: Value) -> Result<(), String> { + let q = PathBuf::from(payload["quests_dir"].as_str().ok_or("缺少任务书目录")?); + let m = mode(&q)?; + let mut settings = crate_root::storage::load_settings(&data_dir); + for k in [ + "api_key", + "base_url", + "model", + "style", + "batch_size", + "concurrency", + ] { + if let Some(v) = payload[k].as_str() { + match k { + "api_key" => settings.api_key = v.into(), + "base_url" => settings.base_url = v.into(), + "model" => settings.model = v.into(), + "style" => settings.style = v.into(), + "batch_size" => settings.batch_size = v.into(), + _ => settings.concurrency = v.into(), + } + } + } + if settings.api_key.trim().is_empty() { + return Err("请先保存 DeepSeek API Key".into()); + } + let mut items = vec![]; + let mut lang = None; + let mut chapter_segs = vec![]; + if m == "lang" { + let map = snbt::load(&q.join("lang/en_us.snbt"))?; + for (k, v) in &map { + let source = match v { + LangValue::Text(x) => x.clone(), + LangValue::Lines(x) => x.join("\n"), + }; + let (p, t) = protect(&source); + items.push(Item { + id: k.clone(), + source, + protected: p, + tokens: t, + }); + } + lang = Some(map) + } else { + for file in chapters::files(&q) { + for s in chapters::extract(&file)? { + let (p, t) = protect(&s.source); + items.push(Item { + id: s.cache_id.clone(), + source: s.source.clone(), + protected: p, + tokens: t, + }); + chapter_segs.push(s) + } + } + } + let mut cache = load_cache(&q); + let mut results = HashMap::new(); + let mut pending = vec![]; + let mut hits = 0; + for x in items.clone() { + if let Some(v) = cache.get(&cache_key(&x.source, &settings)) { + results.insert(x.id, v.clone()); + hits += 1 + } else { + pending.push(x) + } + } + let bs = parse_auto(&settings.batch_size, 25)?; + let concurrency = parse_auto(&settings.concurrency, 6)?.min(12); + let batches = pending.chunks(bs).map(|x| x.to_vec()).collect::>(); + let total = pending.len(); + let client = Client::new(); + let app2 = app.clone(); + let settings2 = settings.clone(); + let stream = stream::iter(batches.into_iter().map(|batch| { + let c = client.clone(); + let s = settings2.clone(); + async move { + let r = request(&c, &s, &batch).await; + (batch, r) + } + })) + .buffer_unordered(concurrency); + tokio::pin!(stream); + let mut failed = vec![]; + while let Some((batch, r)) = stream.next().await { + match r { + Ok(map) => { + for x in batch { + let raw = map.get(&x.id).cloned().unwrap_or(x.protected); + let restored = restore(&raw, &x.tokens); + results.insert(x.id, restored); + } + } + Err(e) => { + for x in batch { + failed.push(format!("{}: {e}", x.id)); + results.insert(x.id, x.source); + } + } + } + let done = results.len().saturating_sub(hits); + let _ = app2.emit( + "translation-event", + json!({"type":"progress","stage":"translating","done":done,"total":total}), + ); + } + let backup = backup(&q, m)?; + let mut warns = BTreeMap::new(); + let mut details = BTreeMap::new(); + for x in &items { + let translated = results.get(&x.id).unwrap_or(&x.source); + let w = warnings(&x.source, translated); + if !w.is_empty() { + warns.insert(x.id.clone(), w); + details.insert(x.id.clone(), json!({"source":x.source,"failed":translated})); + results.insert(x.id.clone(), x.source.clone()); + } else if translated != &x.source { + cache.insert(cache_key(&x.source, &settings), translated.clone()); + } + } + let mut outputs = vec![]; + let (source_file, target_file) = if let Some(mut map) = lang { + for (k, v) in &mut map { + if let Some(t) = results.get(k) { + *v = match v { + LangValue::Text(_) => LangValue::Text(t.clone()), + LangValue::Lines(_) => { + LangValue::Lines(t.split('\n').map(str::to_string).collect()) + } + } + } + } + let target = q.join("lang/zh_cn.snbt"); + snbt::write(&target, &map)?; + let content = fs::read_to_string(&target).map_err(|e| e.to_string())?; + outputs.push(("lang/zh_cn.snbt".into(), content, json!({}))); + (q.join("lang/en_us.snbt"), target) + } else { + let mut by_file: HashMap> = HashMap::new(); + for s in chapter_segs { + by_file + .entry(s.path) + .or_default() + .push((s.index, results[&s.cache_id].clone())); + } + for (file, r) in by_file { + chapters::replace(&file, &r)?; + outputs.push(( + format!("chapters/{}", file.file_name().unwrap().to_string_lossy()), + fs::read_to_string(file).map_err(|e| e.to_string())?, + json!({}), + )); + } + (q.join("chapters"), q.join("chapters")) + }; + save_cache(&q, &cache)?; + let report = Report { + source_file: source_file.display().to_string(), + target_file: target_file.display().to_string(), + backup_dir: backup.display().to_string(), + total_entries: items.len(), + translated_entries: items.len() - failed.len(), + cache_hits: hits, + failed_entries: failed, + warnings: warns, + failed_translations: details, + }; + let rv = serde_json::to_value(&report).unwrap(); + let id = History::new(&data_dir)?.insert(&q, m, &settings, &rv, &outputs)?; + fs::create_dir_all(q.join(".ftb-translater")).map_err(|e| e.to_string())?; + fs::write( + q.join(".ftb-translater/report-latest.json"), + serde_json::to_vec_pretty(&rv).unwrap(), + ) + .map_err(|e| e.to_string())?; + app.emit( + "translation-event", + json!({"type":"done","report":report,"run_id":id}), + ) + .map_err(|e| e.to_string())?; + Ok(()) +} + +pub fn save_review(v: &Value) -> Result { + let target = PathBuf::from(v["target_file"].as_str().unwrap_or("")); + let key = v["key"].as_str().unwrap_or(""); + let text = v["text"].as_str().unwrap_or("").trim(); + if text.is_empty() { + return Err("译文不能为空".into()); + } + if target.is_file() { + let mut map = snbt::load(&target)?; + let entry = map + .iter_mut() + .find(|x| x.0 == key) + .ok_or("目标文件中找不到此条目")?; + entry.1 = LangValue::Text(text.into()); + snbt::write(&target, &map)?; + } else { + let p = key.splitn(3, ':').collect::>(); + if p.len() != 3 { + return Err("章节条目标识无效".into()); + } + chapters::replace( + &target.join(p[0]), + &[(p[1].parse().map_err(|_| "章节序号无效")?, text.into())], + )?; + } + Ok(json!({"saved":true})) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + #[test] + fn protects_format_tokens() { + let src = "Use &e%s&r on in assets/mod/textures/a.png\\n"; + let (p, t) = protect(src); + assert!(!p.contains("minecraft:stone")); + assert_eq!(restore(&p, &t), src); + assert!(warnings( + src, + "使用 &e%s&r 于 ,位于 assets/mod/textures/a.png\\n" + ) + .is_empty()); + } + #[test] + fn rejects_missing_token() { + assert!(!warnings("Use %s and &eGold&r", "使用黄金").is_empty()) + } + #[test] + fn rejects_changed_json_shape() { + let a = r#"{"text":"Hello","color":"red"}"#; + let b = r#"{"text":"你好","color":"blue"}"#; + assert!(!warnings(a, b).is_empty()) + } + #[test] + fn scans_lang_pack() { + let d = tempdir().unwrap(); + let q = d.path().join("config/ftbquests/quests/lang"); + fs::create_dir_all(&q).unwrap(); + fs::write(q.join("en_us.snbt"), "{ title: \"Hello\" }").unwrap(); + let result = scan(&json!({"path":d.path(),"batch_size":"auto"})).unwrap(); + assert_eq!(result["entry_count"], 1); + assert_eq!(result["mode"], "lang"); + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..f80e10b --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,65 @@ +mod chapters; +mod core; +mod snbt; +mod storage; + +use serde_json::{json, Value}; +use std::path::PathBuf; +use storage::History; +use tauri::{Emitter, Manager}; + +fn data_dir(app: &tauri::AppHandle) -> Result { + app.path().app_data_dir().map_err(|e| e.to_string()) +} + +#[tauri::command] +fn bridge(app: tauri::AppHandle, command: String, payload: Option) -> Result { + let v = payload.unwrap_or_else(|| json!({})); + let dir = data_dir(&app)?; + match command.as_str() { + "scan" => core::scan(&v), + "settings" => serde_json::to_value(storage::load_settings(&dir)).map_err(|e| e.to_string()), + "save-settings" => storage::save_settings(&dir, &v), + "history-list" => History::new(&dir)?.list(), + "history-delete" => { + History::new(&dir)?.delete(v["run_id"].as_i64().ok_or("缺少历史编号")?)?; + Ok(json!({"deleted":true})) + } + "history-export" => { + History::new(&dir)?.export( + v["run_id"].as_i64().ok_or("缺少历史编号")?, + std::path::Path::new(v["path"].as_str().ok_or("缺少导出路径")?), + )?; + Ok(json!({"path":v["path"]})) + } + "save-review" => core::save_review(&v), + _ => Err(format!("未知命令:{command}")), + } +} + +#[tauri::command] +fn start_translation(app: tauri::AppHandle, payload: Value) -> Result<(), String> { + let dir = data_dir(&app)?; + let task_app = app.clone(); + tauri::async_runtime::spawn(async move { + if let Err(e) = core::translate(task_app.clone(), dir, payload).await { + let _ = task_app.emit("translation-event", json!({"type":"error","message":e})); + } + }); + Ok(()) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .invoke_handler(tauri::generate_handler![bridge, start_translation]) + .setup(|app| { + if let Some(w) = app.get_webview_window("main") { + let _ = w.set_title("FTB Translater — 任务书汉化"); + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running FTB Translater") +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..a2427f7 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + ftb_translater_lib::run(); +} diff --git a/src-tauri/src/snbt.rs b/src-tauri/src/snbt.rs new file mode 100644 index 0000000..2e2081f --- /dev/null +++ b/src-tauri/src/snbt.rs @@ -0,0 +1,188 @@ +use serde::{Deserialize, Serialize}; +use std::{fs, path::Path}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum LangValue { + Text(String), + Lines(Vec), +} + +pub type LangMap = Vec<(String, LangValue)>; + +struct Parser<'a> { + text: &'a str, + pos: usize, +} +impl<'a> Parser<'a> { + fn new(text: &'a str) -> Self { + Self { text, pos: 0 } + } + fn peek(&self) -> Option { + self.text[self.pos..].chars().next() + } + fn bump(&mut self) -> Option { + let c = self.peek()?; + self.pos += c.len_utf8(); + Some(c) + } + fn skip(&mut self) { + loop { + while self.peek().is_some_and(char::is_whitespace) { + self.bump(); + } + if self.text[self.pos..].starts_with("//") || self.peek() == Some('#') { + while self.peek().is_some_and(|c| c != '\n') { + self.bump(); + } + } else { + break; + } + } + } + fn expect(&mut self, c: char) -> Result<(), String> { + if self.bump() == Some(c) { + Ok(()) + } else { + Err(format!("SNBT 在偏移 {} 处缺少 {c}", self.pos)) + } + } + fn string(&mut self) -> Result { + let q = self.bump().ok_or("字符串意外结束")?; + if q != '\'' && q != '"' { + return Err("需要引号字符串".into()); + } + let mut out = String::new(); + while let Some(c) = self.bump() { + if c == q { + return Ok(out); + } + if c == '\\' { + let e = self.bump().ok_or("转义序列不完整")?; + out.push(match e { + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + x => x, + }) + } else { + out.push(c) + } + } + Err("字符串没有结束引号".into()) + } + fn key(&mut self) -> Result { + if matches!(self.peek(), Some('\'' | '"')) { + return self.string(); + } + let start = self.pos; + while self.peek().is_some_and(|c| !c.is_whitespace() && c != ':') { + self.bump(); + } + if start == self.pos { + Err("缺少 SNBT key".into()) + } else { + Ok(self.text[start..self.pos].to_string()) + } + } + fn value(&mut self) -> Result { + if matches!(self.peek(), Some('\'' | '"')) { + Ok(LangValue::Text(self.string()?)) + } else if self.peek() == Some('[') { + self.bump(); + let mut v = vec![]; + loop { + self.skip(); + if self.peek() == Some(']') { + self.bump(); + break; + } + v.push(self.string()?); + self.skip(); + if self.peek() == Some(',') { + self.bump(); + } + } + Ok(LangValue::Lines(v)) + } else { + Err("语言值必须是字符串或字符串数组".into()) + } + } + fn parse(mut self) -> Result { + self.skip(); + self.expect('{')?; + let mut out = vec![]; + loop { + self.skip(); + if self.peek() == Some('}') { + self.bump(); + break; + } + let k = self.key()?; + self.skip(); + self.expect(':')?; + self.skip(); + out.push((k, self.value()?)); + self.skip(); + if self.peek() == Some(',') { + self.bump(); + } + } + Ok(out) + } +} +pub fn parse(text: &str) -> Result { + Parser::new(text.trim_start_matches('\u{feff}')).parse() +} +pub fn load(path: &Path) -> Result { + parse(&fs::read_to_string(path).map_err(|e| format!("无法读取 {}:{e}", path.display()))?) +} +fn escape(s: &str) -> String { + s.replace('\\', "\\\\") + .replace('"', "\\\"") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") +} +pub fn dump(values: &LangMap) -> String { + let mut out = String::from("{\n"); + for (i, (k, v)) in values.iter().enumerate() { + out.push_str(&format!(" \"{}\": ", escape(k))); + match v { + LangValue::Text(s) => out.push_str(&format!("\"{}\"", escape(s))), + LangValue::Lines(lines) => { + out.push_str("[\n"); + for (j, s) in lines.iter().enumerate() { + out.push_str(&format!( + " \"{}\"{}\n", + escape(s), + if j + 1 < lines.len() { "," } else { "" } + )); + } + out.push_str(" ]"); + } + } + if i + 1 < values.len() { + out.push(',') + } + out.push('\n') + } + out.push_str("}\n"); + out +} +pub fn write(path: &Path, values: &LangMap) -> Result<(), String> { + let text = dump(values); + parse(&text)?; + fs::write(path, text).map_err(|e| format!("无法写入 {}:{e}", path.display())) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn roundtrip() { + let src = "{ title: \"Hello\", desc: [\"A\", \"B\"] }"; + let v = parse(src).unwrap(); + assert_eq!(parse(&dump(&v)).unwrap(), v) + } +} diff --git a/src-tauri/src/storage.rs b/src-tauri/src/storage.rs new file mode 100644 index 0000000..f724895 --- /dev/null +++ b/src-tauri/src/storage.rs @@ -0,0 +1,236 @@ +use chrono::Local; +use rusqlite::{params, Connection}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::{ + fs, + io::Write, + path::{Path, PathBuf}, +}; +use zip::{write::SimpleFileOptions, ZipWriter}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Settings { + pub api_key: String, + pub has_api_key: bool, + pub credential_backend: String, + pub base_url: String, + pub model: String, + pub style: String, + pub batch_size: String, + pub concurrency: String, +} +impl Default for Settings { + fn default() -> Self { + Self { + api_key: String::new(), + has_api_key: false, + credential_backend: "系统凭证管理器".into(), + base_url: "https://api.deepseek.com".into(), + model: "deepseek-chat".into(), + style: "自然玩家向简体中文汉化".into(), + batch_size: "auto".into(), + concurrency: "auto".into(), + } + } +} +#[derive(Serialize, Deserialize)] +struct Config { + base_url: String, + model: String, + style: String, + batch_size: String, + concurrency: String, +} +fn entry() -> Result { + keyring::Entry::new("ftb-translater", "deepseek_api_key").map_err(|e| e.to_string()) +} +pub fn load_settings(dir: &Path) -> Settings { + let mut s = Settings::default(); + if let Ok(raw) = fs::read_to_string(dir.join("settings.json")) { + if let Ok(c) = serde_json::from_str::(&raw) { + s.base_url = c.base_url; + s.model = c.model; + s.style = c.style; + s.batch_size = c.batch_size; + s.concurrency = c.concurrency; + } + } + if let Ok(e) = entry() { + if let Ok(k) = e.get_password() { + s.api_key = k; + s.has_api_key = true + } + } + s +} +pub fn save_settings(dir: &Path, v: &Value) -> Result { + let parse = |k: &str| -> Result { + let x = v + .get(k) + .and_then(Value::as_str) + .unwrap_or("auto") + .trim() + .to_string(); + if ["batch_size", "concurrency"].contains(&k) + && x != "auto" + && x.parse::().ok().filter(|n| *n > 0).is_none() + { + return Err(format!("{k} 必须是 auto 或正整数")); + } + Ok(x) + }; + fs::create_dir_all(dir).map_err(|e| e.to_string())?; + let key = v["api_key"].as_str().unwrap_or("").trim(); + let e = entry()?; + if key.is_empty() { + let _ = e.delete_credential(); + } else { + e.set_password(key) + .map_err(|e| format!("无法保存系统凭证:{e}"))?; + } + let c = Config { + base_url: parse("base_url")?, + model: parse("model")?, + style: parse("style")?, + batch_size: parse("batch_size")?, + concurrency: parse("concurrency")?, + }; + fs::write( + dir.join("settings.json"), + serde_json::to_vec_pretty(&c).unwrap(), + ) + .map_err(|e| e.to_string())?; + Ok(json!({"credential_backend":"系统凭证管理器"})) +} + +pub struct History { + path: PathBuf, +} +impl History { + pub fn new(dir: &Path) -> Result { + fs::create_dir_all(dir).map_err(|e| e.to_string())?; + let h = Self { + path: dir.join("history.sqlite3"), + }; + h.conn()?; + Ok(h) + } + fn conn(&self) -> Result { + let c = Connection::open(&self.path).map_err(|e| e.to_string())?; + c.execute_batch("PRAGMA foreign_keys=ON;CREATE TABLE IF NOT EXISTS translation_runs(id INTEGER PRIMARY KEY,quests_dir TEXT,pack_name TEXT,mode TEXT,model TEXT,style TEXT,total_entries INTEGER,translated_entries INTEGER,cache_hits INTEGER,failed_count INTEGER,warning_count INTEGER,created_at TEXT);CREATE TABLE IF NOT EXISTS translation_files(id INTEGER PRIMARY KEY,run_id INTEGER,filename TEXT,mapping TEXT,output_content TEXT,FOREIGN KEY(run_id) REFERENCES translation_runs(id) ON DELETE CASCADE);").map_err(|e|e.to_string())?; + Ok(c) + } + pub fn insert( + &self, + quests: &Path, + mode: &str, + settings: &Settings, + report: &Value, + outputs: &[(String, String, Value)], + ) -> Result { + let mut c = self.conn()?; + let tx = c.transaction().map_err(|e| e.to_string())?; + tx.execute("INSERT INTO translation_runs(quests_dir,pack_name,mode,model,style,total_entries,translated_entries,cache_hits,failed_count,warning_count,created_at)VALUES(?,?,?,?,?,?,?,?,?,?,?)",params![quests.display().to_string(),pack_name(quests),mode,settings.model,settings.style,report["total_entries"].as_i64(),report["translated_entries"].as_i64(),report["cache_hits"].as_i64(),report["failed_entries"].as_array().map_or(0,Vec::len)as i64,report["warnings"].as_object().map_or(0,|x|x.len())as i64,Local::now().to_rfc3339()]).map_err(|e|e.to_string())?; + let id = tx.last_insert_rowid(); + for (name, content, map) in outputs { + tx.execute("INSERT INTO translation_files(run_id,filename,mapping,output_content)VALUES(?,?,?,?)",params![id,name,map.to_string(),content]).map_err(|e|e.to_string())?; + } + tx.commit().map_err(|e| e.to_string())?; + Ok(id) + } + pub fn list(&self) -> Result { + let c = self.conn()?; + let mut q=c.prepare("SELECT id,pack_name,quests_dir,mode,model,style,total_entries,translated_entries,cache_hits,failed_count,warning_count,created_at FROM translation_runs ORDER BY created_at DESC,id DESC LIMIT 100").map_err(|e|e.to_string())?; + let rows=q.query_map([],|r|Ok(json!({"id":r.get::<_,i64>(0)?,"pack_name":r.get::<_,String>(1)?,"quests_dir":r.get::<_,String>(2)?,"mode":r.get::<_,String>(3)?,"model":r.get::<_,String>(4)?,"style":r.get::<_,String>(5)?,"total_entries":r.get::<_,i64>(6)?,"translated_entries":r.get::<_,i64>(7)?,"cache_hits":r.get::<_,i64>(8)?,"failed_count":r.get::<_,i64>(9)?,"warning_count":r.get::<_,i64>(10)?,"created_at":r.get::<_,String>(11)?}))).map_err(|e|e.to_string())?; + Ok(Value::Array(rows.filter_map(Result::ok).collect())) + } + pub fn delete(&self, id: i64) -> Result<(), String> { + let c = self.conn()?; + c.execute("DELETE FROM translation_runs WHERE id=?", [id]) + .map_err(|e| e.to_string())?; + Ok(()) + } + pub fn export(&self, id: i64, dest: &Path) -> Result<(), String> { + let c = self.conn()?; + let mode: String = c + .query_row("SELECT mode FROM translation_runs WHERE id=?", [id], |r| { + r.get(0) + }) + .map_err(|e| e.to_string())?; + let mut q = c + .prepare("SELECT filename,output_content FROM translation_files WHERE run_id=?") + .map_err(|e| e.to_string())?; + let rows = q + .query_map([id], |r| { + Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)) + }) + .map_err(|e| e.to_string())?; + let file = fs::File::create(dest).map_err(|e| e.to_string())?; + let mut zip = ZipWriter::new(file); + let opts = + SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated); + zip.start_file("manifest.json", opts) + .map_err(|e| e.to_string())?; + zip.write_all(json!({"run_id":id,"mode":mode}).to_string().as_bytes()) + .map_err(|e| e.to_string())?; + for row in rows { + let (name, content) = row.map_err(|e| e.to_string())?; + if name.contains("..") || name.starts_with('/') { + return Err("历史文件路径不安全".into()); + } + zip.start_file(name, opts).map_err(|e| e.to_string())?; + zip.write_all(content.as_bytes()) + .map_err(|e| e.to_string())?; + } + zip.finish().map_err(|e| e.to_string())?; + Ok(()) + } +} +fn pack_name(q: &Path) -> String { + q.ancestors() + .find(|p| p.file_name().is_some_and(|n| n == "config")) + .and_then(Path::parent) + .and_then(Path::file_name) + .map(|x| x.to_string_lossy().into_owned()) + .unwrap_or_else(|| { + q.file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned() + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[test] + fn history_roundtrip_and_export() { + let d = tempdir().unwrap(); + let history = History::new(d.path()).unwrap(); + let report = json!({ + "total_entries": 2, + "translated_entries": 2, + "cache_hits": 1, + "failed_entries": [], + "warnings": {} + }); + let id = history + .insert( + Path::new("/packs/demo/config/ftbquests/quests"), + "lang", + &Settings::default(), + &report, + &[("lang/zh_cn.snbt".into(), "{\"a\":\"甲\"}".into(), json!({}))], + ) + .unwrap(); + assert_eq!(history.list().unwrap().as_array().unwrap().len(), 1); + let archive = d.path().join("translation.zip"); + history.export(id, &archive).unwrap(); + assert!(archive.is_file()); + history.delete(id).unwrap(); + assert!(history.list().unwrap().as_array().unwrap().is_empty()); + } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..1c4d2ae --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "FTB Translater", + "version": "0.2.0", + "identifier": "com.openres.ftb-translater", + "build": { "beforeDevCommand": "npm run dev", "devUrl": "http://localhost:1420", "beforeBuildCommand": "npm run build", "frontendDist": "../dist" }, + "app": { + "windows": [{ "title": "FTB Translater", "width": 1240, "height": 820, "minWidth": 940, "minHeight": 680, "decorations": true, "transparent": false }], + "security": { "csp": null } + }, + "bundle": { "active": true, "targets": "all", "shortDescription": "FTB Quests 任务书汉化工具" } +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..d130909 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useMemo, useState } from "react"; +import { createRoot } from "react-dom/client"; +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; +import { open, save } from "@tauri-apps/plugin-dialog"; +import { Archive, ArrowRight, BookOpen, Check, ChevronRight, CircleAlert, Copy, Eye, EyeOff, FileSearch, FolderOpen, History, KeyRound, Languages, Moon, Play, RefreshCw, Save, Settings, ShieldCheck, Sparkles, Sun, Trash2, X } from "lucide-react"; +import "./styles.css"; + +type View = "workbench" | "history" | "settings"; +type Stage = "idle" | "scanned" | "running" | "done" | "error"; +type SettingsData = { api_key:string; has_api_key:boolean; credential_backend:string; base_url:string; model:string; style:string; batch_size:string; concurrency:string }; +type ScanResult = { quests_dir:string; pack_name:string; mode:"lang"|"chapters"; mode_label:string; source:string; entry_count:number; file_count:number; estimated_batches:number }; +type Report = { source_file:string; target_file:string; backup_dir:string; total_entries:number; translated_entries:number; cache_hits:number; failed_entries:string[]; warnings:Record; failed_translations:Record }; +type Run = { id:number; pack_name:string; quests_dir:string; mode:string; model:string; style:string; total_entries:number; translated_entries:number; cache_hits:number; failed_count:number; warning_count:number; created_at:string }; +type TranslationEvent = { type:"progress"|"log"|"done"|"error"; stage?:string; done?:number; total?:number; message?:string; report?:Report; run_id?:number }; + +const defaults: SettingsData = { api_key:"", has_api_key:false, credential_backend:"系统凭证管理器", base_url:"https://api.deepseek.com", model:"deepseek-chat", style:"准确、自然地翻译为简体中文,保留 Minecraft 与模组专有名词。", batch_size:"auto", concurrency:"auto" }; + +async function call(command:string, payload:Record={}) { return invoke("bridge", { command, payload }); } + +function QuestMark({compact=false}:{compact?:boolean}) { + return

; +} + +function App() { + const [view,setView]=useState("workbench"); const [stage,setStage]=useState("idle"); + const [theme,setTheme]=useState<"light"|"dark">(()=>localStorage.theme==="dark"?"dark":"light"); + const [settings,setSettings]=useState(defaults); const [scan,setScan]=useState(null); + const [selectedPath,setSelectedPath]=useState(""); const [busy,setBusy]=useState(false); const [progress,setProgress]=useState(0); + const [logs,setLogs]=useState([]); const [report,setReport]=useState(null); const [runs,setRuns]=useState([]); + const [toast,setToast]=useState(""); const [confirm,setConfirm]=useState(false); + + useEffect(()=>{ document.documentElement.dataset.theme=theme; localStorage.theme=theme; },[theme]); + useEffect(()=>{ call("settings").then(setSettings).catch(e=>notify(String(e))); },[]); + useEffect(()=>{ const unlisten=listen("translation-event",({payload:e})=>{ + if(e.type==="log"&&e.message) setLogs(v=>[...v.slice(-99),e.message!]); + if(e.type==="progress") { setProgress(e.total?Math.min(100,Math.round((e.done||0)/e.total*100)):100); } + if(e.type==="done"&&e.report) { setBusy(false); setProgress(100); setStage("done"); setReport(e.report); setLogs(v=>[...v,"翻译完成,输出与备份均已写入。"]); notify("任务书汉化完成"); loadHistory(); } + if(e.type==="error") { setBusy(false); setStage("error"); notify(e.message||"翻译失败"); } + }); return()=>{unlisten.then(fn=>fn())}; },[]); + const notify=(text:string)=>{setToast(text); window.setTimeout(()=>setToast(""),3200)}; + const loadHistory=()=>call("history-list").then(setRuns).catch(e=>notify(String(e))); + useEffect(()=>{if(view==="history")loadHistory()},[view]); + + async function chooseFolder(){ const value=await open({directory:true,multiple:false,title:"选择整合包或 FTB Quests 目录"}); if(typeof value==="string"){setSelectedPath(value); await doScan(value)} } + async function doScan(path=selectedPath){ if(!path.trim())return notify("请先选择整合包目录"); setBusy(true); setReport(null); try { const result=await call("scan",{path,batch_size:settings.batch_size}); setScan(result); setSelectedPath(result.quests_dir); setStage("scanned"); setProgress(0); setLogs([`已找到 ${result.entry_count} 条可翻译文本。`,`源目录:${result.source}`]); } catch(e){setStage("error");notify(String(e))} finally{setBusy(false)} } + async function beginTranslation(){setConfirm(false);if(!scan)return;setBusy(true);setStage("running");setProgress(0);setLogs(["正在启动安全翻译任务…"]);try{await invoke("start_translation",{payload:{quests_dir:scan.quests_dir,...settings}})}catch(e){setBusy(false);setStage("error");notify(String(e))}} + async function saveSettings(){try{const r=await call<{credential_backend:string}>("save-settings",settings);setSettings(v=>({...v,has_api_key:!!v.api_key,credential_backend:r.credential_backend}));notify("设置已保存")}catch(e){notify(String(e))}} + const warningCount=report?Object.keys(report.warnings).length:0; + + return
+ +
+ {view==="workbench"&&doScan()} onTranslate={()=>setConfirm(true)} onSettings={()=>setView("settings")}/>} + {view==="settings"&&} + {view==="history"&&} +
+ {confirm&&scan&&setConfirm(false)} onConfirm={beginTranslation}/>} + {toast&&
{toast}
} +
+} + +function Nav({active,icon,label,onClick,badge}:{active:boolean;icon:React.ReactNode;label:string;onClick:()=>void;badge?:number}){return } + +function Workbench(p:{stage:Stage;scan:ScanResult|null;path:string;setPath:(v:string)=>void;busy:boolean;progress:number;logs:string[];report:Report|null;warnings:number;onChoose:()=>void;onScan:()=>void;onTranslate:()=>void;onSettings:()=>void}){ + const steps=[{key:"idle",label:"选择任务书"},{key:"scanned",label:"确认内容"},{key:"running",label:"自动汉化"},{key:"done",label:"检查结果"}]; const index={idle:0,scanned:1,running:2,done:3,error:p.scan?1:0}[p.stage]; + return
+

TRANSLATION WORKBENCH

把任务书带给中文玩家

选择整合包,确认扫描结果,然后安全写回汉化内容。

{p.stage==="running"?"正在汉化":p.stage==="done"?"本次完成":p.stage==="error"?"需要处理":p.stage==="scanned"?"等待开始":"准备就绪"}
+
{steps.map((s,i)=>
{i:i+1}{s.label}
{i<3&&
}
)}
+
+

任务书位置

整合包根目录或 quests、lang、chapters 目录都可以

p.setPath(e.target.value)} placeholder="选择一个整合包目录…" onKeyDown={e=>e.key==="Enter"&&p.onScan()}/>
{p.scan?:
从扫描开始我们会自动判断任务书格式,不会在扫描时改动文件。
}
+ +
+ {(p.logs.length>0||p.report)&&

运行记录

只显示对排查问题有帮助的信息

实时
{p.logs.map((l,i)=>
{String(i+1).padStart(2,"0")}

{l}

)}
{p.report&&

本次结果

{p.report.translated_entries} / {p.report.total_entries} 条已处理

0}/>0}/>
{p.report.backup_dir}
}
} + {p.report&&p.warnings>0&&} +
+} + +function ScanSummary({scan}:{scan:ScanResult}){return
已识别整合包{scan.pack_name||"FTB Quests"}
{scan.mode_label}
{scan.entry_count.toLocaleString()}可翻译条目
{scan.file_count}{scan.mode==="lang"?"语言文件":"章节文件"}
{scan.estimated_batches}预计请求批次

{scan.source}

} +function Metric({label,value,warn=false}:{label:string;value:number;warn?:boolean}){return
{label}{value}
} + +function ReviewPanel({report}:{report:Report}){const entries=Object.entries(report.warnings);return

MANUAL REVIEW

检查格式告警

守卫已经保留原文。确认颜色码、占位符和换行后,可直接修正写入。

{entries.length} 条待检查
{entries.map(([key,warnings])=>)}
} +function ReviewCard({entryKey,warnings,detail,target}:{entryKey:string;warnings:string[];detail?:{source:string;failed:string;error?:string};target:string}){const [text,setText]=useState(detail?.failed||detail?.source||"");const [status,setStatus]=useState("");async function saveText(){setStatus("正在保存…");try{await call("save-review",{target_file:target,key:entryKey,text});setStatus("已写入目标文件 ✓")}catch(e){setStatus(String(e))}}return
{entryKey}{warnings.length} 个问题

{detail?.source||"未记录原文"}