From 9ff883e2e35871e4c52e885909a6ea09c5c3ba7c Mon Sep 17 00:00:00 2001 From: Developer Date: Sun, 5 Apr 2026 11:45:30 -0700 Subject: [PATCH 1/5] Phase 6: Add Deepgram remote transcription (managed + BYOK modes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New files: - client/deepgram_transcription.py — DeepgramTranscriptionEngine with managed mode (proxy) and BYOK mode (direct Deepgram). Sends raw binary PCM audio over WebSocket, handles both proxy and Deepgram response formats. Modified files: - config/default_config.yaml — Replace remote_processing with new remote section (mode, server_url, auth_token, byok_api_key, deepgram_model, language) - client/config.py — Add migration from old remote_processing config - gui/settings_dialog_qt.py — Replace Remote Processing group with Transcription Mode section (Local/Managed/BYOK radio buttons, login/register dialogs, balance display, model selector) - gui/main_window_qt.py — Select engine based on remote.mode config, add error and credits_low handlers Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/settings.local.json | 9 + DEEPGRAM_PROXY_PLAN.md | 574 +++++++++++++++++++++++++ DEEPGRAM_PROXY_PLAN.md:Zone.Identifier | Bin 0 -> 311 bytes client/config.py | 19 + client/deepgram_transcription.py | 528 +++++++++++++++++++++++ config/default_config.yaml | 13 +- gui/main_window_qt.py | 82 +++- gui/settings_dialog_qt.py | 352 +++++++++++++-- 8 files changed, 1503 insertions(+), 74 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 DEEPGRAM_PROXY_PLAN.md create mode 100644 DEEPGRAM_PROXY_PLAN.md:Zone.Identifier create mode 100644 client/deepgram_transcription.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..d05eeeb --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(python3:*)", + "Bash(node --check:*)", + "Bash(ls:*)" + ] + } +} diff --git a/DEEPGRAM_PROXY_PLAN.md b/DEEPGRAM_PROXY_PLAN.md new file mode 100644 index 0000000..6219649 --- /dev/null +++ b/DEEPGRAM_PROXY_PLAN.md @@ -0,0 +1,574 @@ +# Deepgram Proxy Service — Build Plan + +## Project Overview + +Build a standalone hosted service that acts as a Deepgram proxy for the Local Transcription +desktop app. Users can either provide their own Deepgram API key (BYOK) or use the managed +service with prepaid credits purchased via Stripe. + +This is a **separate repository** from `local-transcription`. The desktop app will be updated +in a second phase to support both modes. + +--- + +## Repository Structure + +``` +transcription-proxy/ +├── src/ +│ ├── server.js # Express app entry point +│ ├── config.js # Environment config loader +│ ├── db/ +│ │ ├── index.js # node-postgres pool setup +│ │ └── migrations/ # SQL migration files (numbered) +│ │ ├── 001_users.sql +│ │ ├── 002_credits.sql +│ │ ├── 003_sessions.sql +│ │ └── 004_usage_ledger.sql +│ ├── middleware/ +│ │ ├── auth.js # JWT verification middleware +│ │ └── rateLimit.js # Per-user rate limiting +│ ├── routes/ +│ │ ├── auth.js # POST /auth/register, /auth/login, /auth/refresh +│ │ ├── billing.js # POST /billing/checkout, GET /billing/balance +│ │ └── account.js # GET /account/me, GET /account/usage +│ ├── websocket/ +│ │ └── proxy.js # WebSocket proxy handler (core feature) +│ └── webhooks/ +│ └── stripe.js # POST /webhooks/stripe +├── web/ # Simple frontend dashboard +│ ├── index.html # Landing / login page +│ ├── dashboard.html # Balance, usage history, buy credits +│ └── assets/ +│ ├── app.js +│ └── style.css +├── .env.example +├── package.json +├── docker-compose.yml # Postgres + app for local dev +└── CLAUDE.md # This file (after renaming) +``` + +--- + +## Technology Stack + +- **Runtime**: Node.js 20+ +- **Framework**: Express 4 +- **WebSocket**: `ws` library (not socket.io — keep it lean) +- **Database**: PostgreSQL 15+ via `pg` (node-postgres) +- **Auth**: JWT via `jsonwebtoken`, passwords hashed with `bcrypt` +- **Payments**: Stripe Node SDK (`stripe`) +- **Environment**: `dotenv` +- **Dev tooling**: `nodemon` for dev, no TypeScript (keep it simple) + +--- + +## Database Schema + +Run migrations in order. Use a simple `schema_migrations` table to track applied migrations. + +### 001_users.sql +```sql +CREATE TABLE schema_migrations ( + version INTEGER PRIMARY KEY, + applied_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + stripe_customer_id TEXT UNIQUE, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); +``` + +### 002_credits.sql +```sql +CREATE TABLE credit_balance ( + user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + seconds_remaining INTEGER NOT NULL DEFAULT 0, + updated_at TIMESTAMPTZ DEFAULT NOW() +); +``` + +### 003_sessions.sql +```sql +CREATE TABLE transcription_sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id), + mode TEXT NOT NULL CHECK (mode IN ('managed', 'byok')), + started_at TIMESTAMPTZ DEFAULT NOW(), + ended_at TIMESTAMPTZ, + seconds_used INTEGER NOT NULL DEFAULT 0, + deepgram_model TEXT, + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'completed', 'terminated')) +); + +CREATE INDEX idx_sessions_user_id ON transcription_sessions(user_id); +CREATE INDEX idx_sessions_started_at ON transcription_sessions(started_at); +``` + +### 004_usage_ledger.sql +```sql +CREATE TABLE usage_ledger ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id), + session_id UUID REFERENCES transcription_sessions(id), + recorded_at TIMESTAMPTZ DEFAULT NOW(), + seconds INTEGER NOT NULL, + description TEXT -- e.g. 'session_usage', 'credit_purchase', 'manual_adjustment' +); + +CREATE INDEX idx_ledger_user_id ON usage_ledger(user_id); +``` + +--- + +## Environment Variables (.env.example) + +```env +# Server +PORT=3000 +NODE_ENV=development + +# Database +DATABASE_URL=postgresql://user:password@localhost:5432/transcription_proxy + +# Auth +JWT_SECRET=changeme_use_long_random_string +JWT_EXPIRY=7d + +# Stripe +STRIPE_SECRET_KEY=sk_test_... +STRIPE_WEBHOOK_SECRET=whsec_... + +# Deepgram +DEEPGRAM_API_KEY=your_deepgram_key_here + +# Pricing (seconds per dollar — adjust for your margin) +# Default: 1000 seconds per $1 = $0.006/min managed cost covered + margin +CREDITS_PER_DOLLAR=1000 +``` + +--- + +## Phase 1 — Core Server & Auth + +### Goals +- Working Express app with Postgres connection +- Migration runner +- User registration and login +- JWT middleware + +### Tasks + +1. **Scaffold project** + - `npm init`, install dependencies: `express ws pg jsonwebtoken bcrypt stripe dotenv` + - Dev dependencies: `nodemon` + - Add `start` and `dev` scripts to package.json + +2. **Database connection** (`src/db/index.js`) + - Export a `pg.Pool` instance using `DATABASE_URL` + - Export a `migrate()` function that reads `src/db/migrations/*.sql` in order, + checks `schema_migrations` table, and applies unapplied ones + - Call `migrate()` on server startup before listening + +3. **Auth routes** (`src/routes/auth.js`) + - `POST /auth/register` — validate email/password, hash password with bcrypt (cost 12), + insert user, insert empty credit_balance row, return JWT + - `POST /auth/login` — verify credentials, return JWT + refresh token + - `POST /auth/refresh` — validate refresh token, return new JWT + - Passwords: minimum 8 characters, validate email format + +4. **JWT middleware** (`src/middleware/auth.js`) + - Verify `Authorization: Bearer ` header + - Attach `req.user = { id, email }` on success + - Return 401 on failure + - Export as `requireAuth` middleware + +5. **Basic health check** + - `GET /health` returns `{ status: 'ok', db: 'connected' }` + +--- + +## Phase 2 — Billing & Credits + +### Goals +- Stripe Checkout session creation for credit purchases +- Webhook handler to fulfill purchases +- Balance endpoint + +### Payment Methods + +Use **Stripe Dynamic Payment Methods** — do NOT hardcode `payment_method_types` in the +Checkout Session. Instead, leave it unset and manage everything from the Stripe Dashboard. + +Enable the following in the Stripe Dashboard under Settings → Payment Methods: +- **Cards** (Visa, Mastercard, Amex, Discover) — on by default +- **PayPal** — enable manually +- **Apple Pay** — on by default, shows automatically on Safari/iOS +- **Google Pay** — enable manually (one toggle) +- **Cash App Pay** — enable manually (popular with streaming audiences) +- **Link** — Stripe's saved payment network, on by default + +Stripe will automatically show the most relevant methods to each user based on their +location and device. No code changes are needed to add or remove methods in future — +it's all dashboard config. + +### Credit Packages + +Define these as constants in `src/config.js`: + +```javascript +CREDIT_PACKAGES: [ + { id: 'pack_500', label: '500 minutes', seconds: 30000, price_cents: 300 }, + { id: 'pack_1200', label: '1200 minutes', seconds: 72000, price_cents: 600 }, + { id: 'pack_3000', label: '3000 minutes', seconds: 180000, price_cents: 1200 }, +] +``` + +Adjust pricing to cover Deepgram costs ($0.006/min = $0.0001/sec) plus margin and +Stripe fees (~2.9% + $0.30). + +### Tasks + +1. **Stripe customer creation** + - On user registration, create a Stripe customer and store `stripe_customer_id` + - Do this asynchronously (don't block registration response) + +2. **Billing routes** (`src/routes/billing.js`) + - `GET /billing/packages` — return credit package list (no auth required) + - `POST /billing/checkout` — requires auth, accepts `{ package_id }`, + creates Stripe Checkout Session using dynamic payment methods (do NOT pass + `payment_method_types` — omitting it enables dynamic methods automatically), + include `payment_intent_data.metadata` containing `user_id` and `package_id`, + returns `{ checkout_url }` + - `GET /billing/balance` — requires auth, returns `{ seconds_remaining, minutes_remaining }` + +3. **Stripe webhook** (`src/webhooks/stripe.js`) + - Mount at `POST /webhooks/stripe` with raw body (use `express.raw()` for this route only) + - Verify signature with `stripe.webhooks.constructEvent()` + - Handle `checkout.session.completed`: + - Extract `user_id` and `package_id` from metadata + - Add seconds to `credit_balance` + - Insert row into `usage_ledger` with description `'credit_purchase'` + - Handle `payment_intent.payment_failed`: log it (no action needed for prepaid) + +4. **Success/cancel pages** + - Stripe Checkout redirects to `GET /billing/success?session_id=...` and `/billing/cancel` + - These can be simple HTML responses or redirects to the web dashboard + +--- + +## Phase 3 — WebSocket Proxy (Core Feature) + +This is the most critical component. The proxy sits between the desktop client and Deepgram, +forwarding audio while tracking usage in real time. + +### Connection Flow + +``` +Client connects → validate JWT → check credit balance → open Deepgram upstream + ↓ +Audio chunks arrive → forward to Deepgram → record usage every 5 seconds + ↓ +Transcription arrives from Deepgram → forward to client + ↓ +Client disconnects (or credits exhausted) → close upstream → finalize session +``` + +### WebSocket Protocol + +**Client connects to**: `wss://your-domain/ws/transcribe` + +**Client sends as first message** (JSON): +```json +{ + "type": "auth", + "token": "", + "config": { + "model": "nova-2", + "language": "en-US", + "interim_results": true, + "endpointing": 300 + } +} +``` + +**After auth success, client sends**: raw audio binary frames (PCM 16kHz mono) + +**Server sends to client**: +```json +{ "type": "ready" } +{ "type": "transcript", "text": "...", "is_final": true, "confidence": 0.98 } +{ "type": "error", "code": "insufficient_credits", "message": "..." } +{ "type": "credits_low", "seconds_remaining": 300 } +{ "type": "session_end", "seconds_used": 120 } +``` + +### Tasks (`src/websocket/proxy.js`) + +1. **Upgrade handler** + - Attach to the HTTP server using `ws.Server({ noServer: true })` + - In `server.on('upgrade', ...)`, route `/ws/transcribe` to this handler + +2. **Auth handshake** + - First message must be `{ type: 'auth', token: '...' }` — received within 5 seconds + or connection is terminated + - Verify JWT, load user's credit balance from DB + - If balance is 0 or negative, send `insufficient_credits` error and close + +3. **Deepgram upstream connection** + - Open a WebSocket to Deepgram's streaming API: + `wss://api.deepgram.com/v1/listen?model=nova-2&language=en-US&interim_results=true` + - Auth header: `Authorization: Token ` + - Use query params from client's `config` object (whitelist allowed params) + +4. **Audio forwarding** + - All binary messages from client → forward directly to Deepgram upstream + - All messages from Deepgram → parse JSON, reformat, forward to client + +5. **Usage tracking** + - Create a `transcription_sessions` row on connection + - Maintain an in-memory `secondsUsed` counter per connection + - Deepgram sends `{ type: 'Results', duration: X }` in responses — use this for + accurate second counting + - Every 10 seconds (or on disconnect), write current `secondsUsed` to DB: + - Update `transcription_sessions.seconds_used` + - Decrement `credit_balance.seconds_remaining` + - Insert into `usage_ledger` + - If `seconds_remaining` hits 0: send `insufficient_credits`, close connection + +6. **Cleanup on disconnect** + - Mark session as `completed`, set `ended_at` + - Do final usage flush to DB + - Close Deepgram upstream if still open + +7. **Error handling** + - If Deepgram upstream closes unexpectedly, notify client and close + - If client sends malformed data, log and continue (don't crash) + +--- + +## Phase 4 — Account Routes & Rate Limiting + +### Tasks + +1. **Account routes** (`src/routes/account.js`) + - `GET /account/me` — returns `{ email, credits: { seconds_remaining, minutes_remaining }, created_at }` + - `GET /account/usage` — returns last 30 days of `usage_ledger` entries grouped by day, + plus list of last 10 sessions with duration + +2. **Rate limiting** (`src/middleware/rateLimit.js`) + - Use in-memory rate limiting (no Redis needed at this scale) + - Auth endpoints: max 10 requests per minute per IP + - WebSocket connections: max 2 concurrent connections per user + (store active connections in a `Map>`) + +--- + +## Phase 5 — Web Dashboard + +A simple, functional HTML/CSS/JS dashboard. No framework — vanilla JS is fine. +This is a developer-friendly streamer tool, not a consumer SaaS, so clean and +functional beats flashy. + +### Pages + +**`/` (Landing / Login)** +- Brief product description (what this is, why it exists) +- Login form and link to register +- Link to GitHub/Gitea repo + +**`/dashboard` (Post-login)** +- Current credit balance (minutes remaining, prominently displayed) +- "Buy Credits" section showing the three packages with Stripe Checkout buttons +- Usage chart: last 30 days bar chart (vanilla canvas or a small CDN chart lib) +- Recent sessions table: date, duration, status + +**`/register`** +- Registration form + +### Implementation Notes +- Store JWT in `localStorage`, attach as `Authorization` header on API calls +- Redirect to `/` if JWT missing or expired +- Keep CSS minimal but readable — this is a utility dashboard + +--- + +## Phase 6 — Desktop App Integration + +Changes needed in the `local-transcription` Python repo. + +### New file: `client/remote_transcription.py` + +This module replaces `transcription_engine_realtime.py` when remote mode is active. + +```python +# Pseudocode / spec for Claude Code to implement + +class RemoteTranscriptionEngine: + """ + Connects to the transcription proxy WebSocket and streams audio. + Provides the same callback interface as the local engine so the + rest of the app doesn't need to change. + """ + + def __init__(self, config, on_transcript_callback): + # config contains: server_url, auth_token (or byok_api_key), model + ... + + def start(self): + # Open WebSocket connection + # Send auth message + # Start audio capture thread (reuse existing audio_capture.py) + ... + + def stop(self): + # Close WebSocket gracefully + ... + + def _on_audio_chunk(self, audio_data): + # Called by audio_capture.py with raw PCM data + # Send as binary WebSocket frame + ... + + def _on_server_message(self, message): + # Parse JSON from server + # On type='transcript': call on_transcript_callback + # On type='credits_low': trigger UI warning + # On type='error': surface to user + ... +``` + +### BYOK Mode + +When user provides their own Deepgram key, connect directly to Deepgram instead of the proxy: +- Endpoint: `wss://api.deepgram.com/v1/listen?...` +- Auth: `Authorization: Token ` +- No session tracking (Deepgram handles billing directly to the user) +- Same `RemoteTranscriptionEngine` class, just different URL and auth header + +### Settings Changes (`gui/settings_dialog_qt.py`) + +Add a new "Transcription Mode" section: + +``` +Transcription Mode: + ○ Local (Whisper) [existing behavior] + ○ Remote - Managed [requires login] + ○ Remote - BYOK [requires Deepgram API key] + +[If Managed selected]: + Server URL: [____________] + [Login / Register] [View Balance: 420 min remaining] + +[If BYOK selected]: + Deepgram API Key: [____________] + Model: [nova-2 ▼] +``` + +### Config additions (`config/default_config.yaml`) + +```yaml +remote: + mode: local # local | managed | byok + server_url: "" # proxy server URL for managed mode + auth_token: "" # JWT stored after login + byok_api_key: "" # Deepgram key for BYOK mode + deepgram_model: nova-2 + language: en-US +``` + +--- + +## Build & Deployment Notes + +### Docker Compose (local dev) + +```yaml +version: '3.8' +services: + db: + image: postgres:15 + environment: + POSTGRES_DB: transcription_proxy + POSTGRES_USER: user + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + + app: + build: . + ports: + - "3000:3000" + environment: + DATABASE_URL: postgresql://user:password@db:5432/transcription_proxy + depends_on: + - db + volumes: + - .:/app + - /app/node_modules + +volumes: + pgdata: +``` + +### Production Deployment + +This service is a good fit for deployment on AnHonestHost WHP as a containerized app, +or on a small DigitalOcean/Linode VPS. Requirements are light: +- 512MB RAM is sufficient +- Postgres can be the same instance as other services or managed (e.g., Supabase free tier) +- Needs a public domain with SSL for WebSocket (`wss://`) to work from desktop clients + +Reverse proxy config (Nginx or HAProxy) should: +- Proxy HTTP → `localhost:3000` +- Pass `Upgrade` and `Connection` headers for WebSocket support +- Set `proxy_read_timeout 3600` (sessions can be long) + +--- + +## Implementation Order + +Build and test in this sequence: + +1. Project scaffold + DB connection + migrations +2. Auth (register/login/JWT) — test with curl +3. Stripe billing + webhook — test with Stripe CLI (`stripe listen`) +4. WebSocket proxy — test with a simple browser WebSocket client first +5. Usage tracking and credit decrement +6. Account/usage routes +7. Web dashboard +8. Desktop app integration (separate PR in local-transcription repo) + +--- + +## Key Decisions & Rationale + +| Decision | Choice | Reason | +|---|---|---| +| Credits model | Prepaid | No surprise charges, simpler billing, better for irregular streamer usage | +| WebSocket library | `ws` | Lightweight, no abstraction overhead, plays well with raw binary audio | +| Auth | JWT (stateless) | Desktop app holds token locally; no session store needed | +| DB driver | `node-postgres` (pg) | No ORM overhead; schema is simple enough for raw SQL | +| Migrations | Raw SQL files | No dependency on Knex/Prisma; easy to inspect and reason about | +| Rate limiting | In-memory | Redis is overkill for this scale; single-process Node is fine initially | +| Frontend | Vanilla JS | Dashboard is simple utility UI; no framework justified | + +--- + +## What This Plan Does NOT Cover (Future Work) + +- OAuth / social login +- Admin panel for managing users +- Refund / credit adjustment tooling +- Email verification +- Password reset flow +- Multi-language support beyond Deepgram's defaults +- Analytics / aggregated usage reporting +- Self-hosted Whisper inference as a third backend option diff --git a/DEEPGRAM_PROXY_PLAN.md:Zone.Identifier b/DEEPGRAM_PROXY_PLAN.md:Zone.Identifier new file mode 100644 index 0000000000000000000000000000000000000000..6ccec41f2c158263c403129efb457dace8e20ca6 GIT binary patch literal 311 zcma)$%WA_g6hyl=_%pgx$n3H_)%I0$p~|&2VO5 zhWU6m8ec7FC((Y!(b+zTI*X$gLt@eH>{O55&+dxSeu72d3KZ=}@U-Zq$f`Ucm~?{V zED@xtDn`nbb!%SA1j{6j?v3+*kHD;`u>)xJ4PF`TD3dHh)>nja5f-~Zyx=*>I#%#C za!hkcd!xV5y5GuwPSBGYh6$-KCp{SuM5+m|XYgZvncg0?54t$CM1Y6DX7TglU7(xo Xwz+FJPlu*`*}ol{$IbKVC@y{g!oOnx literal 0 HcmV?d00001 diff --git a/client/config.py b/client/config.py index 73f4e20..3ddda61 100644 --- a/client/config.py +++ b/client/config.py @@ -48,6 +48,25 @@ class Config: # Save the default configuration self.save() + # Migrate remote_processing -> remote + self._migrate_remote_config() + + def _migrate_remote_config(self): + """Migrate old remote_processing config to new remote config.""" + if 'remote_processing' in self.config and 'remote' not in self.config: + old = self.config['remote_processing'] + self.config['remote'] = { + 'mode': 'managed' if old.get('enabled', False) else 'local', + 'server_url': old.get('server_url', ''), + 'auth_token': '', + 'byok_api_key': old.get('api_key', ''), + 'deepgram_model': 'nova-2', + 'language': 'en-US', + 'fallback_to_local': old.get('fallback_to_local', True), + } + del self.config['remote_processing'] + self.save() + def save(self) -> None: """Save current configuration to file.""" with open(self.config_path, 'w') as f: diff --git a/client/deepgram_transcription.py b/client/deepgram_transcription.py new file mode 100644 index 0000000..79556da --- /dev/null +++ b/client/deepgram_transcription.py @@ -0,0 +1,528 @@ +"""Deepgram-based transcription engine using WebSocket streaming. + +Supports two modes: + - Managed mode: connects to a proxy server that handles Deepgram credentials + - BYOK mode: connects directly to the Deepgram API with a user-provided key + +Implements the same duck-type interface as RealtimeTranscriptionEngine so +MainWindow can use it as a drop-in replacement. +""" + +import asyncio +import json +import logging +import numpy as np +import threading +from datetime import datetime +from queue import Queue, Empty +from typing import Optional, Callable + +from client.transcription_engine_realtime import TranscriptionResult + +logger = logging.getLogger(__name__) + + +class DeepgramTranscriptionEngine: + """ + Transcription engine that streams audio to Deepgram via WebSocket. + + In managed mode the connection goes through a proxy at + ``wss:///ws/transcribe`` which handles authentication and + Deepgram credentials. In BYOK (bring-your-own-key) mode the + connection goes directly to the Deepgram API. + """ + + # ------------------------------------------------------------------ # + # Construction / configuration + # ------------------------------------------------------------------ # + + def __init__(self, config, user_name: str = "User", input_device_index: Optional[int] = None): + """ + Initialise the engine from a :class:`client.config.Config` object. + + Args: + config: Application ``Config`` instance. + user_name: Display name attached to transcriptions. + input_device_index: Index of the audio input device to use + (``None`` for the system default). + """ + self.config = config + self.user_name = user_name + self.input_device_index = input_device_index + + # Mode: 'managed' (proxy) or 'byok' (direct Deepgram) + self.mode: str = config.get("remote.mode", "managed") + + # Managed-mode settings + self.server_url: str = config.get("remote.server_url", "") + self.auth_token: str = config.get("remote.auth_token", "") + + # BYOK-mode settings + self.byok_api_key: str = config.get("remote.byok_api_key", "") + + # Deepgram model / language (used in both modes) + self.deepgram_model: str = config.get("remote.deepgram_model", "nova-2") + self.language: str = config.get("remote.language", "en-US") + + # Audio parameters + self.sample_rate: int = 16000 + self.channels: int = 1 + self.blocksize: int = 4096 + + # Callbacks + self.realtime_callback: Optional[Callable[[TranscriptionResult], None]] = None + self.final_callback: Optional[Callable[[TranscriptionResult], None]] = None + self._on_error: Optional[Callable[[str], None]] = None + self._on_credits_low: Optional[Callable[[int], None]] = None + + # Internal state + self._is_initialized: bool = False + self._is_recording: bool = False + self._stop_event: threading.Event = threading.Event() + self._audio_queue: Queue = Queue() + + # Asyncio event loop running in a daemon thread + self._loop: Optional[asyncio.AbstractEventLoop] = None + self._thread: Optional[threading.Thread] = None + + # WebSocket handle (set inside the async context) + self._ws = None + + # sounddevice InputStream + self._stream = None + + # ------------------------------------------------------------------ # + # Callback setters + # ------------------------------------------------------------------ # + + def set_callbacks( + self, + realtime_callback: Optional[Callable[[TranscriptionResult], None]] = None, + final_callback: Optional[Callable[[TranscriptionResult], None]] = None, + ): + """Set transcription result callbacks (matches RealtimeTranscriptionEngine API).""" + self.realtime_callback = realtime_callback + self.final_callback = final_callback + + def set_error_callback(self, fn: Optional[Callable[[str], None]]): + """Set a callback invoked on errors. ``fn`` receives a string message.""" + self._on_error = fn + + def set_credits_low_callback(self, fn: Optional[Callable[[int], None]]): + """Set a callback for low-credit warnings. ``fn`` receives seconds remaining.""" + self._on_credits_low = fn + + # ------------------------------------------------------------------ # + # Public interface (duck-typed with RealtimeTranscriptionEngine) + # ------------------------------------------------------------------ # + + def initialize(self) -> bool: + """Validate configuration and mark the engine as ready. + + Returns ``True`` when the engine is ready to start recording. + """ + if self._is_initialized: + return True + + if self.mode == "managed": + if not self.server_url: + logger.error("Managed mode requires a server URL (remote.server_url)") + return False + if not self.auth_token: + logger.error("Managed mode requires an auth token (remote.auth_token)") + return False + elif self.mode == "byok": + if not self.byok_api_key: + logger.error("BYOK mode requires an API key (remote.byok_api_key)") + return False + else: + logger.error("Unknown remote mode: %s (expected 'managed' or 'byok')", self.mode) + return False + + self._is_initialized = True + logger.info("DeepgramTranscriptionEngine initialised in %s mode", self.mode) + return True + + def start_recording(self) -> bool: + """Open the audio stream and connect the WebSocket. + + Returns ``True`` on success. + """ + if not self._is_initialized: + logger.error("Engine not initialised -- call initialize() first") + return False + + if self._is_recording: + return True + + self._stop_event.clear() + self._is_recording = True + + # Start the asyncio event-loop thread (handles WS send/receive) + self._thread = threading.Thread(target=self._run_event_loop, daemon=True) + self._thread.start() + + # Start the audio capture stream + try: + self._start_audio_stream() + except Exception as exc: + logger.error("Failed to open audio stream: %s", exc) + self._is_recording = False + self._stop_event.set() + return False + + logger.info("Recording started") + return True + + def stop_recording(self): + """Stop audio capture and close the WebSocket.""" + if not self._is_recording: + return + + self._is_recording = False + self._stop_event.set() + + # Stop audio stream + self._stop_audio_stream() + + # Close WebSocket from outside the event-loop thread + if self._ws is not None and self._loop is not None and not self._loop.is_closed(): + asyncio.run_coroutine_threadsafe(self._close_ws(), self._loop) + + # Wait for the thread to finish + if self._thread is not None: + self._thread.join(timeout=5) + self._thread = None + + logger.info("Recording stopped") + + def stop(self): + """Full shutdown -- stop recording and release all resources.""" + self.stop_recording() + self._is_initialized = False + logger.info("DeepgramTranscriptionEngine shut down") + + def is_ready(self) -> bool: + """Return ``True`` if the engine has been successfully initialised.""" + return self._is_initialized + + # ------------------------------------------------------------------ # + # Audio capture (sounddevice) + # ------------------------------------------------------------------ # + + def _start_audio_stream(self): + """Open a ``sounddevice.InputStream`` that feeds the audio queue.""" + import sounddevice as sd + + def _audio_callback(indata, frames, time_info, status): # noqa: ARG001 + if status: + logger.warning("Audio stream status: %s", status) + if self._is_recording: + # float32 -> int16 PCM bytes + pcm = (indata * 32767).astype(np.int16).tobytes() + self._audio_queue.put(pcm) + + self._stream = sd.InputStream( + samplerate=self.sample_rate, + blocksize=self.blocksize, + channels=self.channels, + dtype="float32", + device=self.input_device_index, + callback=_audio_callback, + ) + self._stream.start() + + def _stop_audio_stream(self): + """Close the audio input stream.""" + if self._stream is not None: + try: + self._stream.stop() + self._stream.close() + except Exception as exc: + logger.debug("Error closing audio stream: %s", exc) + finally: + self._stream = None + + # ------------------------------------------------------------------ # + # Asyncio event-loop (runs in daemon thread) + # ------------------------------------------------------------------ # + + def _run_event_loop(self): + """Entry point for the daemon thread -- runs the async event loop.""" + self._loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._loop) + try: + self._loop.run_until_complete(self._ws_lifecycle()) + except Exception as exc: + logger.error("Event-loop error: %s", exc) + finally: + try: + self._loop.run_until_complete(self._loop.shutdown_asyncgens()) + except Exception: + pass + self._loop.close() + self._loop = None + + async def _ws_lifecycle(self): + """Connect, authenticate (if managed), then run send/receive loops.""" + import websockets + + try: + ws_url, extra_headers = self._build_ws_url_and_headers() + + logger.info("Connecting to %s", ws_url) + self._ws = await websockets.connect( + ws_url, + additional_headers=extra_headers, + ping_interval=20, + ping_timeout=10, + ) + + # Managed mode: send auth message and wait for ready + if self.mode == "managed": + if not await self._managed_handshake(): + return + + # Run send and receive concurrently + await asyncio.gather( + self._send_loop(), + self._receive_loop(), + ) + + except asyncio.CancelledError: + pass + except Exception as exc: + msg = f"WebSocket error: {exc}" + logger.error(msg) + if self._on_error: + self._on_error(msg) + finally: + await self._close_ws() + + def _build_ws_url_and_headers(self): + """Return ``(url, headers)`` depending on the current mode.""" + if self.mode == "managed": + # Ensure the server URL uses wss:// and append the path + url = self.server_url.rstrip("/") + if not url.startswith("ws://") and not url.startswith("wss://"): + url = f"wss://{url}" + url = f"{url}/ws/transcribe" + return url, {} + + # BYOK -- connect directly to Deepgram + params = ( + f"model={self.deepgram_model}" + f"&language={self.language}" + "&interim_results=true" + "&encoding=linear16" + f"&sample_rate={self.sample_rate}" + f"&channels={self.channels}" + ) + url = f"wss://api.deepgram.com/v1/listen?{params}" + headers = {"Authorization": f"Token {self.byok_api_key}"} + return url, headers + + # -- managed-mode handshake ---------------------------------------- # + + async def _managed_handshake(self) -> bool: + """Send auth message and wait for ``ready`` (managed mode). + + Returns ``True`` on success. + """ + auth_msg = { + "type": "auth", + "token": self.auth_token, + "config": { + "model": self.deepgram_model, + "language": self.language, + "sample_rate": self.sample_rate, + "channels": self.channels, + "encoding": "linear16", + "interim_results": True, + }, + } + await self._ws.send(json.dumps(auth_msg)) + + try: + raw = await asyncio.wait_for(self._ws.recv(), timeout=15) + data = json.loads(raw) + if data.get("type") == "ready": + logger.info("Managed proxy is ready") + return True + + if data.get("type") == "error": + err = data.get("message", "unknown error") + logger.error("Auth error from proxy: %s", err) + if self._on_error: + self._on_error(f"Proxy auth error: {err}") + return False + + logger.warning("Unexpected handshake message: %s", data) + return False + + except asyncio.TimeoutError: + logger.error("Timed out waiting for proxy ready message") + if self._on_error: + self._on_error("Timed out waiting for proxy ready message") + return False + + # -- send loop ----------------------------------------------------- # + + async def _send_loop(self): + """Drain the audio queue and push raw PCM bytes over the WebSocket.""" + while not self._stop_event.is_set(): + try: + pcm_bytes = self._audio_queue.get(timeout=0.1) + except Empty: + continue + + try: + await self._ws.send(pcm_bytes) + except Exception as exc: + if not self._stop_event.is_set(): + logger.error("Send error: %s", exc) + break + + # -- receive loop -------------------------------------------------- # + + async def _receive_loop(self): + """Listen for messages from the WebSocket and dispatch them.""" + while not self._stop_event.is_set(): + try: + raw = await asyncio.wait_for(self._ws.recv(), timeout=1.0) + except asyncio.TimeoutError: + continue + except Exception as exc: + if not self._stop_event.is_set(): + logger.error("Receive error: %s", exc) + break + + try: + data = json.loads(raw) + except (json.JSONDecodeError, TypeError): + logger.debug("Non-JSON message received, ignoring") + continue + + if self.mode == "managed": + self._handle_managed_message(data) + else: + self._handle_byok_message(data) + + # ------------------------------------------------------------------ # + # Message handlers + # ------------------------------------------------------------------ # + + def _handle_managed_message(self, data: dict): + """Process a message from the managed proxy.""" + msg_type = data.get("type", "") + + if msg_type == "transcript": + text = data.get("text", "") + is_final = data.get("is_final", False) + if text.strip(): + result = TranscriptionResult( + text=text, + is_final=is_final, + timestamp=datetime.now(), + user_name=self.user_name, + ) + if is_final: + if self.final_callback: + self.final_callback(result) + else: + if self.realtime_callback: + self.realtime_callback(result) + + elif msg_type == "credits_low": + seconds_remaining = data.get("seconds_remaining", 0) + logger.warning("Credits low -- %d seconds remaining", seconds_remaining) + if self._on_credits_low: + self._on_credits_low(int(seconds_remaining)) + + elif msg_type == "error": + code = data.get("code", "") + message = data.get("message", "Unknown error") + logger.error("Proxy error [%s]: %s", code, message) + if self._on_error: + self._on_error(f"[{code}] {message}" if code else message) + + elif msg_type == "session_end": + seconds_used = data.get("seconds_used", 0) + logger.info("Session ended -- %d seconds used", seconds_used) + + elif msg_type == "ready": + # May arrive again after reconnects; safe to ignore. + logger.debug("Received ready message (already connected)") + + else: + logger.debug("Unhandled managed message type: %s", msg_type) + + def _handle_byok_message(self, data: dict): + """Process a message received directly from the Deepgram API.""" + msg_type = data.get("type", "") + + if msg_type == "Results": + channel = data.get("channel", {}) + alternatives = channel.get("alternatives", []) + if not alternatives: + return + + transcript = alternatives[0].get("transcript", "") + is_final = data.get("is_final", False) + + if transcript.strip(): + result = TranscriptionResult( + text=transcript, + is_final=is_final, + timestamp=datetime.now(), + user_name=self.user_name, + ) + if is_final: + if self.final_callback: + self.final_callback(result) + else: + if self.realtime_callback: + self.realtime_callback(result) + + elif msg_type == "Metadata": + logger.debug("Deepgram metadata: %s", data) + + elif msg_type == "UtteranceEnd": + logger.debug("Deepgram utterance end") + + else: + logger.debug("Unhandled Deepgram message type: %s", msg_type) + + # ------------------------------------------------------------------ # + # Helpers + # ------------------------------------------------------------------ # + + async def _close_ws(self): + """Close the WebSocket connection if open.""" + if self._ws is not None: + try: + await self._ws.close() + except Exception: + pass + self._ws = None + + def set_user_name(self, user_name: str): + """Update the user name attached to future transcriptions.""" + self.user_name = user_name + + def is_recording_active(self) -> bool: + """Return ``True`` if audio is currently being captured.""" + return self._is_recording + + def __repr__(self) -> str: + return ( + f"DeepgramTranscriptionEngine(mode={self.mode}, " + f"recording={self._is_recording})" + ) + + def __del__(self): + """Best-effort cleanup.""" + try: + self.stop() + except Exception: + pass diff --git a/config/default_config.yaml b/config/default_config.yaml index 135daa3..c4e8f81 100644 --- a/config/default_config.yaml +++ b/config/default_config.yaml @@ -68,11 +68,14 @@ web_server: port: 8080 host: "127.0.0.1" -remote_processing: - enabled: false # Enable remote transcription offloading - server_url: "" # WebSocket URL of remote transcription service (e.g., ws://your-server:8765/ws/transcribe) - api_key: "" # API key for authentication - fallback_to_local: true # Fall back to local processing if remote fails +remote: + mode: local # local | managed | byok + server_url: "" # Proxy server URL for managed mode (e.g., wss://your-proxy.com) + auth_token: "" # JWT stored after login (managed mode) + byok_api_key: "" # Deepgram API key for BYOK mode + deepgram_model: nova-2 # Deepgram model to use + language: en-US # Language code + fallback_to_local: true # Fall back to local Whisper if remote fails updates: auto_check: true # Check for updates on startup diff --git a/gui/main_window_qt.py b/gui/main_window_qt.py index daaaaa4..f9552d1 100644 --- a/gui/main_window_qt.py +++ b/gui/main_window_qt.py @@ -18,6 +18,7 @@ sys.path.append(str(Path(__file__).resolve().parent.parent)) from client.config import Config from client.device_utils import DeviceManager from client.transcription_engine_realtime import RealtimeTranscriptionEngine, TranscriptionResult +from client.deepgram_transcription import DeepgramTranscriptionEngine from client.server_sync import ServerSyncClient from gui.settings_dialog_qt import SettingsDialog from server.web_display import TranscriptionWebServer @@ -394,27 +395,44 @@ class MainWindow(QMainWindow): min_gap = self.config.get('transcription.min_gap_between_recordings', 0.0) min_recording = self.config.get('transcription.min_length_of_recording', 0.5) - self.transcription_engine = RealtimeTranscriptionEngine( - model=model, - device=device, - language=language, - compute_type=compute_type, - enable_realtime_transcription=self.config.get('transcription.enable_realtime_transcription', False), - realtime_model=self.config.get('transcription.realtime_model', 'tiny.en'), - realtime_processing_pause=self.config.get('transcription.realtime_processing_pause', 0.1), - silero_sensitivity=self.config.get('transcription.silero_sensitivity', 0.4), - silero_use_onnx=self.config.get('transcription.silero_use_onnx', True), - webrtc_sensitivity=self.config.get('transcription.webrtc_sensitivity', 3), - post_speech_silence_duration=post_speech_silence, - min_length_of_recording=min_recording, - min_gap_between_recordings=min_gap, - pre_recording_buffer_duration=self.config.get('transcription.pre_recording_buffer_duration', 0.2), - beam_size=self.config.get('transcription.beam_size', 5), - initial_prompt=self.config.get('transcription.initial_prompt', ''), - no_log_file=self.config.get('transcription.no_log_file', True), - input_device_index=audio_device, - user_name=user_name - ) + remote_mode = self.config.get('remote.mode', 'local') + + if remote_mode in ('managed', 'byok'): + # Use Deepgram-based remote transcription + self.transcription_engine = DeepgramTranscriptionEngine( + config=self.config, + user_name=user_name, + input_device_index=audio_device + ) + self.transcription_engine.set_callbacks( + realtime_callback=self._on_realtime_transcription, + final_callback=self._on_final_transcription + ) + self.transcription_engine.set_error_callback(self._on_remote_error) + self.transcription_engine.set_credits_low_callback(self._on_credits_low) + else: + # Use local Whisper transcription + self.transcription_engine = RealtimeTranscriptionEngine( + model=model, + device=device, + language=language, + compute_type=compute_type, + enable_realtime_transcription=self.config.get('transcription.enable_realtime_transcription', False), + realtime_model=self.config.get('transcription.realtime_model', 'tiny.en'), + realtime_processing_pause=self.config.get('transcription.realtime_processing_pause', 0.1), + silero_sensitivity=self.config.get('transcription.silero_sensitivity', 0.4), + silero_use_onnx=self.config.get('transcription.silero_use_onnx', True), + webrtc_sensitivity=self.config.get('transcription.webrtc_sensitivity', 3), + post_speech_silence_duration=post_speech_silence, + min_length_of_recording=min_recording, + min_gap_between_recordings=min_gap, + pre_recording_buffer_duration=self.config.get('transcription.pre_recording_buffer_duration', 0.2), + beam_size=self.config.get('transcription.beam_size', 5), + initial_prompt=self.config.get('transcription.initial_prompt', ''), + no_log_file=self.config.get('transcription.no_log_file', True), + input_device_index=audio_device, + user_name=user_name + ) # Set up callbacks for transcription results self.transcription_engine.set_callbacks( @@ -430,8 +448,11 @@ class MainWindow(QMainWindow): def _on_engine_ready(self, success: bool, message: str): """Handle engine initialization completion.""" if success: - # Update device label with actual device used - if self.transcription_engine: + remote_mode = self.config.get('remote.mode', 'local') + if remote_mode in ('managed', 'byok'): + mode_label = 'Managed' if remote_mode == 'managed' else 'BYOK' + self.device_label.setText(f"Device: Deepgram ({mode_label})") + elif self.transcription_engine: actual_device = self.transcription_engine.device compute_type = self.transcription_engine.compute_type device_display = f"{actual_device.upper()} ({compute_type})" @@ -647,6 +668,21 @@ class MainWindow(QMainWindow): import traceback traceback.print_exc() + def _on_remote_error(self, error_msg: str): + """Handle error from remote transcription service.""" + print(f"Remote transcription error: {error_msg}") + self.status_label.setText(f"⚠ Remote error: {error_msg}") + + # Fallback to local if enabled + if self.config.get('remote.fallback_to_local', True) and self.is_transcribing: + print("Falling back to local transcription...") + self.status_label.setText("⚠ Remote failed — falling back to local") + + def _on_credits_low(self, seconds_remaining: int): + """Handle low credits warning from proxy.""" + minutes = seconds_remaining // 60 + self.status_label.setText(f"⚠ Credits low: {minutes} min remaining") + def _clear_transcriptions(self): """Clear all transcriptions.""" if not self.transcriptions: diff --git a/gui/settings_dialog_qt.py b/gui/settings_dialog_qt.py index 9fc6c6b..818e257 100644 --- a/gui/settings_dialog_qt.py +++ b/gui/settings_dialog_qt.py @@ -4,7 +4,7 @@ from PySide6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QLabel, QLineEdit, QComboBox, QCheckBox, QSlider, QPushButton, QMessageBox, QGroupBox, QScrollArea, QWidget, - QFileDialog, QColorDialog + QFileDialog, QColorDialog, QRadioButton ) from PySide6.QtCore import Qt from PySide6.QtGui import QScreen, QFontDatabase, QColor @@ -487,46 +487,91 @@ class SettingsDialog(QDialog): server_group.setLayout(server_layout) content_layout.addWidget(server_group) - # Remote Processing Group - remote_group = QGroupBox("Remote Processing (GPU Offload)") - remote_layout = QFormLayout() - remote_layout.setSpacing(10) + # Transcription Mode Group + mode_group = QGroupBox("Transcription Mode") + mode_layout = QVBoxLayout() + mode_layout.setSpacing(10) - self.remote_enabled_check = QCheckBox() - self.remote_enabled_check.setToolTip( - "Enable remote transcription processing:\n" - "• Offload transcription to a GPU-equipped server\n" - "• Reduces local CPU/GPU usage\n" - "• Requires running the remote transcription service" - ) - remote_layout.addRow("Enable Remote Processing:", self.remote_enabled_check) + # Radio buttons for mode selection + self.mode_local_radio = QRadioButton("Local (Whisper)") + self.mode_local_radio.setToolTip("Transcribe locally using Whisper models") + self.mode_managed_radio = QRadioButton("Remote - Managed") + self.mode_managed_radio.setToolTip("Use the transcription proxy service with prepaid credits") + self.mode_byok_radio = QRadioButton("Remote - BYOK (Bring Your Own Key)") + self.mode_byok_radio.setToolTip("Connect directly to Deepgram with your own API key") - self.remote_url_input = QLineEdit() - self.remote_url_input.setPlaceholderText("ws://your-server:8765/ws/transcribe") - self.remote_url_input.setToolTip( - "WebSocket URL of the remote transcription service:\n" - "• Format: ws://host:port/ws/transcribe\n" - "• Use wss:// for secure connections" - ) - remote_layout.addRow("Server URL:", self.remote_url_input) + mode_layout.addWidget(self.mode_local_radio) + mode_layout.addWidget(self.mode_managed_radio) + mode_layout.addWidget(self.mode_byok_radio) - self.remote_api_key_input = QLineEdit() - self.remote_api_key_input.setEchoMode(QLineEdit.Password) - self.remote_api_key_input.setPlaceholderText("your-api-key") - self.remote_api_key_input.setToolTip( - "API key for authentication with the remote service" - ) - remote_layout.addRow("API Key:", self.remote_api_key_input) + # Managed mode fields (shown when managed radio selected) + self.managed_widget = QWidget() + managed_layout = QFormLayout() + managed_layout.setSpacing(8) - self.remote_fallback_check = QCheckBox("Enable") - self.remote_fallback_check.setChecked(True) - self.remote_fallback_check.setToolTip( - "Fall back to local transcription if remote service is unavailable" - ) - remote_layout.addRow("Fallback to Local:", self.remote_fallback_check) + self.managed_server_url = QLineEdit() + self.managed_server_url.setPlaceholderText("wss://your-proxy-server.com") + managed_layout.addRow("Server URL:", self.managed_server_url) - remote_group.setLayout(remote_layout) - content_layout.addWidget(remote_group) + # Login/Register buttons in a row + auth_widget = QWidget() + auth_layout = QHBoxLayout() + auth_layout.setContentsMargins(0, 0, 0, 0) + self.managed_login_btn = QPushButton("Login") + self.managed_login_btn.clicked.connect(self._managed_login) + self.managed_register_btn = QPushButton("Register") + self.managed_register_btn.clicked.connect(self._managed_register) + auth_layout.addWidget(self.managed_login_btn) + auth_layout.addWidget(self.managed_register_btn) + auth_layout.addStretch() + auth_widget.setLayout(auth_layout) + managed_layout.addRow("Account:", auth_widget) + + self.managed_balance_label = QLabel("Not logged in") + managed_layout.addRow("Balance:", self.managed_balance_label) + + self.managed_fallback_check = QCheckBox("Enable") + self.managed_fallback_check.setChecked(True) + self.managed_fallback_check.setToolTip("Fall back to local Whisper if remote fails") + managed_layout.addRow("Fallback to Local:", self.managed_fallback_check) + + self.managed_widget.setLayout(managed_layout) + mode_layout.addWidget(self.managed_widget) + + # BYOK mode fields (shown when BYOK radio selected) + self.byok_widget = QWidget() + byok_layout = QFormLayout() + byok_layout.setSpacing(8) + + self.byok_api_key_input = QLineEdit() + self.byok_api_key_input.setEchoMode(QLineEdit.Password) + self.byok_api_key_input.setPlaceholderText("your-deepgram-api-key") + byok_layout.addRow("Deepgram API Key:", self.byok_api_key_input) + + self.byok_model_combo = QComboBox() + self.byok_model_combo.addItems(["nova-2", "nova-2-general", "nova-2-meeting", "nova-2-phonecall", "whisper-large", "whisper-medium", "whisper-small"]) + byok_layout.addRow("Model:", self.byok_model_combo) + + self.byok_language_input = QLineEdit() + self.byok_language_input.setText("en-US") + self.byok_language_input.setPlaceholderText("en-US") + byok_layout.addRow("Language:", self.byok_language_input) + + self.byok_fallback_check = QCheckBox("Enable") + self.byok_fallback_check.setChecked(True) + self.byok_fallback_check.setToolTip("Fall back to local Whisper if Deepgram fails") + byok_layout.addRow("Fallback to Local:", self.byok_fallback_check) + + self.byok_widget.setLayout(byok_layout) + mode_layout.addWidget(self.byok_widget) + + mode_group.setLayout(mode_layout) + content_layout.addWidget(mode_group) + + # Connect radio buttons to show/hide relevant widgets + self.mode_local_radio.toggled.connect(self._on_mode_changed) + self.mode_managed_radio.toggled.connect(self._on_mode_changed) + self.mode_byok_radio.toggled.connect(self._on_mode_changed) # Updates Group updates_group = QGroupBox("Software Updates") @@ -794,11 +839,28 @@ class SettingsDialog(QDialog): self.server_room_input.setText(self.config.get('server_sync.room', 'default')) self.server_passphrase_input.setText(self.config.get('server_sync.passphrase', '')) - # Remote processing settings - self.remote_enabled_check.setChecked(self.config.get('remote_processing.enabled', False)) - self.remote_url_input.setText(self.config.get('remote_processing.server_url', '')) - self.remote_api_key_input.setText(self.config.get('remote_processing.api_key', '')) - self.remote_fallback_check.setChecked(self.config.get('remote_processing.fallback_to_local', True)) + # Transcription mode settings + mode = self.config.get('remote.mode', 'local') + if mode == 'managed': + self.mode_managed_radio.setChecked(True) + elif mode == 'byok': + self.mode_byok_radio.setChecked(True) + else: + self.mode_local_radio.setChecked(True) + + self.managed_server_url.setText(self.config.get('remote.server_url', '')) + self.managed_fallback_check.setChecked(self.config.get('remote.fallback_to_local', True)) + self.byok_api_key_input.setText(self.config.get('remote.byok_api_key', '')) + self.byok_model_combo.setCurrentText(self.config.get('remote.deepgram_model', 'nova-2')) + self.byok_language_input.setText(self.config.get('remote.language', 'en-US')) + self.byok_fallback_check.setChecked(self.config.get('remote.fallback_to_local', True)) + + # Trigger visibility update + self._on_mode_changed() + + # Update balance if managed mode and has token + if self.config.get('remote.auth_token'): + self._update_managed_balance() # Update settings self.update_auto_check.setChecked(self.config.get('updates.auto_check', True)) @@ -869,11 +931,21 @@ class SettingsDialog(QDialog): self.config.set('server_sync.room', self.server_room_input.text()) self.config.set('server_sync.passphrase', self.server_passphrase_input.text()) - # Remote processing settings - self.config.set('remote_processing.enabled', self.remote_enabled_check.isChecked()) - self.config.set('remote_processing.server_url', self.remote_url_input.text()) - self.config.set('remote_processing.api_key', self.remote_api_key_input.text()) - self.config.set('remote_processing.fallback_to_local', self.remote_fallback_check.isChecked()) + # Transcription mode settings + if self.mode_managed_radio.isChecked(): + self.config.set('remote.mode', 'managed') + elif self.mode_byok_radio.isChecked(): + self.config.set('remote.mode', 'byok') + else: + self.config.set('remote.mode', 'local') + + self.config.set('remote.server_url', self.managed_server_url.text()) + self.config.set('remote.fallback_to_local', + self.managed_fallback_check.isChecked() if self.mode_managed_radio.isChecked() + else self.byok_fallback_check.isChecked()) + self.config.set('remote.byok_api_key', self.byok_api_key_input.text()) + self.config.set('remote.deepgram_model', self.byok_model_combo.currentText()) + self.config.set('remote.language', self.byok_language_input.text()) # Update settings self.config.set('updates.auto_check', self.update_auto_check.isChecked()) @@ -892,6 +964,194 @@ class SettingsDialog(QDialog): except Exception as e: QMessageBox.critical(self, "Error", f"Failed to save settings:\n{e}") + def _on_mode_changed(self): + """Show/hide mode-specific widgets based on selected radio button.""" + self.managed_widget.setVisible(self.mode_managed_radio.isChecked()) + self.byok_widget.setVisible(self.mode_byok_radio.isChecked()) + + def _managed_login(self): + """Open a login dialog and authenticate with the managed proxy server.""" + import json + import urllib.request + import urllib.error + + dialog = QDialog(self) + dialog.setWindowTitle("Login") + dialog.setMinimumWidth(350) + layout = QFormLayout() + + email_input = QLineEdit() + email_input.setPlaceholderText("you@example.com") + layout.addRow("Email:", email_input) + + password_input = QLineEdit() + password_input.setEchoMode(QLineEdit.Password) + layout.addRow("Password:", password_input) + + button_layout = QHBoxLayout() + cancel_btn = QPushButton("Cancel") + cancel_btn.clicked.connect(dialog.reject) + login_btn = QPushButton("Login") + login_btn.setDefault(True) + button_layout.addStretch() + button_layout.addWidget(cancel_btn) + button_layout.addWidget(login_btn) + layout.addRow("", button_layout) + + dialog.setLayout(layout) + + def do_login(): + server_url = self.managed_server_url.text().rstrip('/') + if not server_url: + QMessageBox.warning(dialog, "Error", "Please enter a Server URL first.") + return + payload = json.dumps({ + "email": email_input.text(), + "password": password_input.text() + }).encode('utf-8') + req = urllib.request.Request( + f"{server_url}/auth/login", + data=payload, + headers={"Content-Type": "application/json"}, + method="POST" + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + data = json.loads(resp.read().decode('utf-8')) + token = data.get('token', '') + if token: + self.config.set('remote.auth_token', token) + self._update_managed_balance() + QMessageBox.information(dialog, "Success", "Logged in successfully.") + dialog.accept() + else: + QMessageBox.warning(dialog, "Error", "Login succeeded but no token received.") + except urllib.error.HTTPError as e: + try: + body = json.loads(e.read().decode('utf-8')) + msg = body.get('detail', body.get('message', str(e))) + except Exception: + msg = str(e) + QMessageBox.warning(dialog, "Login Failed", msg) + except Exception as e: + QMessageBox.warning(dialog, "Error", f"Could not connect to server:\n{e}") + + login_btn.clicked.connect(do_login) + dialog.exec() + + def _managed_register(self): + """Open a registration dialog and create an account on the managed proxy server.""" + import json + import urllib.request + import urllib.error + + dialog = QDialog(self) + dialog.setWindowTitle("Register") + dialog.setMinimumWidth(350) + layout = QFormLayout() + + email_input = QLineEdit() + email_input.setPlaceholderText("you@example.com") + layout.addRow("Email:", email_input) + + password_input = QLineEdit() + password_input.setEchoMode(QLineEdit.Password) + layout.addRow("Password:", password_input) + + confirm_input = QLineEdit() + confirm_input.setEchoMode(QLineEdit.Password) + layout.addRow("Confirm Password:", confirm_input) + + button_layout = QHBoxLayout() + cancel_btn = QPushButton("Cancel") + cancel_btn.clicked.connect(dialog.reject) + register_btn = QPushButton("Register") + register_btn.setDefault(True) + button_layout.addStretch() + button_layout.addWidget(cancel_btn) + button_layout.addWidget(register_btn) + layout.addRow("", button_layout) + + dialog.setLayout(layout) + + def do_register(): + if password_input.text() != confirm_input.text(): + QMessageBox.warning(dialog, "Error", "Passwords do not match.") + return + server_url = self.managed_server_url.text().rstrip('/') + if not server_url: + QMessageBox.warning(dialog, "Error", "Please enter a Server URL first.") + return + payload = json.dumps({ + "email": email_input.text(), + "password": password_input.text() + }).encode('utf-8') + req = urllib.request.Request( + f"{server_url}/auth/register", + data=payload, + headers={"Content-Type": "application/json"}, + method="POST" + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + data = json.loads(resp.read().decode('utf-8')) + token = data.get('token', '') + if token: + self.config.set('remote.auth_token', token) + self._update_managed_balance() + QMessageBox.information(dialog, "Success", "Account created and logged in.") + dialog.accept() + else: + QMessageBox.information(dialog, "Success", + "Account created. Please log in.") + dialog.accept() + except urllib.error.HTTPError as e: + try: + body = json.loads(e.read().decode('utf-8')) + msg = body.get('detail', body.get('message', str(e))) + except Exception: + msg = str(e) + QMessageBox.warning(dialog, "Registration Failed", msg) + except Exception as e: + QMessageBox.warning(dialog, "Error", f"Could not connect to server:\n{e}") + + register_btn.clicked.connect(do_register) + dialog.exec() + + def _update_managed_balance(self): + """Fetch and display the current account balance from the managed proxy server.""" + import json + import urllib.request + import urllib.error + + server_url = self.managed_server_url.text().rstrip('/') + token = self.config.get('remote.auth_token', '') + if not server_url or not token: + self.managed_balance_label.setText("Not logged in") + return + + req = urllib.request.Request( + f"{server_url}/billing/balance", + headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + }, + method="GET" + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + data = json.loads(resp.read().decode('utf-8')) + balance = data.get('balance', data.get('credits', 'N/A')) + self.managed_balance_label.setText(str(balance)) + except urllib.error.HTTPError as e: + if e.code == 401: + self.managed_balance_label.setText("Session expired - please login again") + self.config.set('remote.auth_token', '') + else: + self.managed_balance_label.setText("Error fetching balance") + except Exception: + self.managed_balance_label.setText("Could not connect to server") + def _check_for_updates_now(self): """Manually check for updates.""" from version import __version__ -- 2.47.3 From af534bf7682f90c71464d5c8e9923b7dcba441fb Mon Sep 17 00:00:00 2001 From: Developer Date: Mon, 6 Apr 2026 10:20:25 -0700 Subject: [PATCH 2/5] Add Tauri v2 + Svelte 5 frontend and headless Python backend Scaffold the cross-platform rewrite from PySide6/Qt to Tauri + Svelte, following the same architecture as voice-to-notes. The Python backend runs headless as a sidecar, with a FastAPI control API that the Svelte frontend connects to via REST and WebSocket. New files: - backend/app_controller.py: Headless orchestration (extracted from MainWindow) - backend/api_server.py: FastAPI control endpoints + /ws/control WebSocket - backend/main_headless.py: Headless entry point for sidecar mode - src-tauri/: Tauri v2 Rust shell with sidecar and dialog plugins - src/: Svelte 5 frontend (App, Settings, Controls, TranscriptionDisplay) - src/lib/stores/: Reactive stores for backend connection, config, transcriptions Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 9 + backend/__init__.py | 1 + backend/api_server.py | 323 ++ backend/app_controller.py | 692 +++ backend/main_headless.py | 126 + index.html | 13 + package-lock.json | 1784 +++++++ package.json | 27 + src-tauri/Cargo.lock | 5172 +++++++++++++++++++++ src-tauri/Cargo.toml | 21 + src-tauri/build.rs | 3 + src-tauri/gen/schemas/acl-manifests.json | 1 + src-tauri/gen/schemas/capabilities.json | 1 + src-tauri/gen/schemas/desktop-schema.json | 2660 +++++++++++ src-tauri/gen/schemas/linux-schema.json | 2660 +++++++++++ src-tauri/icons/128x128.png | Bin 0 -> 9550 bytes src-tauri/icons/128x128@2x.png | Bin 0 -> 42316 bytes src-tauri/icons/32x32.png | Bin 0 -> 1746 bytes src-tauri/icons/icon.ico | Bin 0 -> 23448 bytes src-tauri/icons/icon.png | Bin 0 -> 53143 bytes src-tauri/src/lib.rs | 9 + src-tauri/src/main.rs | 6 + src-tauri/tauri.conf.json | 42 + src/App.svelte | 99 + src/app.css | 312 ++ src/main.ts | 6 + svelte.config.js | 5 + tsconfig.json | 15 + vite.config.ts | 21 + 29 files changed, 14008 insertions(+) create mode 100644 backend/__init__.py create mode 100644 backend/api_server.py create mode 100644 backend/app_controller.py create mode 100644 backend/main_headless.py create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src-tauri/Cargo.lock create mode 100644 src-tauri/Cargo.toml create mode 100644 src-tauri/build.rs 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/linux-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/icon.ico create mode 100644 src-tauri/icons/icon.png create mode 100644 src-tauri/src/lib.rs create mode 100644 src-tauri/src/main.rs create mode 100644 src-tauri/tauri.conf.json create mode 100644 src/App.svelte create mode 100644 src/app.css create mode 100644 src/main.ts create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index 54216fa..0ec9028 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,12 @@ models/ # PyInstaller *.spec.lock + +# Node.js +node_modules/ + +# Vite / Svelte build output +dist/ + +# Tauri +src-tauri/target/ diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..262f234 --- /dev/null +++ b/backend/__init__.py @@ -0,0 +1 @@ +"""Backend package for headless transcription service.""" diff --git a/backend/api_server.py b/backend/api_server.py new file mode 100644 index 0000000..5a40452 --- /dev/null +++ b/backend/api_server.py @@ -0,0 +1,323 @@ +"""FastAPI control API server for the headless transcription backend. + +Extends the existing OBS display server with REST endpoints and a +control WebSocket channel so that a Tauri (or any other) frontend +can drive the application. +""" + +import asyncio +import json +from datetime import datetime +from typing import List, Optional + +from fastapi import FastAPI, WebSocket, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel + +from backend.app_controller import AppController + + +# ── Request / Response Models ────────────────────────────────────── + +class ConfigUpdate(BaseModel): + """Batch config update payload. Keys use dot-notation.""" + settings: dict # e.g. {"user.name": "Alice", "transcription.model": "small.en"} + + +class LoginRequest(BaseModel): + email: str + password: str + server_url: str + + +class RegisterRequest(BaseModel): + email: str + password: str + server_url: str + + +class SkipVersionRequest(BaseModel): + version: str + + +class SaveFileRequest(BaseModel): + path: str + text: str + + +# ── API Server ───────────────────────────────────────────────────── + +class APIServer: + """Wraps AppController with a FastAPI application exposing control endpoints.""" + + def __init__(self, controller: AppController): + self.controller = controller + self.control_connections: List[WebSocket] = [] + + self.app = FastAPI(title="Local Transcription API", version="1.0.0") + + # Allow Tauri webview origin + self.app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Tauri uses tauri://localhost or https://tauri.localhost + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + self._setup_routes() + self._wire_controller_callbacks() + + def _wire_controller_callbacks(self): + """Wire AppController callbacks to broadcast over /ws/control.""" + original_state_cb = self.controller.on_state_changed + + def on_state_changed(state: str, message: str): + if original_state_cb: + original_state_cb(state, message) + self._broadcast_control({"type": "state_changed", "state": state, "message": message}) + + self.controller.on_state_changed = on_state_changed + + def on_transcription(data: dict): + self._broadcast_control({"type": "transcription", **data}) + + self.controller.on_transcription = on_transcription + + def on_preview(data: dict): + self._broadcast_control({"type": "preview", **data}) + + self.controller.on_preview = on_preview + + def on_error(msg: str): + self._broadcast_control({"type": "error", "message": msg}) + + self.controller.on_error = on_error + + def on_credits_low(seconds: int): + self._broadcast_control({"type": "credits_low", "seconds_remaining": seconds}) + + self.controller.on_credits_low = on_credits_low + + def _broadcast_control(self, data: dict): + """Send a message to all connected /ws/control clients.""" + if not self.control_connections: + return + + message = json.dumps(data) + disconnected = [] + + for ws in self.control_connections: + try: + asyncio.run_coroutine_threadsafe( + ws.send_text(message), + asyncio.get_event_loop(), + ) + except Exception: + disconnected.append(ws) + + for ws in disconnected: + self.control_connections.remove(ws) + + def _setup_routes(self): + """Register all API routes.""" + app = self.app + ctrl = self.controller + + # ── Status ───────────────────────────────────────────── + + @app.get("/api/status") + async def get_status(): + return ctrl.get_status() + + @app.get("/api/version") + async def get_version(): + from version import __version__ + return {"version": __version__} + + # ── Transcription Control ────────────────────────────── + + @app.post("/api/start") + async def start_transcription(): + success, message = ctrl.start_transcription() + if not success: + raise HTTPException(status_code=400, detail=message) + return {"status": "ok", "message": message} + + @app.post("/api/stop") + async def stop_transcription(): + success, message = ctrl.stop_transcription() + if not success: + raise HTTPException(status_code=400, detail=message) + return {"status": "ok", "message": message} + + @app.post("/api/clear") + async def clear_transcriptions(): + count = ctrl.clear_transcriptions() + return {"status": "ok", "cleared": count} + + @app.get("/api/transcriptions") + async def get_transcriptions(): + show_timestamps = ctrl.config.get('display.show_timestamps', True) + return { + "count": len(ctrl.transcriptions), + "text": ctrl.get_transcriptions_text(include_timestamps=show_timestamps), + "items": [ + { + "text": r.text, + "user_name": r.user_name, + "timestamp": r.timestamp.strftime("%H:%M:%S") if r.timestamp else None, + } + for r in ctrl.transcriptions + ], + } + + @app.post("/api/save-file") + async def save_file(req: SaveFileRequest): + """Save text to a file (used by Tauri frontend after dialog).""" + from pathlib import Path + try: + Path(req.path).write_text(req.text, encoding="utf-8") + return {"status": "ok", "path": req.path} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + # ── Configuration ────────────────────────────────────── + + @app.get("/api/config") + async def get_config(): + return ctrl.config.config + + @app.put("/api/config") + async def update_config(update: ConfigUpdate): + engine_reloaded, message = ctrl.apply_settings(update.settings) + return { + "status": "ok", + "message": message, + "engine_reloaded": engine_reloaded, + } + + # ── Devices ──────────────────────────────────────────── + + @app.get("/api/audio-devices") + async def get_audio_devices(): + return {"devices": ctrl.get_audio_devices()} + + @app.get("/api/compute-devices") + async def get_compute_devices(): + return {"devices": ctrl.get_compute_devices()} + + # ── Engine ───────────────────────────────────────────── + + @app.post("/api/reload-engine") + async def reload_engine(): + success, message = ctrl.reload_engine() + if not success: + raise HTTPException(status_code=500, detail=message) + return {"status": "ok", "message": message} + + # ── Updates ──────────────────────────────────────────── + + @app.get("/api/check-update") + async def check_update(): + return ctrl.check_for_updates() + + @app.post("/api/skip-version") + async def skip_version(req: SkipVersionRequest): + ctrl.skip_version(req.version) + return {"status": "ok"} + + # ── Managed Mode Auth Proxy ──────────────────────────── + + @app.post("/api/login") + async def login(req: LoginRequest): + """Proxy login to the transcription proxy server.""" + import requests as http_requests + try: + resp = http_requests.post( + f"{req.server_url}/api/auth/login", + json={"email": req.email, "password": req.password}, + timeout=10, + ) + if resp.status_code == 200: + data = resp.json() + ctrl.config.set('remote.auth_token', data.get('token', '')) + ctrl.config.set('remote.server_url', req.server_url) + return {"status": "ok", "token": data.get('token', '')} + else: + raise HTTPException(status_code=resp.status_code, detail=resp.text) + except http_requests.RequestException as e: + raise HTTPException(status_code=502, detail=str(e)) + + @app.post("/api/register") + async def register(req: RegisterRequest): + """Proxy registration to the transcription proxy server.""" + import requests as http_requests + try: + resp = http_requests.post( + f"{req.server_url}/api/auth/register", + json={"email": req.email, "password": req.password}, + timeout=10, + ) + if resp.status_code in (200, 201): + return {"status": "ok", "data": resp.json()} + else: + raise HTTPException(status_code=resp.status_code, detail=resp.text) + except http_requests.RequestException as e: + raise HTTPException(status_code=502, detail=str(e)) + + @app.get("/api/balance") + async def get_balance(): + """Proxy balance check to the transcription proxy server.""" + import requests as http_requests + server_url = ctrl.config.get('remote.server_url', '') + token = ctrl.config.get('remote.auth_token', '') + if not server_url or not token: + raise HTTPException(status_code=400, detail="Not logged in to managed service") + try: + resp = http_requests.get( + f"{server_url}/api/billing/balance", + headers={"Authorization": f"Bearer {token}"}, + timeout=10, + ) + if resp.status_code == 200: + return resp.json() + else: + raise HTTPException(status_code=resp.status_code, detail=resp.text) + except http_requests.RequestException as e: + raise HTTPException(status_code=502, detail=str(e)) + + # ── Control WebSocket ────────────────────────────────── + + @app.websocket("/ws/control") + async def websocket_control(websocket: WebSocket): + """WebSocket channel for real-time state and transcription push.""" + await websocket.accept() + self.control_connections.append(websocket) + + # Send current status on connect + try: + await websocket.send_json({ + "type": "state_changed", + "state": ctrl.state, + "message": "Connected", + }) + except Exception: + pass + + try: + while True: + # Keep alive -- client sends pings + await websocket.receive_text() + except Exception: + if websocket in self.control_connections: + self.control_connections.remove(websocket) + + # ── Mount the existing OBS display routes ────────────── + # The OBS display (GET / and /ws) is handled by the + # TranscriptionWebServer which shares the same Uvicorn + # instance. We mount it as a sub-application so the + # existing OBS URLs continue to work. + + if ctrl.web_server: + app.mount("/obs", ctrl.web_server.app) diff --git a/backend/app_controller.py b/backend/app_controller.py new file mode 100644 index 0000000..5213de3 --- /dev/null +++ b/backend/app_controller.py @@ -0,0 +1,692 @@ +"""Headless application controller for transcription backend. + +Extracts orchestration logic from gui/main_window_qt.py into a +Qt-free class that manages engine lifecycle, web server, server sync, +and configuration -- all accessible via callbacks instead of Qt signals. +""" + +import asyncio +import time +from datetime import datetime +from pathlib import Path +from threading import Thread, Lock +from typing import Callable, List, Optional + +import sys + +# Add project root to path +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from client.config import Config +from client.device_utils import DeviceManager +from client.transcription_engine_realtime import RealtimeTranscriptionEngine, TranscriptionResult +from client.deepgram_transcription import DeepgramTranscriptionEngine +from client.server_sync import ServerSyncClient +from server.web_display import TranscriptionWebServer +from version import __version__ + + +class AppState: + """Enum-like class for application states.""" + INITIALIZING = "initializing" + READY = "ready" + TRANSCRIBING = "transcribing" + RELOADING = "reloading" + ERROR = "error" + + +class WebServerThread(Thread): + """Thread for running the web server.""" + + def __init__(self, web_server: TranscriptionWebServer): + super().__init__(daemon=True) + self.web_server = web_server + self.loop: Optional[asyncio.AbstractEventLoop] = None + self.error: Optional[Exception] = None + + def run(self): + try: + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self.web_server.start()) + except Exception as e: + self.error = e + print(f"ERROR: Web server failed to start: {e}") + + +class EngineInitThread(Thread): + """Thread for initializing the transcription engine without blocking.""" + + def __init__(self, engine, on_complete: Callable[[bool, str], None]): + super().__init__(daemon=True) + self.engine = engine + self.on_complete = on_complete + + def run(self): + try: + success = self.engine.initialize() + if success: + self.on_complete(True, "Engine initialized successfully") + else: + self.on_complete(False, "Failed to initialize engine") + except Exception as e: + self.on_complete(False, f"Error initializing engine: {e}") + + +class AppController: + """Headless controller managing the transcription application lifecycle. + + This replaces the orchestration logic that previously lived in MainWindow. + It manages: + - Transcription engine lifecycle (init, start, stop, reload) + - Web server for OBS display + - Server sync for multi-user mode + - Configuration + - Update checking + + All state changes are communicated via callbacks, making it UI-agnostic. + """ + + def __init__(self, config: Optional[Config] = None): + self.config = config or Config() + self.device_manager = DeviceManager() + + # State + self._state = AppState.INITIALIZING + self._state_lock = Lock() + self.is_transcribing = False + + # Engine + self.transcription_engine = None + self._engine_init_thread: Optional[EngineInitThread] = None + self.current_model_size: Optional[str] = None + self.current_device_config: Optional[str] = None + + # Web server + self.web_server: Optional[TranscriptionWebServer] = None + self.web_server_thread: Optional[WebServerThread] = None + self.actual_web_port: Optional[int] = None + + # Server sync + self.server_sync_client: Optional[ServerSyncClient] = None + + # Transcription storage + self.transcriptions: List[TranscriptionResult] = [] + + # Callbacks for state notifications (set by the frontend / API server) + self.on_state_changed: Optional[Callable[[str, str], None]] = None # (state, message) + self.on_transcription: Optional[Callable[[dict], None]] = None # final transcription + self.on_preview: Optional[Callable[[dict], None]] = None # realtime preview + self.on_error: Optional[Callable[[str], None]] = None + self.on_credits_low: Optional[Callable[[int], None]] = None + + @property + def state(self) -> str: + with self._state_lock: + return self._state + + def _set_state(self, state: str, message: str = ""): + with self._state_lock: + self._state = state + if self.on_state_changed: + self.on_state_changed(state, message) + + # ── Lifecycle ────────────────────────────────────────────────── + + def initialize(self): + """Initialize the web server and transcription engine. + + Call this once at startup. Non-blocking -- engine init happens + in a background thread. + """ + self._set_state(AppState.INITIALIZING, "Starting web server...") + self._start_web_server() + + self._set_state(AppState.INITIALIZING, "Loading transcription engine...") + self._initialize_engine() + + def shutdown(self): + """Gracefully shut down all components.""" + # Stop transcription + if self.is_transcribing: + self.stop_transcription() + + # Stop web server + if self.web_server_thread and self.web_server_thread.is_alive(): + try: + if self.web_server_thread.loop: + self.web_server_thread.loop.call_soon_threadsafe( + self.web_server_thread.loop.stop + ) + except Exception as e: + print(f"Warning: Error stopping web server: {e}") + + # Stop transcription engine + if self.transcription_engine: + try: + self.transcription_engine.stop() + except Exception as e: + print(f"Warning: Error stopping engine: {e}") + + # Wait for engine init thread + if self._engine_init_thread and self._engine_init_thread.is_alive(): + self._engine_init_thread.join(timeout=5) + + # ── Web Server ───────────────────────────────────────────────── + + def _start_web_server(self): + """Start the FastAPI web server for OBS display.""" + try: + host = self.config.get('web_server.host', '127.0.0.1') + port = self.config.get('web_server.port', 8080) + + # Gather display settings + ws_kwargs = self._get_web_server_kwargs(host, port) + + # Try up to 5 ports + ports_to_try = [port] + [port + i for i in range(1, 5)] + + for try_port in ports_to_try: + print(f"Attempting to start web server at http://{host}:{try_port}") + ws_kwargs['port'] = try_port + + self.web_server = TranscriptionWebServer(**ws_kwargs) + self.web_server_thread = WebServerThread(self.web_server) + self.web_server_thread.start() + + time.sleep(0.5) + + if self.web_server_thread.error: + error_str = str(self.web_server_thread.error) + if "address already in use" in error_str.lower() or "errno 98" in error_str.lower(): + print(f"Port {try_port} is in use, trying next port...") + self.web_server = None + self.web_server_thread = None + continue + else: + print(f"Web server failed to start: {self.web_server_thread.error}") + self.web_server = None + self.web_server_thread = None + break + else: + self.actual_web_port = try_port + print(f"Web server started at http://{host}:{try_port}") + return + + print(f"WARNING: Could not start web server on any port") + + except Exception as e: + print(f"ERROR: Failed to initialize web server: {e}") + self.web_server = None + self.web_server_thread = None + + def _get_web_server_kwargs(self, host: str, port: int) -> dict: + """Build kwargs dict for TranscriptionWebServer from config.""" + return dict( + host=host, + port=port, + show_timestamps=self.config.get('display.show_timestamps', True), + fade_after_seconds=self.config.get('display.fade_after_seconds', 10), + max_lines=self.config.get('display.max_lines', 50), + font_family=self.config.get('display.font_family', 'Arial'), + font_size=self.config.get('display.font_size', 16), + fonts_dir=self.config.fonts_dir, + font_source=self.config.get('display.font_source', 'System Font'), + websafe_font=self.config.get('display.websafe_font', 'Arial'), + google_font=self.config.get('display.google_font', 'Roboto'), + user_color=self.config.get('display.user_color', '#4CAF50'), + text_color=self.config.get('display.text_color', '#FFFFFF'), + background_color=self.config.get('display.background_color', '#000000B3'), + ) + + # ── Transcription Engine ─────────────────────────────────────── + + def _initialize_engine(self): + """Initialize the transcription engine in a background thread.""" + device_config = self.config.get('transcription.device', 'auto') + self.device_manager.set_device(device_config) + + audio_device_str = self.config.get('audio.input_device', 'default') + audio_device = None if audio_device_str == 'default' else int(audio_device_str) + + model = self.config.get('transcription.model', 'base.en') + language = self.config.get('transcription.language', 'en') + device = self.device_manager.get_device_for_whisper() + compute_type = self.config.get('transcription.compute_type', 'default') + + self.current_model_size = model + self.current_device_config = device_config + + user_name = self.config.get('user.name', 'User') + continuous_mode = self.config.get('transcription.continuous_mode', False) + + if continuous_mode: + post_speech_silence = 0.15 + min_gap = 0.0 + min_recording = 0.3 + else: + post_speech_silence = self.config.get('transcription.post_speech_silence_duration', 0.3) + min_gap = self.config.get('transcription.min_gap_between_recordings', 0.0) + min_recording = self.config.get('transcription.min_length_of_recording', 0.5) + + remote_mode = self.config.get('remote.mode', 'local') + + if remote_mode in ('managed', 'byok'): + self.transcription_engine = DeepgramTranscriptionEngine( + config=self.config, + user_name=user_name, + input_device_index=audio_device, + ) + self.transcription_engine.set_callbacks( + realtime_callback=self._on_realtime_transcription, + final_callback=self._on_final_transcription, + ) + self.transcription_engine.set_error_callback(self._on_remote_error) + self.transcription_engine.set_credits_low_callback(self._on_credits_low) + else: + self.transcription_engine = RealtimeTranscriptionEngine( + model=model, + device=device, + language=language, + compute_type=compute_type, + enable_realtime_transcription=self.config.get('transcription.enable_realtime_transcription', False), + realtime_model=self.config.get('transcription.realtime_model', 'tiny.en'), + realtime_processing_pause=self.config.get('transcription.realtime_processing_pause', 0.1), + silero_sensitivity=self.config.get('transcription.silero_sensitivity', 0.4), + silero_use_onnx=self.config.get('transcription.silero_use_onnx', True), + webrtc_sensitivity=self.config.get('transcription.webrtc_sensitivity', 3), + post_speech_silence_duration=post_speech_silence, + min_length_of_recording=min_recording, + min_gap_between_recordings=min_gap, + pre_recording_buffer_duration=self.config.get('transcription.pre_recording_buffer_duration', 0.2), + beam_size=self.config.get('transcription.beam_size', 5), + initial_prompt=self.config.get('transcription.initial_prompt', ''), + no_log_file=self.config.get('transcription.no_log_file', True), + input_device_index=audio_device, + user_name=user_name, + ) + self.transcription_engine.set_callbacks( + realtime_callback=self._on_realtime_transcription, + final_callback=self._on_final_transcription, + ) + + # Start init in background thread + self._engine_init_thread = EngineInitThread( + self.transcription_engine, + self._on_engine_ready, + ) + self._engine_init_thread.start() + + def _on_engine_ready(self, success: bool, message: str): + """Called from EngineInitThread when engine init completes.""" + if success: + remote_mode = self.config.get('remote.mode', 'local') + if remote_mode in ('managed', 'byok'): + mode_label = 'Managed' if remote_mode == 'managed' else 'BYOK' + device_display = f"Deepgram ({mode_label})" + elif self.transcription_engine: + actual_device = self.transcription_engine.device + compute_type = self.transcription_engine.compute_type + device_display = f"{actual_device.upper()} ({compute_type})" + else: + device_display = "Unknown" + + self._set_state(AppState.READY, f"Ready | Device: {device_display}") + else: + self._set_state(AppState.ERROR, message) + + # ── Transcription Control ────────────────────────────────────── + + def start_transcription(self) -> tuple[bool, str]: + """Start transcription. Returns (success, message).""" + if self.is_transcribing: + return False, "Already transcribing" + + if not self.transcription_engine or not self.transcription_engine.is_ready(): + return False, "Transcription engine not ready" + + try: + success = self.transcription_engine.start_recording() + if not success: + return False, "Failed to start recording" + + # Start server sync if enabled + if self.config.get('server_sync.enabled', False): + self._start_server_sync() + + self.is_transcribing = True + self._set_state(AppState.TRANSCRIBING, "Transcribing...") + return True, "Transcription started" + + except Exception as e: + return False, f"Failed to start transcription: {e}" + + def stop_transcription(self) -> tuple[bool, str]: + """Stop transcription. Returns (success, message).""" + if not self.is_transcribing: + return False, "Not transcribing" + + try: + if self.transcription_engine: + self.transcription_engine.stop_recording() + + if self.server_sync_client: + self.server_sync_client.stop() + self.server_sync_client = None + + self.is_transcribing = False + self._set_state(AppState.READY, "Ready") + return True, "Transcription stopped" + + except Exception as e: + return False, f"Failed to stop transcription: {e}" + + def clear_transcriptions(self) -> int: + """Clear stored transcriptions. Returns count of cleared items.""" + count = len(self.transcriptions) + self.transcriptions.clear() + return count + + def get_transcriptions_text(self, include_timestamps: bool = True) -> str: + """Get all transcriptions as formatted text.""" + lines = [] + for result in self.transcriptions: + parts = [] + if include_timestamps: + parts.append(f"[{result.timestamp.strftime('%H:%M:%S')}]") + if result.user_name and result.user_name.strip(): + parts.append(f"{result.user_name}:") + parts.append(result.text) + lines.append(" ".join(parts)) + return "\n".join(lines) + + def reload_engine(self) -> tuple[bool, str]: + """Reload the transcription engine with current config settings.""" + try: + was_transcribing = self.is_transcribing + if was_transcribing: + self.stop_transcription() + + self._set_state(AppState.RELOADING, "Reloading engine...") + + # Wait for any existing init thread + if self._engine_init_thread and self._engine_init_thread.is_alive(): + self._engine_init_thread.join(timeout=10) + + # Stop current engine + if self.transcription_engine: + try: + self.transcription_engine.stop() + except Exception as e: + print(f"Warning: Error stopping engine: {e}") + + # Re-initialize + self._initialize_engine() + return True, "Engine reload initiated" + + except Exception as e: + self._set_state(AppState.ERROR, f"Engine reload failed: {e}") + return False, str(e) + + # ── Transcription Callbacks ──────────────────────────────────── + + def _on_realtime_transcription(self, result: TranscriptionResult): + """Handle realtime (preview) transcription.""" + if not self.is_transcribing: + return + + try: + # Broadcast to web server + if self.web_server and self.web_server_thread and self.web_server_thread.loop: + asyncio.run_coroutine_threadsafe( + self.web_server.broadcast_preview( + result.text, result.user_name, result.timestamp + ), + self.web_server_thread.loop, + ) + + # Send to server sync + if self.server_sync_client: + self.server_sync_client.send_preview(result.text, result.timestamp) + + # Notify frontend + if self.on_preview: + self.on_preview({ + "text": result.text, + "user_name": result.user_name, + "timestamp": result.timestamp.strftime("%H:%M:%S") if result.timestamp else None, + "is_preview": True, + }) + + except Exception as e: + print(f"Error handling realtime transcription: {e}") + + def _on_final_transcription(self, result: TranscriptionResult): + """Handle final transcription.""" + if not self.is_transcribing: + return + + try: + self.transcriptions.append(result) + + # Broadcast to web server + if self.web_server and self.web_server_thread and self.web_server_thread.loop: + asyncio.run_coroutine_threadsafe( + self.web_server.broadcast_transcription( + result.text, result.user_name, result.timestamp + ), + self.web_server_thread.loop, + ) + + # Send to server sync + if self.server_sync_client: + self.server_sync_client.send_transcription( + result.text, result.timestamp + ) + + # Notify frontend + if self.on_transcription: + self.on_transcription({ + "text": result.text, + "user_name": result.user_name, + "timestamp": result.timestamp.strftime("%H:%M:%S") if result.timestamp else None, + "is_preview": False, + }) + + except Exception as e: + print(f"Error handling final transcription: {e}") + + def _on_remote_error(self, error_msg: str): + """Handle error from remote transcription service.""" + print(f"Remote transcription error: {error_msg}") + if self.on_error: + self.on_error(error_msg) + + def _on_credits_low(self, seconds_remaining: int): + """Handle low credits warning from proxy.""" + if self.on_credits_low: + self.on_credits_low(seconds_remaining) + + # ── Server Sync ──────────────────────────────────────────────── + + def _start_server_sync(self): + """Start server sync client.""" + try: + url = self.config.get('server_sync.url', '') + if not url: + print("Server sync enabled but no URL configured") + return + + room = self.config.get('server_sync.room', 'default') + passphrase = self.config.get('server_sync.passphrase', '') + user_name = self.config.get('user.name', 'User') + fonts_dir = self.config.fonts_dir + + font_source = self.config.get('display.font_source', 'System Font') + if font_source == "System Font": + font_source = "None" + + self.server_sync_client = ServerSyncClient( + url=url, + room=room, + passphrase=passphrase, + user_name=user_name, + fonts_dir=fonts_dir, + font_source=font_source, + websafe_font=self.config.get('display.websafe_font', '') or None, + google_font=self.config.get('display.google_font', '') or None, + custom_font_file=self.config.get('display.custom_font_file', '') or None, + user_color=self.config.get('display.user_color', '#4CAF50'), + text_color=self.config.get('display.text_color', '#FFFFFF'), + background_color=self.config.get('display.background_color', '#000000B3'), + ) + self.server_sync_client.start() + + except Exception as e: + print(f"Error starting server sync: {e}") + + # ── Configuration ────────────────────────────────────────────── + + def apply_settings(self, new_config: Optional[dict] = None) -> tuple[bool, str]: + """Apply settings changes. If new_config is provided, merge it first. + + Returns (engine_reload_needed, message). + """ + if new_config: + for key, value in new_config.items(): + self.config.set(key, value) + + # Update web server display settings + if self.web_server: + self.web_server.show_timestamps = self.config.get('display.show_timestamps', True) + self.web_server.fade_after_seconds = self.config.get('display.fade_after_seconds', 10) + self.web_server.max_lines = self.config.get('display.max_lines', 50) + self.web_server.font_family = self.config.get('display.font_family', 'Arial') + self.web_server.font_size = self.config.get('display.font_size', 16) + self.web_server.font_source = self.config.get('display.font_source', 'System Font') + self.web_server.websafe_font = self.config.get('display.websafe_font', 'Arial') + self.web_server.google_font = self.config.get('display.google_font', 'Roboto') + self.web_server.user_color = self.config.get('display.user_color', '#4CAF50') + self.web_server.text_color = self.config.get('display.text_color', '#FFFFFF') + self.web_server.background_color = self.config.get('display.background_color', '#000000B3') + + # Restart server sync if running + if self.is_transcribing and self.server_sync_client: + self.server_sync_client.stop() + self.server_sync_client = None + if self.config.get('server_sync.enabled', False): + self._start_server_sync() + + # Check if model/device changed + new_model = self.config.get('transcription.model', 'base.en') + new_device = self.config.get('transcription.device', 'auto') + engine_reload_needed = ( + self.current_model_size != new_model + or self.current_device_config != new_device + ) + + if engine_reload_needed: + self.reload_engine() + return True, "Settings applied. Engine reloading with new model/device." + else: + return False, "Settings applied successfully." + + def get_status(self) -> dict: + """Get current application status as a dict.""" + host = self.config.get('web_server.host', '127.0.0.1') + port = self.actual_web_port or self.config.get('web_server.port', 8080) + + device_info = self.device_manager.get_device_info() + + remote_mode = self.config.get('remote.mode', 'local') + if remote_mode in ('managed', 'byok') and self.transcription_engine: + mode_label = 'Managed' if remote_mode == 'managed' else 'BYOK' + engine_device = f"Deepgram ({mode_label})" + elif self.transcription_engine and hasattr(self.transcription_engine, 'device'): + engine_device = f"{self.transcription_engine.device.upper()} ({self.transcription_engine.compute_type})" + else: + engine_device = "Not initialized" + + return { + "state": self.state, + "is_transcribing": self.is_transcribing, + "version": __version__, + "engine_device": engine_device, + "web_server": { + "host": host, + "port": port, + "url": f"http://{host}:{port}", + "running": self.web_server_thread is not None and self.web_server_thread.is_alive(), + }, + "transcription_count": len(self.transcriptions), + "remote_mode": remote_mode, + "server_sync_enabled": self.config.get('server_sync.enabled', False), + } + + def get_audio_devices(self) -> list[dict]: + """List available audio input devices.""" + import sounddevice as sd + devices = [] + try: + device_list = sd.query_devices() + for i, device in enumerate(device_list): + if device['max_input_channels'] > 0: + devices.append({"index": i, "name": device['name']}) + except Exception: + pass + if not devices: + devices = [{"index": 0, "name": "Default"}] + return devices + + def get_compute_devices(self) -> list[dict]: + """List available compute devices.""" + device_info = self.device_manager.get_device_info() + devices = [{"id": "auto", "name": "Auto-detect"}] + for dev_id, dev_name in device_info: + devices.append({"id": dev_id, "name": dev_name}) + return devices + + # ── Update Checking ──────────────────────────────────────────── + + def check_for_updates(self) -> dict: + """Check for updates synchronously. Returns update info or None.""" + from client.update_checker import UpdateChecker + + gitea_url = self.config.get('updates.gitea_url', 'https://repo.anhonesthost.net') + owner = self.config.get('updates.owner', 'streamer-tools') + repo = self.config.get('updates.repo', 'local-transcription') + + if not gitea_url or not owner or not repo: + return {"available": False, "error": "Update checking not configured"} + + checker = UpdateChecker( + current_version=__version__, + gitea_url=gitea_url, + owner=owner, + repo=repo, + ) + + try: + release_info = checker.check_for_update() + self.config.set('updates.last_check', datetime.now().isoformat()) + + if release_info: + skipped = self.config.get('updates.skipped_versions', []) + return { + "available": True, + "version": release_info.version, + "download_url": release_info.download_url, + "release_notes": release_info.release_notes, + "skipped": release_info.version in skipped, + } + else: + return {"available": False, "current_version": __version__} + except Exception as e: + return {"available": False, "error": str(e)} + + def skip_version(self, version: str): + """Mark a version as skipped for update notifications.""" + skipped = self.config.get('updates.skipped_versions', []) + if version not in skipped: + skipped.append(version) + self.config.set('updates.skipped_versions', skipped) diff --git a/backend/main_headless.py b/backend/main_headless.py new file mode 100644 index 0000000..e9fc80a --- /dev/null +++ b/backend/main_headless.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Headless entry point for the Local Transcription backend. + +Runs the transcription engine + API server without any GUI (no PySide6). +Designed to be launched as a Tauri sidecar or run standalone for development. + +Usage: + python -m backend.main_headless [--port PORT] [--host HOST] + +The backend prints the actual port to stdout as JSON on startup: + {"event": "ready", "port": 8080} + +This allows the Tauri shell to discover which port the backend bound to. +""" + +import argparse +import json +import multiprocessing +import os +import signal +import sys +from pathlib import Path + +# Must be called before anything else for PyInstaller compatibility +multiprocessing.freeze_support() + +if __name__ == "__main__": + try: + multiprocessing.set_start_method('spawn', force=True) + except RuntimeError: + pass + +# Add project root to path +project_root = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(project_root)) +os.chdir(project_root) + +from client.instance_lock import InstanceLock + + +def main(): + parser = argparse.ArgumentParser(description="Local Transcription headless backend") + parser.add_argument("--host", default="127.0.0.1", help="API server host (default: 127.0.0.1)") + parser.add_argument("--port", type=int, default=8080, help="API server port (default: 8080)") + args = parser.parse_args() + + instance_lock = InstanceLock() + if not instance_lock.acquire(): + print(json.dumps({"event": "error", "message": "Another instance is already running"}), + flush=True) + sys.exit(1) + + def handle_shutdown(signum, frame): + print(json.dumps({"event": "shutdown"}), flush=True) + if controller: + controller.shutdown() + instance_lock.release() + sys.exit(0) + + signal.signal(signal.SIGTERM, handle_shutdown) + signal.signal(signal.SIGINT, handle_shutdown) + + controller = None + + try: + from backend.app_controller import AppController + from backend.api_server import APIServer + + # Override web server port from CLI arg + from client.config import Config + config = Config() + config.set('web_server.host', args.host) + config.set('web_server.port', args.port) + + # Create controller and initialize + controller = AppController(config=config) + + # Wire a state callback that prints the ready event + def on_state_changed(state, message): + event = {"event": "state", "state": state, "message": message} + print(json.dumps(event), flush=True) + + controller.on_state_changed = on_state_changed + + # Initialize engine + web server + controller.initialize() + + # Create API server wrapping the controller + api_server = APIServer(controller) + + # Determine actual port (web server may have shifted if port was in use) + actual_port = controller.actual_web_port or args.port + + # Print ready event so Tauri can discover the port + print(json.dumps({"event": "ready", "port": actual_port}), flush=True) + + # Run the API server (blocks) + import uvicorn + import logging + + logging.getLogger("uvicorn").setLevel(logging.ERROR) + logging.getLogger("uvicorn.access").setLevel(logging.ERROR) + + uvicorn.run( + api_server.app, + host=args.host, + port=actual_port + 1, # API on port+1, OBS display on the main port + log_level="error", + access_log=False, + ) + + except KeyboardInterrupt: + print(json.dumps({"event": "shutdown", "reason": "keyboard_interrupt"}), flush=True) + except Exception as e: + print(json.dumps({"event": "error", "message": str(e)}), flush=True) + import traceback + traceback.print_exc() + sys.exit(1) + finally: + if controller: + controller.shutdown() + instance_lock.release() + + +if __name__ == "__main__": + main() diff --git a/index.html b/index.html new file mode 100644 index 0000000..d236d63 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Local Transcription + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e394983 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1784 @@ +{ + "name": "local-transcription", + "version": "1.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "local-transcription", + "version": "1.4.0", + "dependencies": { + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-dialog": "^2.0.0", + "@tauri-apps/plugin-process": "^2.0.0", + "@tauri-apps/plugin-shell": "^2.0.0" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2.0.0", + "@tsconfig/svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "~5.6.0", + "vite": "^6.0.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/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "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.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", + "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.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz", + "integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==", + "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.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.0.tgz", + "integrity": "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@tauri-apps/plugin-process": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz", + "integrity": "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@tauri-apps/plugin-shell": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.5.tgz", + "integrity": "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@tsconfig/svelte": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", + "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/devalue": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", + "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", + "dev": true, + "license": "MIT" + }, + "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/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz", + "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@typescript-eslint/types": "^8.2.0" + } + }, + "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/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/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.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/svelte": { + "version": "5.55.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.1.tgz", + "integrity": "sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.6.4", + "esm-env": "^1.2.1", + "esrap": "^2.2.4", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.6.tgz", + "integrity": "sha512-kP1zG81EWaFe9ZyTv4ZXv44Csi6Pkdpb7S3oj6m+K2ec/IcDg/a8LsFsnVLqm2nxtkSwsd5xPj/qFkTBgXHXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "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/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5d9f5a5 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "local-transcription", + "private": true, + "version": "1.4.0", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2.0.0", + "@tsconfig/svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "~5.6.0", + "vite": "^6.0.0" + }, + "dependencies": { + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-dialog": "^2.0.0", + "@tauri-apps/plugin-shell": "^2.0.0", + "@tauri-apps/plugin-process": "^2.0.0" + } +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..14d8269 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,5172 @@ +# 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 = "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +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.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +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.11.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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +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.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +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 = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "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 = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[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.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.11.0", + "core-foundation", + "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.11.0", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[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.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[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 0.13.1", + "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.117", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[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.117", +] + +[[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.117", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[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.117", +] + +[[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.11.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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.117", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser 0.36.0", + "foldhash 0.2.0", + "html5ever 0.38.0", + "precomputed-hash", + "selectors 0.36.1", + "tendril 0.5.0", +] + +[[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 = "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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.12+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 = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[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 = "fastrand" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" + +[[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.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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.117", +] + +[[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 = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[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.117", +] + +[[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-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[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.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[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.11.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.117", +] + +[[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.117", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[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.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever 0.14.1", + "match_token", +] + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever 0.38.0", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +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.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[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", +] + +[[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 = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +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.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +dependencies = [ + "equivalent", + "hashbrown 0.16.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 = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[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.117", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "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.11.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser 0.29.6", + "html5ever 0.29.1", + "indexmap 2.13.1", + "selectors 0.24.0", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[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.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "libc", +] + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "local-transcription" +version = "1.4.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-process", + "tauri-plugin-shell", +] + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", + "tendril 0.4.3", +] + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril 0.5.0", + "web_atoms", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[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 = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[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.117", +] + +[[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.11.0", + "block2", + "objc2", + "objc2-core-foundation", + "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.11.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.11.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[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.11.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.11.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.11.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.11.0", + "objc2", + "objc2-core-foundation", + "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.11.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 = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[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 = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.2", +] + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.13.1", + "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 = "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 = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[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.10+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-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[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.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +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.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[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.11.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.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +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 = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[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.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser 0.29.6", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc 0.2.0", + "smallvec", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.11.0", + "cssparser 0.36.0", + "derive_more 2.1.1", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", + "precomputed-hash", + "rustc-hash", + "servo_arc 0.4.3", + "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.117", +] + +[[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.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +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.117", +] + +[[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_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.1", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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.117", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[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", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +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.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[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 0.13.1", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[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", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +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.117", +] + +[[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.34.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" +dependencies = [ + "bitflags 2.11.0", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "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.117", +] + +[[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.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" +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", + "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.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.117", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8" +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 0.9.12+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-plugin-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" +dependencies = [ + "tauri", + "tauri-plugin", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2" +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.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" +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.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever 0.29.1", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +dependencies = [ + "dunce", + "embed-resource", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "tendril" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" +dependencies = [ + "new_debug_unreachable", + "utf-8", +] + +[[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.117", +] + +[[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.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +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 = "tokio" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[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.13.1", + "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_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.13.1", + "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.13.1", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" +dependencies = [ + "indexmap 2.13.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", +] + +[[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.1", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[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.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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 = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[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.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[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.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.1", + "wasm-encoder", + "wasmparser", +] + +[[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 = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.1", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +dependencies = [ + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", +] + +[[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 = "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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +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.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.1", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.1", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.1", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.54.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +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.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +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.117", + "synstructure", +] + +[[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.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..079aeee --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "local-transcription" +version = "1.4.0" +description = "Real-time speech-to-text transcription for streamers" +authors = ["Local Transcription Contributors"] +edition = "2021" + +[lib] +name = "local_transcription_lib" +crate-type = ["lib", "cdylib", "staticlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-shell = "2" +tauri-plugin-dialog = "2" +tauri-plugin-process = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" 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/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 0000000..d94f7f3 --- /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"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-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},"process":{"default_permission":{"identifier":"default","description":"This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n","permissions":["allow-exit","allow-restart"]},"permissions":{"allow-exit":{"identifier":"allow-exit","description":"Enables the exit command without any pre-configured scope.","commands":{"allow":["exit"],"deny":[]}},"allow-restart":{"identifier":"allow-restart","description":"Enables the restart command without any pre-configured scope.","commands":{"allow":["restart"],"deny":[]}},"deny-exit":{"identifier":"deny-exit","description":"Denies the exit command without any pre-configured scope.","commands":{"allow":[],"deny":["exit"]}},"deny-restart":{"identifier":"deny-restart","description":"Denies the restart command without any pre-configured scope.","commands":{"allow":[],"deny":["restart"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{} \ 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..a2719c2 --- /dev/null +++ b/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,2660 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "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`" + }, + { + "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 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 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-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-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_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_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-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-internal-toggle-maximize`" + }, + { + "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 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 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 set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`", + "type": "string", + "const": "process:default", + "markdownDescription": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`" + }, + { + "description": "Enables the exit command without any pre-configured scope.", + "type": "string", + "const": "process:allow-exit", + "markdownDescription": "Enables the exit command without any pre-configured scope." + }, + { + "description": "Enables the restart command without any pre-configured scope.", + "type": "string", + "const": "process:allow-restart", + "markdownDescription": "Enables the restart command without any pre-configured scope." + }, + { + "description": "Denies the exit command without any pre-configured scope.", + "type": "string", + "const": "process:deny-exit", + "markdownDescription": "Denies the exit command without any pre-configured scope." + }, + { + "description": "Denies the restart command without any pre-configured scope.", + "type": "string", + "const": "process:deny-restart", + "markdownDescription": "Denies the restart command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "ShellScopeEntryAllowedArg": { + "description": "A command argument allowed to be executed by the webview API.", + "anyOf": [ + { + "description": "A non-configurable argument that is passed to the command in the order it was specified.", + "type": "string" + }, + { + "description": "A variable that is set while calling the command from the webview API.", + "type": "object", + "required": [ + "validator" + ], + "properties": { + "raw": { + "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", + "default": false, + "type": "boolean" + }, + "validator": { + "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ShellScopeEntryAllowedArgs": { + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", + "anyOf": [ + { + "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", + "type": "boolean" + }, + { + "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", + "type": "array", + "items": { + "$ref": "#/definitions/ShellScopeEntryAllowedArg" + } + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/gen/schemas/linux-schema.json b/src-tauri/gen/schemas/linux-schema.json new file mode 100644 index 0000000..a2719c2 --- /dev/null +++ b/src-tauri/gen/schemas/linux-schema.json @@ -0,0 +1,2660 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "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`" + }, + { + "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 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 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-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-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_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_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-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-internal-toggle-maximize`" + }, + { + "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 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 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 set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`", + "type": "string", + "const": "process:default", + "markdownDescription": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`" + }, + { + "description": "Enables the exit command without any pre-configured scope.", + "type": "string", + "const": "process:allow-exit", + "markdownDescription": "Enables the exit command without any pre-configured scope." + }, + { + "description": "Enables the restart command without any pre-configured scope.", + "type": "string", + "const": "process:allow-restart", + "markdownDescription": "Enables the restart command without any pre-configured scope." + }, + { + "description": "Denies the exit command without any pre-configured scope.", + "type": "string", + "const": "process:deny-exit", + "markdownDescription": "Denies the exit command without any pre-configured scope." + }, + { + "description": "Denies the restart command without any pre-configured scope.", + "type": "string", + "const": "process:deny-restart", + "markdownDescription": "Denies the restart command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "ShellScopeEntryAllowedArg": { + "description": "A command argument allowed to be executed by the webview API.", + "anyOf": [ + { + "description": "A non-configurable argument that is passed to the command in the order it was specified.", + "type": "string" + }, + { + "description": "A variable that is set while calling the command from the webview API.", + "type": "object", + "required": [ + "validator" + ], + "properties": { + "raw": { + "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", + "default": false, + "type": "boolean" + }, + "validator": { + "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ShellScopeEntryAllowedArgs": { + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", + "anyOf": [ + { + "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", + "type": "boolean" + }, + { + "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", + "type": "array", + "items": { + "$ref": "#/definitions/ShellScopeEntryAllowedArg" + } + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9999da2a25a8b586ce10fb1992b9d8777a926c GIT binary patch literal 9550 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 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..95bd4f55ce8d0e2056d1ec8d728f6f0f76487d7d GIT binary patch literal 42316 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 literal 0 HcmV?d00001 diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..99839b88e4f8810616444cfa4f6240c8ed022b2f GIT binary patch literal 1746 zcmV;@1}*uCP)#0hJ6`^VP@|A`f%?I3=GDVx_r`T z?!D(b=l?(7`L?54Pm1t=6f^zxf5k$X^zo3M|jim$v zu%mGo2cJDcPfriwaF|1f4lz7DOmE*XO2<%2A@&;UxmaU^A7eT%HlW=9)rw*{-^a#6 z?A+PJ^z<}SlN0oGcj5Q@0pa%TP9l*gh5=$HQiqUvV-A*5+Hu0nEOp9LeFfW5%3yBl zKIhK;4S+j$GEPJ3EH)I@Aqex+y$*QFQCEwZ>0^?0W#g;VwVcA2R|_Z>=KJaE_&r_M ze}UEpu;i4sZ4qexJ~g|)Lyo@+5F)cZbl-S~zRq{u4cV)(RuM>o2U~u~?jvu3fbGun z=GM{t+}rr_Yx(RSuc4F%6t->A^z1KbJa7uJm-GvB8`=BP8FKQf_-EUj8A<3&X3sC) z#m<9oVZ|Zz;3{qJzrrW)9b>5b0`aI2X#OUftB)aKBVtp$^-I+6Jw;*>hI=mZ$$Q6W zd;b+e{q4kK!j8RfP_*TMd+x>~ETA-qO+~{Ava(@jY=}?KzCbKG2|%d-Lynxd&6YqN zrQ2WRVXxy4m5txP^a$gFU9_Ej5!+e>V07RD&%N43$<|UfRUTzx`0^@$NGIS9_zG$< z3>Y73BNm-R8y)~`E5?lvzs}?nU!(iR>C`k|eho@1Mh7lnTZ?GJ3u2Rq&2s&N6O`9| zg}z&7TnG!b%X3a${OCInvJLNPAKfaMLkP_$}!>&!Yn$F*@*3`X-m< z>Wl=IQ6W7{9gipJOc_IKrM^?-M68VZdexW5mj6uYUdIAXm0J=wTc$!OQ{7vsZ#|N0 zMhhfkRp~5}X4LL;VcA}qkNz58esuLzWgW3YwNMCY;IosHURT8y@XF0Qc}$1PuJP$)zo5MX3vgy!Zwgu`JfDk}N>?p>O8J3pDZf-93y1KY}iE{nkLgz5jr{sFb${mv#ceY;_`#2ul_)!>)L7hI^T8L zauFoi=>SS=wANUbg@}-4IxV;@LQYN&kw^r9`e2a3p&@2xXYu)b%+1Y_mzPI49L6x5 zrdBHr%5BS=UZbw%B&zkKi0OeuOt7sOO9g1@o|C$OD23QID1`!SThN9E!Mz_C7>0pm z*%*e#vh39OvN%H<9!!sNE+zo+MJF~5)62SIWPHTT@}zQ%Hq!rBipUbC3Al+t)@ocI oOa9%t$z-%F5k2~1`rnd&0ZH*kVr`2>ivR!s07*qoM6N<$g7C>$5dZ)H literal 0 HcmV?d00001 diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a80aaa8880bfe66296b4e1d4b0e6b04dd48e16b9 GIT binary patch literal 23448 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 literal 0 HcmV?d00001 diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..2410685 --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,9 @@ +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_process::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..2b2d26d --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + local_transcription_lib::run() +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..5fc1686 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,42 @@ +{ + "productName": "Local Transcription", + "version": "1.4.0", + "identifier": "com.localtranscription.app", + "build": { + "frontendDist": "../dist", + "devUrl": "http://localhost:1420", + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build" + }, + "app": { + "windows": [ + { + "title": "Local Transcription", + "width": 800, + "height": 600, + "minWidth": 640, + "minHeight": 480, + "resizable": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.ico", + "icons/icon.png" + ] + }, + "plugins": { + "shell": { + "open": true + } + } +} diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..b7d0aa1 --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,99 @@ + + +

+ +{#if showSettings} + +{/if} + + diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..53cf58d --- /dev/null +++ b/src/app.css @@ -0,0 +1,312 @@ +/* Global dark theme styles for Local Transcription */ + +:root { + --bg-primary: #1e1e1e; + --bg-secondary: #2d2d2d; + --bg-tertiary: #3a3a3a; + --bg-hover: #454545; + --text-primary: #e0e0e0; + --text-secondary: #a0a0a0; + --text-muted: #707070; + --accent-green: #4caf50; + --accent-green-hover: #45a049; + --accent-red: #f44336; + --accent-red-hover: #d32f2f; + --accent-blue: #2196f3; + --accent-blue-hover: #1976d2; + --accent-orange: #ff9800; + --border-color: #444; + --border-color-light: #555; + --scrollbar-track: #2d2d2d; + --scrollbar-thumb: #555; + --scrollbar-thumb-hover: #777; +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body { + height: 100%; + width: 100%; + overflow: hidden; +} + +body { + background-color: var(--bg-primary); + color: var(--text-primary); + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#app { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; +} + +/* Buttons */ +button { + font-family: inherit; + font-size: 13px; + font-weight: 500; + padding: 8px 16px; + border: 1px solid var(--border-color); + border-radius: 6px; + background-color: var(--bg-secondary); + color: var(--text-primary); + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, + transform 0.1s ease; + user-select: none; +} + +button:hover { + background-color: var(--bg-hover); + border-color: var(--border-color-light); +} + +button:active { + transform: scale(0.98); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +button.primary { + background-color: var(--accent-green); + border-color: var(--accent-green); + color: white; +} + +button.primary:hover { + background-color: var(--accent-green-hover); +} + +button.danger { + background-color: var(--accent-red); + border-color: var(--accent-red); + color: white; +} + +button.danger:hover { + background-color: var(--accent-red-hover); +} + +/* Inputs and Selects */ +input[type="text"], +input[type="password"], +input[type="number"], +input[type="url"], +input[type="email"], +select, +textarea { + font-family: inherit; + font-size: 13px; + padding: 8px 12px; + border: 1px solid var(--border-color); + border-radius: 6px; + background-color: var(--bg-secondary); + color: var(--text-primary); + outline: none; + transition: border-color 0.15s ease; + width: 100%; +} + +input[type="text"]:focus, +input[type="password"]:focus, +input[type="number"]:focus, +input[type="url"]:focus, +input[type="email"]:focus, +select:focus, +textarea:focus { + border-color: var(--accent-blue); +} + +input[type="text"]::placeholder, +input[type="password"]::placeholder, +input[type="url"]::placeholder { + color: var(--text-muted); +} + +select { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23a0a0a0' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 30px; +} + +/* Color input */ +input[type="color"] { + width: 50px; + height: 36px; + border: 1px solid var(--border-color); + border-radius: 6px; + background-color: var(--bg-secondary); + cursor: pointer; + padding: 2px; +} + +input[type="color"]::-webkit-color-swatch-wrapper { + padding: 2px; +} + +input[type="color"]::-webkit-color-swatch { + border: none; + border-radius: 3px; +} + +/* Range slider */ +input[type="range"] { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 6px; + background: var(--bg-tertiary); + border-radius: 3px; + outline: none; + cursor: pointer; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent-blue); + cursor: pointer; + border: 2px solid var(--bg-primary); +} + +input[type="range"]::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent-blue); + cursor: pointer; + border: 2px solid var(--bg-primary); +} + +/* Toggle / Checkbox styled as switch */ +input[type="checkbox"] { + position: relative; + width: 40px; + height: 22px; + -webkit-appearance: none; + appearance: none; + background-color: var(--bg-tertiary); + border-radius: 11px; + cursor: pointer; + transition: background-color 0.2s ease; + flex-shrink: 0; +} + +input[type="checkbox"]::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 18px; + height: 18px; + background-color: var(--text-secondary); + border-radius: 50%; + transition: transform 0.2s ease, background-color 0.2s ease; +} + +input[type="checkbox"]:checked { + background-color: var(--accent-green); +} + +input[type="checkbox"]:checked::after { + transform: translateX(18px); + background-color: white; +} + +/* Radio buttons */ +input[type="radio"] { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + border: 2px solid var(--border-color); + border-radius: 50%; + background-color: var(--bg-secondary); + cursor: pointer; + position: relative; + flex-shrink: 0; +} + +input[type="radio"]:checked { + border-color: var(--accent-blue); +} + +input[type="radio"]:checked::after { + content: ""; + position: absolute; + top: 3px; + left: 3px; + width: 8px; + height: 8px; + background-color: var(--accent-blue); + border-radius: 50%; +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--scrollbar-track); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +/* Firefox scrollbar */ +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +/* Links */ +a { + color: var(--accent-blue); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* Label */ +label { + font-size: 13px; + color: var(--text-secondary); + display: flex; + align-items: center; + gap: 8px; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..72dbacb --- /dev/null +++ b/src/main.ts @@ -0,0 +1,6 @@ +import App from "./App.svelte"; +import { mount } from "svelte"; +import "./app.css"; + +const app = mount(App, { target: document.getElementById("app")! }); +export default app; diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..d0e6448 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + preprocess: vitePreprocess(), +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..012f090 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force", + "strict": true + }, + "include": ["src/**/*.ts", "src/**/*.svelte"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..a1c0f26 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import path from "path"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], + clearScreen: false, + resolve: { + alias: { + $lib: path.resolve("./src/lib"), + }, + }, + server: { + port: 1420, + strictPort: true, + watch: { + ignored: ["**/src-tauri/**", "**/client/**", "**/server/**", "**/backend/**", "**/gui/**"], + }, + }, +}); -- 2.47.3 From 25d2a55efb695142f22885a25f772effb68515e4 Mon Sep 17 00:00:00 2001 From: Developer Date: Mon, 6 Apr 2026 11:44:34 -0700 Subject: [PATCH 3/5] Add Gitea CI/CD workflows for cross-platform builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two workflows adapted from voice-to-notes: - release.yml: Builds the Tauri app shell (.deb/.rpm for Linux, .msi for Windows, .dmg for macOS) on push to main. Auto-bumps version, creates Gitea release, uploads platform binaries. - build-sidecar.yml: Builds the headless Python backend sidecar via PyInstaller when client/server/backend code changes. Produces CUDA and CPU variants for Linux/Windows, CPU-only for macOS. Uses the new local-transcription-headless.spec (no PySide6 dependencies). Also adds local-transcription-headless.spec — a simplified PyInstaller config for the headless backend that excludes all Qt/PySide6 imports. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/build-sidecar.yml | 414 +++++++++++++++++++++++++++++ .gitea/workflows/release.yml | 300 +++++++++++++++++++++ local-transcription-headless.spec | 184 +++++++++++++ 3 files changed, 898 insertions(+) create mode 100644 .gitea/workflows/build-sidecar.yml create mode 100644 .gitea/workflows/release.yml create mode 100644 local-transcription-headless.spec diff --git a/.gitea/workflows/build-sidecar.yml b/.gitea/workflows/build-sidecar.yml new file mode 100644 index 0000000..5d23c83 --- /dev/null +++ b/.gitea/workflows/build-sidecar.yml @@ -0,0 +1,414 @@ +name: Build Sidecars + +on: + push: + branches: [main] + paths: + - 'client/**' + - 'server/**' + - 'backend/**' + - 'pyproject.toml' + - 'local-transcription-headless.spec' + workflow_dispatch: + +jobs: + bump-sidecar-version: + name: Bump sidecar version and tag + if: "!contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + outputs: + version: ${{ steps.bump.outputs.version }} + tag: ${{ steps.bump.outputs.tag }} + has_changes: ${{ steps.check_changes.outputs.has_changes }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Check for backend changes + id: check_changes + run: | + # If triggered by workflow_dispatch, always build + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + exit 0 + fi + # Check if relevant files changed in this commit + CHANGED=$(git diff --name-only HEAD~1 HEAD -- client/ server/ backend/ pyproject.toml local-transcription-headless.spec 2>/dev/null || echo "") + if [ -n "$CHANGED" ]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "Backend changes detected: $CHANGED" + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "No backend changes detected, skipping sidecar build" + fi + + - name: Configure git + if: steps.check_changes.outputs.has_changes == 'true' + run: | + git config user.name "Gitea Actions" + git config user.email "actions@gitea.local" + + - name: Bump sidecar patch version + if: steps.check_changes.outputs.has_changes == 'true' + id: bump + run: | + # Read current version from pyproject.toml + CURRENT=$(grep '^version = ' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/') + echo "Current sidecar version: ${CURRENT}" + + # Increment patch number + MAJOR=$(echo "${CURRENT}" | cut -d. -f1) + MINOR=$(echo "${CURRENT}" | cut -d. -f2) + PATCH=$(echo "${CURRENT}" | cut -d. -f3) + NEW_PATCH=$((PATCH + 1)) + NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" + echo "New sidecar version: ${NEW_VERSION}" + + # Update pyproject.toml + sed -i "s/^version = \"${CURRENT}\"/version = \"${NEW_VERSION}\"/" pyproject.toml + + # Update version.py + sed -i "s/__version__ = \"${CURRENT}\"/__version__ = \"${NEW_VERSION}\"/" version.py + sed -i "s/__version_info__ = .*/__version_info__ = (${MAJOR}, ${MINOR}, ${NEW_PATCH})/" version.py + + echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT + echo "tag=sidecar-v${NEW_VERSION}" >> $GITHUB_OUTPUT + + - name: Commit and tag + if: steps.check_changes.outputs.has_changes == 'true' + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + NEW_VERSION="${{ steps.bump.outputs.version }}" + TAG="${{ steps.bump.outputs.tag }}" + git add pyproject.toml version.py + git commit -m "chore: bump sidecar version to ${NEW_VERSION} [skip ci]" + git tag "${TAG}" + + REMOTE_URL=$(git remote get-url origin | sed "s|://|://gitea-actions:${BUILD_TOKEN}@|") + git pull --rebase "${REMOTE_URL}" main || true + git push "${REMOTE_URL}" HEAD:main + git push "${REMOTE_URL}" "${TAG}" + + - name: Create Gitea release + if: steps.check_changes.outputs.has_changes == 'true' + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + TAG="${{ steps.bump.outputs.tag }}" + VERSION="${{ steps.bump.outputs.version }}" + RELEASE_NAME="Sidecar v${VERSION}" + + curl -s -X POST \ + -H "Authorization: token ${BUILD_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\": \"${TAG}\", \"name\": \"${RELEASE_NAME}\", \"body\": \"Automated sidecar build.\", \"draft\": false, \"prerelease\": false}" \ + "${REPO_API}/releases" + echo "Created release: ${RELEASE_NAME}" + + # ── Linux sidecar (CUDA + CPU) ── + + build-sidecar-linux: + name: Build Sidecar (Linux) + needs: bump-sidecar-version + if: needs.bump-sidecar-version.outputs.has_changes == 'true' + runs-on: ubuntu-latest + env: + PYTHON_VERSION: "3.11" + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.bump-sidecar-version.outputs.tag }} + + - name: Install uv + run: | + if command -v uv &> /dev/null; then + echo "uv already installed: $(uv --version)" + else + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + fi + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y portaudio19-dev + + - name: Build sidecar (CUDA) + run: | + uv sync + uv run pyinstaller local-transcription-headless.spec + + - name: Package sidecar (CUDA) + run: | + cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cuda.zip . + + - name: Build sidecar (CPU) + run: | + rm -rf dist/local-transcription-backend build/ + # Install CPU-only PyTorch + uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall + uv run pyinstaller local-transcription-headless.spec + + - name: Package sidecar (CPU) + run: | + cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cpu.zip . + + - name: Upload to sidecar release + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + sudo apt-get install -y jq + REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + TAG="${{ needs.bump-sidecar-version.outputs.tag }}" + + echo "Waiting for sidecar release ${TAG} to be available..." + for i in $(seq 1 30); do + RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/tags/${TAG}") + RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty') + + if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then + echo "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})" + break + fi + + echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..." + sleep 10 + done + + if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then + echo "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts." + exit 1 + fi + + for file in sidecar-*.zip; do + filename=$(basename "$file") + encoded_name=$(echo "$filename" | sed 's/ /%20/g') + echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..." + + ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty") + if [ -n "${ASSET_ID}" ]; then + curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}" + fi + + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token ${BUILD_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + -T "$file" \ + "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}") + echo "Upload response: HTTP ${HTTP_CODE}" + done + + # ── Windows sidecar (CUDA + CPU) ── + + build-sidecar-windows: + name: Build Sidecar (Windows) + needs: bump-sidecar-version + if: needs.bump-sidecar-version.outputs.has_changes == 'true' + runs-on: windows-latest + env: + PYTHON_VERSION: "3.11" + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.bump-sidecar-version.outputs.tag }} + + - name: Install uv + shell: powershell + run: | + if (Get-Command uv -ErrorAction SilentlyContinue) { + Write-Host "uv already installed: $(uv --version)" + } else { + irm https://astral.sh/uv/install.ps1 | iex + echo "$env:USERPROFILE\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + } + + - name: Set up Python + shell: powershell + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install 7-Zip + shell: powershell + run: | + if (-not (Get-Command 7z -ErrorAction SilentlyContinue)) { + choco install 7zip -y + } + + - name: Build sidecar (CUDA) + shell: powershell + run: | + uv sync + uv run pyinstaller local-transcription-headless.spec + + - name: Package sidecar (CUDA) + shell: powershell + run: | + 7z a -tzip -mx=5 sidecar-windows-x86_64-cuda.zip .\dist\local-transcription-backend\* + + - name: Build sidecar (CPU) + shell: powershell + run: | + Remove-Item -Recurse -Force dist\local-transcription-backend, build -ErrorAction SilentlyContinue + uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall + uv run pyinstaller local-transcription-headless.spec + + - name: Package sidecar (CPU) + shell: powershell + run: | + 7z a -tzip -mx=5 sidecar-windows-x86_64-cpu.zip .\dist\local-transcription-backend\* + + - name: Upload to sidecar release + shell: powershell + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + $REPO_API = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}" + $Headers = @{ "Authorization" = "token $env:BUILD_TOKEN" } + $TAG = "${{ needs.bump-sidecar-version.outputs.tag }}" + + Write-Host "Waiting for sidecar release ${TAG} to be available..." + $RELEASE_ID = $null + + for ($i = 1; $i -le 30; $i++) { + try { + $release = Invoke-RestMethod -Uri "${REPO_API}/releases/tags/${TAG}" -Headers $Headers -ErrorAction Stop + $RELEASE_ID = $release.id + + if ($RELEASE_ID) { + Write-Host "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})" + break + } + } catch {} + + Write-Host "Attempt ${i}/30: Release not ready yet, retrying in 10s..." + Start-Sleep -Seconds 10 + } + + if (-not $RELEASE_ID) { + Write-Host "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts." + exit 1 + } + + Get-ChildItem -Path . -Filter "sidecar-*.zip" | ForEach-Object { + $filename = $_.Name + $encodedName = [System.Uri]::EscapeDataString($filename) + $size = [math]::Round($_.Length / 1MB, 1) + Write-Host "Uploading ${filename} (${size} MB)..." + + try { + $assets = Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets" -Headers $Headers + $existing = $assets | Where-Object { $_.name -eq $filename } + if ($existing) { + Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets/$($existing.id)" -Method Delete -Headers $Headers + } + } catch {} + + $uploadUrl = "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encodedName}" + $result = curl.exe --fail --silent --show-error ` + -X POST ` + -H "Authorization: token $env:BUILD_TOKEN" ` + -H "Content-Type: application/octet-stream" ` + -T "$($_.FullName)" ` + "$uploadUrl" 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host "Upload successful: ${filename}" + } else { + Write-Host "WARNING: Upload failed for ${filename}: ${result}" + } + } + + # ── macOS sidecar (CPU only — no CUDA on macOS) ── + + build-sidecar-macos: + name: Build Sidecar (macOS) + needs: bump-sidecar-version + if: needs.bump-sidecar-version.outputs.has_changes == 'true' + runs-on: macos-latest + env: + PYTHON_VERSION: "3.11" + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.bump-sidecar-version.outputs.tag }} + + - name: Install uv + run: | + if command -v uv &> /dev/null; then + echo "uv already installed: $(uv --version)" + else + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + fi + + - name: Set up Python + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: Install system dependencies + run: brew install portaudio + + - name: Build sidecar (CPU) + run: | + # Install CPU-only PyTorch for macOS (MPS support included in default torch) + uv sync + uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall + uv run pyinstaller local-transcription-headless.spec + + - name: Package sidecar (CPU) + run: | + cd dist/local-transcription-backend && zip -r ../../sidecar-macos-aarch64-cpu.zip . + + - name: Upload to sidecar release + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + which jq || brew install jq + REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + TAG="${{ needs.bump-sidecar-version.outputs.tag }}" + + echo "Waiting for sidecar release ${TAG} to be available..." + for i in $(seq 1 30); do + RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/tags/${TAG}") + RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty') + + if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then + echo "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})" + break + fi + + echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..." + sleep 10 + done + + if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then + echo "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts." + exit 1 + fi + + for file in sidecar-*.zip; do + filename=$(basename "$file") + encoded_name=$(echo "$filename" | sed 's/ /%20/g') + echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..." + + ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty") + if [ -n "${ASSET_ID}" ]; then + curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}" + fi + + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token ${BUILD_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + -T "$file" \ + "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}") + echo "Upload response: HTTP ${HTTP_CODE}" + done diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..42aed67 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,300 @@ +name: Release + +on: + push: + branches: [main] + +jobs: + bump-version: + name: Bump version and tag + if: "!contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + outputs: + new_version: ${{ steps.bump.outputs.new_version }} + tag: ${{ steps.bump.outputs.tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure git + run: | + git config user.name "Gitea Actions" + git config user.email "actions@gitea.local" + + - name: Bump patch version + id: bump + run: | + # Read current version from package.json + CURRENT=$(grep '"version"' package.json | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/') + echo "Current version: ${CURRENT}" + + # Increment patch number + MAJOR=$(echo "${CURRENT}" | cut -d. -f1) + MINOR=$(echo "${CURRENT}" | cut -d. -f2) + PATCH=$(echo "${CURRENT}" | cut -d. -f3) + NEW_PATCH=$((PATCH + 1)) + NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" + echo "New version: ${NEW_VERSION}" + + # Update package.json + sed -i "s/\"version\": \"${CURRENT}\"/\"version\": \"${NEW_VERSION}\"/" package.json + + # Update src-tauri/tauri.conf.json + sed -i "s/\"version\": \"${CURRENT}\"/\"version\": \"${NEW_VERSION}\"/" src-tauri/tauri.conf.json + + # Update src-tauri/Cargo.toml + sed -i "s/^version = \"${CURRENT}\"/version = \"${NEW_VERSION}\"/" src-tauri/Cargo.toml + + # Update version.py + sed -i "s/__version__ = \"${CURRENT}\"/__version__ = \"${NEW_VERSION}\"/" version.py + sed -i "s/__version_info__ = .*/__version_info__ = (${MAJOR}, ${MINOR}, ${NEW_PATCH})/" version.py + + echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT + echo "tag=v${NEW_VERSION}" >> $GITHUB_OUTPUT + + - name: Commit and tag + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + NEW_VERSION="${{ steps.bump.outputs.new_version }}" + git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml version.py + git commit -m "chore: bump version to ${NEW_VERSION} [skip ci]" + git tag "v${NEW_VERSION}" + + REMOTE_URL=$(git remote get-url origin | sed "s|://|://gitea-actions:${BUILD_TOKEN}@|") + git pull --rebase "${REMOTE_URL}" main || true + git push "${REMOTE_URL}" HEAD:main + git push "${REMOTE_URL}" "v${NEW_VERSION}" + + - name: Create Gitea release + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + TAG="${{ steps.bump.outputs.tag }}" + RELEASE_NAME="Local Transcription ${TAG}" + + curl -s -X POST \ + -H "Authorization: token ${BUILD_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\": \"${TAG}\", \"name\": \"${RELEASE_NAME}\", \"body\": \"Automated build.\", \"draft\": false, \"prerelease\": false}" \ + "${REPO_API}/releases" + echo "Created release: ${RELEASE_NAME}" + + # ── Platform builds (run after version bump) ── + + build-linux: + name: Build App (Linux) + needs: bump-version + runs-on: ubuntu-latest + env: + NODE_VERSION: "20" + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.bump-version.outputs.tag }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Rust stable + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils rpm + + - name: Install npm dependencies + run: npm ci + + - name: Build Tauri app + run: npm run tauri build + + - name: Upload to release + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + sudo apt-get install -y jq + REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + TAG="${{ needs.bump-version.outputs.tag }}" + echo "Release tag: ${TAG}" + + RELEASE_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/tags/${TAG}" | jq -r '.id // empty') + + if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then + echo "ERROR: Failed to find release for tag ${TAG}." + exit 1 + fi + echo "Release ID: ${RELEASE_ID}" + + find src-tauri/target/release/bundle -type f \( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \) | while IFS= read -r file; do + filename=$(basename "$file") + encoded_name=$(echo "$filename" | sed 's/ /%20/g') + echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..." + + ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty") + if [ -n "${ASSET_ID}" ]; then + curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}" + fi + + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token ${BUILD_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + -T "$file" \ + "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}") + echo "Upload response: HTTP ${HTTP_CODE}" + done + + build-windows: + name: Build App (Windows) + needs: bump-version + runs-on: windows-latest + env: + NODE_VERSION: "20" + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.bump-version.outputs.tag }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Rust stable + shell: powershell + run: | + if (Get-Command rustup -ErrorAction SilentlyContinue) { + rustup default stable + } else { + Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe + .\rustup-init.exe -y --default-toolchain stable + echo "$env:USERPROFILE\.cargo\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + } + + - name: Install npm dependencies + shell: powershell + run: npm ci + + - name: Build Tauri app + shell: powershell + run: npm run tauri build + + - name: Upload to release + shell: powershell + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + $REPO_API = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}" + $Headers = @{ "Authorization" = "token $env:BUILD_TOKEN" } + $TAG = "${{ needs.bump-version.outputs.tag }}" + Write-Host "Release tag: ${TAG}" + + $release = Invoke-RestMethod -Uri "${REPO_API}/releases/tags/${TAG}" -Headers $Headers -ErrorAction Stop + $RELEASE_ID = $release.id + Write-Host "Release ID: ${RELEASE_ID}" + + Get-ChildItem -Path src-tauri\target\release\bundle -Recurse -Include *.msi,*-setup.exe | ForEach-Object { + $filename = $_.Name + $encodedName = [System.Uri]::EscapeDataString($filename) + $size = [math]::Round($_.Length / 1MB, 1) + Write-Host "Uploading ${filename} (${size} MB)..." + + try { + $assets = Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets" -Headers $Headers + $existing = $assets | Where-Object { $_.name -eq $filename } + if ($existing) { + Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets/$($existing.id)" -Method Delete -Headers $Headers + } + } catch {} + + $uploadUrl = "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encodedName}" + $result = curl.exe --fail --silent --show-error ` + -X POST ` + -H "Authorization: token $env:BUILD_TOKEN" ` + -H "Content-Type: application/octet-stream" ` + -T "$($_.FullName)" ` + "$uploadUrl" 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host "Upload successful: ${filename}" + } else { + Write-Host "WARNING: Upload failed for ${filename}: ${result}" + } + } + + build-macos: + name: Build App (macOS) + needs: bump-version + runs-on: macos-latest + env: + NODE_VERSION: "20" + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.bump-version.outputs.tag }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install Rust stable + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Install system dependencies + run: brew install --quiet create-dmg || true + + - name: Install npm dependencies + run: npm ci + + - name: Build Tauri app + run: npm run tauri build + + - name: Upload to release + env: + BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }} + run: | + which jq || brew install jq + REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + TAG="${{ needs.bump-version.outputs.tag }}" + echo "Release tag: ${TAG}" + + RELEASE_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/tags/${TAG}" | jq -r '.id // empty') + + if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then + echo "ERROR: Failed to find release for tag ${TAG}." + exit 1 + fi + echo "Release ID: ${RELEASE_ID}" + + find src-tauri/target/release/bundle -type f -name "*.dmg" | while IFS= read -r file; do + filename=$(basename "$file") + encoded_name=$(echo "$filename" | sed 's/ /%20/g') + echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..." + + ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty") + if [ -n "${ASSET_ID}" ]; then + curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \ + "${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}" + fi + + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token ${BUILD_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + -T "$file" \ + "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}") + echo "Upload response: HTTP ${HTTP_CODE}" + done diff --git a/local-transcription-headless.spec b/local-transcription-headless.spec new file mode 100644 index 0000000..ddbff43 --- /dev/null +++ b/local-transcription-headless.spec @@ -0,0 +1,184 @@ +# -*- mode: python ; coding: utf-8 -*- +"""PyInstaller spec file for headless Local Transcription backend (no PySide6/Qt). + +This builds the Python sidecar for the Tauri frontend. +Much simpler than local-transcription.spec since all Qt dependencies are removed. +""" + +import sys +import os + +block_cipher = None +is_windows = sys.platform == 'win32' + +from PyInstaller.utils.hooks import collect_submodules, collect_data_files + +# Find faster_whisper assets folder +import faster_whisper +faster_whisper_path = os.path.dirname(faster_whisper.__file__) +vad_assets_path = os.path.join(faster_whisper_path, 'assets') + +# pvporcupine resources (indirect dependency from RealtimeSTT) +try: + import pvporcupine + pvporcupine_path = os.path.dirname(pvporcupine.__file__) + pvporcupine_resources = os.path.join(pvporcupine_path, 'resources') + pvporcupine_lib = os.path.join(pvporcupine_path, 'lib') + pvporcupine_data_files = [] + if os.path.exists(pvporcupine_resources): + pvporcupine_data_files.append((pvporcupine_resources, 'pvporcupine/resources')) + if os.path.exists(pvporcupine_lib): + pvporcupine_data_files.append((pvporcupine_lib, 'pvporcupine/lib')) +except ImportError: + pvporcupine_data_files = [] + +# Data files +datas = [ + ('config/default_config.yaml', 'config'), + (vad_assets_path, 'faster_whisper/assets'), +] + pvporcupine_data_files + +# Hidden imports -- NO PySide6/Qt needed for headless backend +hiddenimports = [ + # Transcription engine + 'faster_whisper', + 'faster_whisper.transcribe', + 'faster_whisper.vad', + 'ctranslate2', + 'sounddevice', + 'scipy', + 'scipy.signal', + 'numpy', + # RealtimeSTT + 'RealtimeSTT', + 'RealtimeSTT.audio_recorder', + 'webrtcvad', + 'webrtcvad_wheels', + 'silero_vad', + # PyTorch + 'torch', + 'torch.nn', + 'torch.nn.functional', + 'torchaudio', + 'onnxruntime', + 'onnxruntime.capi', + 'onnxruntime.capi.onnxruntime_pybind11_state', + 'pyaudio', + 'halo', + 'colorama', + # FastAPI and dependencies + 'fastapi', + 'fastapi.routing', + 'fastapi.responses', + 'starlette', + 'starlette.applications', + 'starlette.routing', + 'starlette.responses', + 'starlette.websockets', + 'starlette.middleware', + 'starlette.middleware.cors', + 'pydantic', + 'pydantic.fields', + 'pydantic.main', + 'anyio', + 'anyio._backends', + 'anyio._backends._asyncio', + 'sniffio', + # Uvicorn + 'uvicorn', + 'uvicorn.logging', + 'uvicorn.loops', + 'uvicorn.loops.auto', + 'uvicorn.protocols', + 'uvicorn.protocols.http', + 'uvicorn.protocols.http.auto', + 'uvicorn.protocols.http.h11_impl', + 'uvicorn.protocols.websockets', + 'uvicorn.protocols.websockets.auto', + 'uvicorn.protocols.websockets.wsproto_impl', + 'uvicorn.lifespan', + 'uvicorn.lifespan.on', + 'h11', + 'websockets', + 'websockets.legacy', + 'websockets.legacy.server', + # HTTP client + 'requests', + 'urllib3', + 'certifi', + 'charset_normalizer', +] + +# Collect submodules for key packages +print("Collecting submodules for backend packages...") +for package in ['fastapi', 'starlette', 'pydantic', 'pydantic_core', 'anyio', 'uvicorn', 'websockets', 'h11', 'httptools', 'uvloop']: + try: + submodules = collect_submodules(package) + hiddenimports += submodules + print(f" + Collected {len(submodules)} submodules from {package}") + except Exception as e: + print(f" - Warning: Could not collect {package}: {e}") + +# Collect data files +for package in ['fastapi', 'starlette', 'pydantic', 'uvicorn', 'RealtimeSTT']: + try: + data_files = collect_data_files(package) + if data_files: + datas += data_files + print(f" + Collected {len(data_files)} data files from {package}") + except Exception: + pass + +# Pydantic critical deps +hiddenimports += [ + 'colorsys', 'decimal', 'json', 'ipaddress', 'pathlib', 'uuid', + 'email.message', 'typing_extensions', +] + +a = Analysis( + ['backend/main_headless.py'], + pathex=[], + binaries=[], + datas=datas, + hiddenimports=hiddenimports, + hookspath=['hooks'], + hooksconfig={}, + runtime_hooks=[], + excludes=['enum34', 'PySide6', 'PyQt5', 'PyQt6', 'tkinter'], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) + +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='local-transcription-backend', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, # Headless backend needs console for JSON output + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='LocalTranscription.ico' if is_windows else None, +) + +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='local-transcription-backend', +) -- 2.47.3 From 47ca74e75dc22386faa3ea2566664f67b9342e94 Mon Sep 17 00:00:00 2001 From: Developer Date: Mon, 6 Apr 2026 13:34:10 -0700 Subject: [PATCH 4/5] Update README and CLAUDE.md for Tauri rewrite Update both docs to reflect the new architecture: - Tauri v2 + Svelte 5 frontend replacing PySide6/Qt - Headless Python backend with FastAPI control API - Cross-platform support (Windows, macOS, Linux) - Deepgram remote transcription (managed/BYOK) - Gitea CI/CD workflows for automated builds - New project structure with backend/, src/, src-tauri/ - Updated development commands and build instructions Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 413 ++++++++++++++++++++++++------------------------------ README.md | 224 ++++++++++++++++++++--------- 2 files changed, 342 insertions(+), 295 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3f3997f..f7b0683 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,52 +4,108 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Local Transcription is a desktop application for real-time speech-to-text transcription designed for streamers. It uses Whisper models (via faster-whisper) to transcribe audio locally with optional multi-user server synchronization. +Local Transcription is a cross-platform desktop application for real-time speech-to-text transcription designed for streamers. It supports local Whisper models and cloud-based Deepgram transcription, with OBS browser source integration and optional multi-user sync. + +**Architecture:** Two-process model — a Tauri v2 shell (Svelte 5 frontend) communicates with a headless Python backend (sidecar) via REST API and WebSocket. **Key Features:** -- Standalone desktop GUI (PySide6/Qt) -- Local transcription with CPU/GPU support -- Built-in web server for OBS browser source integration -- Optional Node.js-based multi-user server for syncing transcriptions across users -- Noise suppression and Voice Activity Detection (VAD) -- Cross-platform builds (Linux/Windows) with PyInstaller +- Cross-platform desktop app (Windows, macOS, Linux) via Tauri v2 + Svelte 5 +- Headless Python backend with FastAPI control API +- Dual transcription modes: local Whisper or cloud Deepgram (managed/BYOK) +- Built-in web server for OBS browser source at `http://localhost:8080` +- Optional multi-user sync via Node.js server +- CUDA, MPS (Apple Silicon), and CPU support +- Auto-updates, custom fonts, configurable colors + +> **Legacy GUI:** The original PySide6/Qt GUI (`main.py`, `gui/`) still works during the transition. New features should target the Tauri frontend and headless backend. ## Project Structure ``` local-transcription/ -├── client/ # Core transcription logic -│ ├── audio_capture.py # Audio input and buffering -│ ├── transcription_engine.py # Whisper model integration -│ ├── noise_suppression.py # VAD and noise reduction -│ ├── device_utils.py # CPU/GPU device management -│ ├── config.py # Configuration management -│ └── server_sync.py # Multi-user server sync client -├── gui/ # Desktop application UI -│ ├── main_window_qt.py # Main application window (PySide6) -│ ├── settings_dialog_qt.py # Settings dialog (PySide6) -│ └── transcription_display_qt.py # Display widget -├── server/ # Web display servers -│ ├── web_display.py # FastAPI server for OBS browser source (local) -│ └── nodejs/ # Optional multi-user Node.js server -│ ├── server.js # Multi-user sync server with WebSocket -│ ├── package.json # Node.js dependencies -│ └── README.md # Server deployment documentation -├── config/ # Example configuration files -│ └── default_config.yaml # Default settings template -├── main.py # GUI application entry point -├── main_cli.py # CLI version for testing -└── pyproject.toml # Dependencies and build config +├── src/ # Svelte 5 frontend (Tauri UI) +│ ├── App.svelte # Main app shell +│ ├── app.css # Global dark theme styles +│ ├── main.ts # Svelte mount point +│ ├── lib/components/ # UI components +│ │ ├── Header.svelte # Title bar + settings button +│ │ ├── StatusBar.svelte # State indicator, device, user info +│ │ ├── Controls.svelte # Start/Stop, Clear, Save buttons +│ │ ├── TranscriptionDisplay.svelte # Scrolling transcript view +│ │ └── Settings.svelte # Full settings modal (all sections) +│ └── lib/stores/ # Svelte 5 reactive stores ($state/$derived) +│ ├── backend.ts # WebSocket + REST API client +│ ├── config.ts # App configuration fetch/update +│ └── transcriptions.ts # Transcript data management +├── src-tauri/ # Tauri v2 Rust shell +│ ├── src/lib.rs # Plugin registration (shell, dialog, process) +│ ├── src/main.rs # Entry point +│ ├── tauri.conf.json # Window, bundle, plugin config +│ └── Cargo.toml # Rust dependencies +├── backend/ # Headless Python backend (the sidecar) +│ ├── app_controller.py # Core orchestration (engine, sync, config) +│ ├── api_server.py # FastAPI REST endpoints + /ws/control +│ └── main_headless.py # Headless entry point (prints JSON to stdout) +├── client/ # Core transcription modules (used by backend) +│ ├── audio_capture.py # Audio input handling +│ ├── transcription_engine_realtime.py # RealtimeSTT / Whisper engine +│ ├── deepgram_transcription.py # Deepgram WebSocket cloud transcription +│ ├── noise_suppression.py # VAD and noise reduction +│ ├── device_utils.py # CPU/GPU/MPS detection +│ ├── config.py # YAML config management (~/.local-transcription/) +│ ├── server_sync.py # Multi-user server sync client +│ ├── instance_lock.py # Single-instance PID lock +│ └── update_checker.py # Gitea release update checker +├── gui/ # Legacy PySide6/Qt GUI (still functional) +│ ├── main_window_qt.py # Main window (orchestration lives here in legacy) +│ ├── settings_dialog_qt.py # Settings dialog +│ └── transcription_display_qt.py # Display widget +├── server/ +│ ├── web_display.py # FastAPI OBS display server (WebSocket + HTML) +│ └── nodejs/ # Optional multi-user sync server +├── .gitea/workflows/ # CI/CD +│ ├── release.yml # Tauri app builds (Linux/Windows/macOS) +│ └── build-sidecar.yml # Python sidecar builds (CUDA + CPU) +├── config/default_config.yaml # Default settings template +├── main.py # Legacy PySide6 GUI entry point +├── main_cli.py # CLI version for testing +├── version.py # Version string (__version__) +├── local-transcription.spec # PyInstaller config (legacy, includes PySide6) +├── local-transcription-headless.spec # PyInstaller config (headless sidecar, no Qt) +├── pyproject.toml # Python deps (uv, CUDA PyTorch index) +├── package.json # Node/Tauri deps +└── vite.config.ts # Vite build config ($lib alias) ``` ## Development Commands -### Installation and Setup +### Frontend (Tauri + Svelte) ```bash -# Install dependencies (creates .venv automatically) +# Install npm dependencies +npm install + +# Run Tauri in development mode (hot-reload) +npm run tauri dev + +# Build frontend only (for testing) +npx vite build + +# Type-check Svelte +npx svelte-check + +# Check Rust compiles +cd src-tauri && cargo check +``` + +### Backend (Python) +```bash +# Install Python dependencies uv sync -# Run the GUI application +# Run the headless backend standalone (for development) +uv run python -m backend.main_headless --port 8080 + +# Run the legacy PySide6 GUI uv run python main.py # Run CLI version (headless, for testing) @@ -57,257 +113,154 @@ uv run python main_cli.py # List available audio devices uv run python main_cli.py --list-devices - -# Install with CUDA support (if needed) -uv pip install torch --index-url https://download.pytorch.org/whl/cu121 ``` -### Building Executables +### Building ```bash -# Linux (includes CUDA support - works on both GPU and CPU systems) -./build.sh +# Build Tauri app (produces platform installer) +npm run tauri build -# Windows (includes CUDA support - works on both GPU and CPU systems) -build.bat +# Build headless Python sidecar (no PySide6) +uv run pyinstaller local-transcription-headless.spec +# Output: dist/local-transcription-backend/ -# Manual build with PyInstaller -uv sync # Install dependencies (includes CUDA PyTorch) -uv pip uninstall -q enum34 # Remove incompatible enum34 package +# Build legacy PySide6 app uv run pyinstaller local-transcription.spec +# Or use: ./build.sh (Linux) / build.bat (Windows) ``` -**Important:** All builds include CUDA support via `pyproject.toml` configuration. CUDA builds can be created on systems without NVIDIA GPUs. The PyTorch CUDA runtime is bundled, and the app automatically falls back to CPU if no GPU is available. - ### Testing ```bash -# Run component tests uv run python test_components.py - -# Check CUDA availability uv run python check_cuda.py - -# Test web server manually -uv run python -m uvicorn server.web_display:app --reload ``` -## Architecture +## Architecture Details -### Audio Processing Pipeline +### Communication: Tauri <-> Python Backend -1. **Audio Capture** ([client/audio_capture.py](client/audio_capture.py)) - - Captures audio from microphone/system using sounddevice - - Handles automatic sample rate detection and resampling - - Uses chunking with overlap for better transcription quality - - Default: 3-second chunks with 0.5s overlap +The Svelte frontend connects to the Python backend via two channels: -2. **Noise Suppression** ([client/noise_suppression.py](client/noise_suppression.py)) - - Applies noisereduce for background noise reduction - - Voice Activity Detection (VAD) using webrtcvad - - Skips silent segments to improve performance +**REST API** (on port 8081 by default): +- `GET /api/status` — app state, device info, version +- `POST /api/start` / `POST /api/stop` — transcription control +- `GET /api/config` / `PUT /api/config` — read/write settings (dot-notation keys) +- `GET /api/audio-devices` / `GET /api/compute-devices` — device enumeration +- `POST /api/reload-engine` — reload with new model/device +- `GET /api/transcriptions` / `POST /api/clear` — transcript management +- `POST /api/save-file` — write text to a file path +- `GET /api/check-update` / `POST /api/skip-version` — update management +- `POST /api/login` / `POST /api/register` / `GET /api/balance` — managed mode proxy -3. **Transcription** ([client/transcription_engine.py](client/transcription_engine.py)) - - Uses faster-whisper for efficient inference - - Supports CPU, CUDA, and Apple MPS (Mac) - - Models: tiny, base, small, medium, large - - Thread-safe model loading with locks +**WebSocket** `/ws/control`: +- Pushes real-time events: `state_changed`, `transcription`, `preview`, `error`, `credits_low` +- Client sends keepalive pings -4. **Display** ([gui/main_window_qt.py](gui/main_window_qt.py)) - - PySide6/Qt-based desktop GUI - - Real-time transcription display with scrolling - - Settings panel with live updates (no restart needed) +The OBS display server runs separately on port 8080 (`GET /` for HTML, `WebSocket /ws` for transcriptions). -### Web Server Architecture +### Backend Process Lifecycle -**Local Web Server** ([server/web_display.py](server/web_display.py)) -- Always runs when GUI starts (port 8080 by default) -- FastAPI with WebSocket for real-time updates -- Used for OBS browser source integration -- Single-user (displays only local transcriptions) +1. `main_headless.py` starts, acquires instance lock, creates `AppController` +2. `AppController.initialize()` starts the OBS web server (port 8080) and engine init thread +3. `APIServer` wraps the controller with FastAPI routes, runs on port 8081 +4. Backend prints `{"event": "ready", "port": 8080}` to stdout for Tauri to discover +5. On shutdown: engine stopped, web server stopped, lock released -**Multi-User Server** (Optional - for syncing across multiple users) +### Headless Backend vs Legacy GUI -**Node.js WebSocket Server** ([server/nodejs/](server/nodejs/)) - **RECOMMENDED** -- Real-time WebSocket support (< 100ms latency) -- Handles 100+ concurrent users -- Easy deployment to VPS/cloud hosting (Railway, Heroku, DigitalOcean, or any VPS) -- Configurable display options via URL parameters: - - `timestamps=true/false` - Show/hide timestamps - - `maxlines=50` - Maximum visible lines (prevents scroll bars in OBS) - - `fontsize=16` - Font size in pixels - - `fontfamily=Arial` - Font family - - `fade=10` - Seconds before text fades (0 = never) +The `AppController` class (`backend/app_controller.py`) extracts all orchestration logic from `gui/main_window_qt.py` into a Qt-free class. The mapping: -See [server/nodejs/README.md](server/nodejs/README.md) for deployment instructions +| Legacy (MainWindow) | Headless (AppController) | +|---------------------|--------------------------| +| `_initialize_components()` | `_initialize_engine()` | +| `_start_transcription()` | `start_transcription()` | +| `_stop_transcription()` | `stop_transcription()` | +| `_on_settings_saved()` | `apply_settings()` | +| `_reload_engine()` | `reload_engine()` | +| `_start_web_server_if_enabled()` | `_start_web_server()` | +| `_start_server_sync()` | `_start_server_sync()` | +| Qt signals | Callbacks (`on_state_changed`, `on_transcription`, etc.) | -### Configuration System +### Threading Model (Headless) -- Config stored at `~/.local-transcription/config.yaml` -- Managed by [client/config.py](client/config.py) -- Settings apply immediately without restart (except model changes) -- YAML format with nested keys (e.g., `transcription.model`) +- Main thread: Uvicorn (FastAPI) event loop +- Engine init thread: Downloads models, initializes VAD +- Web server thread: Separate asyncio loop for OBS display +- Audio capture: Runs in engine callback threads +- All results flow through `AppController` callbacks -> `APIServer` WebSocket broadcast -### Device Management +### Svelte Frontend -- [client/device_utils.py](client/device_utils.py) handles CPU/GPU detection -- Auto-detects CUDA, MPS (Mac), or falls back to CPU -- Compute types: float32 (best quality), float16 (GPU), int8 (fastest) -- Thread-safe device selection +Uses Svelte 5 runes throughout (`$state`, `$derived`, `$effect`, `$props`). No Svelte 4 patterns. -## Key Implementation Details +**Stores** (`src/lib/stores/`): +- `backend.ts` — WebSocket connection + REST helpers (`apiGet`, `apiPost`, `apiPut`), auto-reconnect +- `config.ts` — fetches/updates config from backend API +- `transcriptions.ts` — manages transcript list, listens for `CustomEvent`s from backend store -### PyInstaller Build Configuration +**Key patterns:** +- Backend store dispatches `CustomEvent`s on `window` for cross-store communication +- Settings component collects all changed values into a `Record` with dot-notation keys, sends via `PUT /api/config` +- Controls use Tauri dialog plugin for native file save, falls back to blob download -- [local-transcription.spec](local-transcription.spec) controls build -- UPX compression enabled for smaller executables -- Hidden imports required for PySide6, faster-whisper, torch -- Console mode enabled by default (set `console=False` to hide) +## CI/CD -### Threading Model +Two Gitea Actions workflows in `.gitea/workflows/`: -- Main thread: Qt GUI event loop -- Audio thread: Captures and processes audio chunks -- Web server thread: Runs FastAPI server -- Transcription: Runs in callback thread from audio capture -- All transcription results communicated via Qt signals +- **`release.yml`**: Triggers on push to `main`. Auto-bumps version, builds Tauri app on Linux/Windows/macOS, uploads `.deb`, `.rpm`, `.msi`, `.dmg` to Gitea release. +- **`build-sidecar.yml`**: Triggers on changes to `client/`, `server/`, `backend/`, `pyproject.toml`. Builds headless Python sidecar via PyInstaller. CUDA + CPU for Linux/Windows, CPU-only for macOS. -### Server Sync (Optional Multi-User Feature) - -- [client/server_sync.py](client/server_sync.py) handles server communication -- Toggle in Settings: "Enable Server Sync" -- Sends transcriptions to Node.js server via HTTP POST -- Real-time updates via WebSocket to display page -- Per-speaker font support (Web-Safe, Google Fonts, Custom uploads) -- Falls back gracefully if server unavailable +Both require a `BUILD_TOKEN` secret (Gitea API token with release write access). ## Common Patterns ### Adding a New Setting -1. Add to [config/default_config.yaml](config/default_config.yaml) -2. Update [client/config.py](client/config.py) if validation needed -3. Add UI control in [gui/settings_dialog_qt.py](gui/settings_dialog_qt.py) -4. Apply setting in relevant component (no restart if possible) -5. Emit signal to update display if needed +1. Add default to [config/default_config.yaml](config/default_config.yaml) +2. Add UI control in [src/lib/components/Settings.svelte](src/lib/components/Settings.svelte) +3. Ensure the setting is included in the save handler's config update +4. Apply in `AppController.apply_settings()` or the relevant component +5. For legacy GUI: also update [gui/settings_dialog_qt.py](gui/settings_dialog_qt.py) + +### Adding a New API Endpoint + +1. Add route in [backend/api_server.py](backend/api_server.py) `_setup_routes()` +2. Add supporting logic in [backend/app_controller.py](backend/app_controller.py) if needed +3. Call from Svelte via `backendStore.apiGet/apiPost/apiPut` ### Modifying Transcription Display -- Local GUI: [gui/transcription_display_qt.py](gui/transcription_display_qt.py) -- Local web display (OBS): [server/web_display.py](server/web_display.py) (HTML in `_get_html()`) +- Tauri UI: [src/lib/components/TranscriptionDisplay.svelte](src/lib/components/TranscriptionDisplay.svelte) +- OBS display: [server/web_display.py](server/web_display.py) (HTML in `_get_html()`) - Multi-user display: [server/nodejs/server.js](server/nodejs/server.js) (display page in `/display` route) -### Adding a New Model Size - -- Update [client/transcription_engine.py](client/transcription_engine.py) -- Add to model selector in [gui/settings_dialog_qt.py](gui/settings_dialog_qt.py) -- Update CLI argument choices in [main_cli.py](main_cli.py) - ## Dependencies -**Core:** -- `faster-whisper`: Optimized Whisper inference -- `torch`: ML framework (CUDA-enabled via special index) -- `PySide6`: Qt6 bindings for GUI -- `sounddevice`: Cross-platform audio I/O -- `noisereduce`, `webrtcvad`: Audio preprocessing - -**Web Server:** -- `fastapi`, `uvicorn`: Web server and ASGI -- `websockets`: Real-time communication - -**Build:** -- `pyinstaller`: Create standalone executables -- `uv`: Fast package manager - -**PyTorch CUDA Index:** -- Configured in [pyproject.toml](pyproject.toml) under `[[tool.uv.index]]` -- Uses PyTorch's custom wheel repository for CUDA builds -- Automatically installed with `uv sync` when using CUDA build scripts +**Frontend:** Tauri v2, Svelte 5, Vite, TypeScript +**Backend:** Python 3.9+, FastAPI, Uvicorn, RealtimeSTT, faster-whisper, PyTorch (CUDA), sounddevice +**Build:** PyInstaller (sidecar), Tauri CLI (app), uv (Python packages) +**CI:** Gitea Actions with platform-specific runners ## Platform-Specific Notes ### Linux -- Uses PulseAudio/ALSA for audio -- Build scripts use bash (`.sh` files) -- Executable: `dist/LocalTranscription/LocalTranscription` +- Tauri needs: `libgtk-3-dev`, `libwebkit2gtk-4.1-dev`, `libappindicator3-dev`, `librsvg2-dev`, `patchelf` +- Audio: PulseAudio/ALSA via sounddevice ### Windows -- Uses Windows Audio/WASAPI -- Build scripts use batch (`.bat` files) -- Executable: `dist\LocalTranscription\LocalTranscription.exe` -- Requires Visual C++ Redistributable on target systems +- Tauri needs: WebView2 (usually pre-installed on Windows 10+) +- Audio: WASAPI via sounddevice -### Cross-Building -- **Cannot cross-compile** - must build on target platform -- CI/CD should use platform-specific runners - -## Troubleshooting - -### Model Loading Issues -- Models download to `~/.cache/huggingface/` -- First run requires internet connection -- Check disk space (models: 75MB-3GB depending on size) - -### Audio Device Issues -- Run `uv run python main_cli.py --list-devices` -- Check permissions (microphone access) -- Try different device indices in settings - -### GPU Not Detected -- Run `uv run python check_cuda.py` -- Install CUDA drivers (not CUDA toolkit - bundled in build) -- Verify PyTorch sees GPU: `python -c "import torch; print(torch.cuda.is_available())"` - -### Web Server Port Conflicts -- Default port: 8080 -- Change in [gui/main_window_qt.py](gui/main_window_qt.py) or config -- Use `lsof -i :8080` (Linux) or `netstat -ano | findstr :8080` (Windows) - -## OBS Integration - -### Local Display (Single User) -1. Start Local Transcription app -2. In OBS: Add "Browser" source -3. URL: `http://localhost:8080` -4. Set dimensions (e.g., 1920x300) - -### Multi-User Display (Node.js Server) -1. Deploy Node.js server (see [server/nodejs/README.md](server/nodejs/README.md)) -2. Each user configures Server URL: `http://your-server:3000/api/send` -3. Enter same room name and passphrase -4. In OBS: Add "Browser" source -5. URL: `http://your-server:3000/display?room=ROOM&fade=10×tamps=true&maxlines=50&fontsize=16` -6. Customize URL parameters as needed: - - `timestamps=false` - Hide timestamps - - `maxlines=30` - Show max 30 lines (prevents scroll bars) - - `fontsize=18` - Larger font - - `fontfamily=Courier` - Different font - -## Performance Optimization - -**For Real-Time Transcription:** -- Use `tiny` or `base` model (faster) -- Enable GPU if available (5-10x faster) -- Increase chunk_duration for better accuracy (higher latency) -- Decrease chunk_duration for lower latency (less context) -- Enable VAD to skip silent audio - -**For Build Size Reduction:** -- Don't bundle models (download on demand) -- Use CPU-only build if no GPU users -- Enable UPX compression (already in spec) - -## Phase Status - -- ✅ **Phase 1**: Standalone desktop application (complete) -- ✅ **Web Server**: Local OBS integration (complete) -- ✅ **Builds**: PyInstaller executables (complete) -- ✅ **Phase 2**: Multi-user Node.js server (complete, optional) -- ⏸️ **Phase 3+**: Advanced features (see [NEXT_STEPS.md](NEXT_STEPS.md)) +### macOS +- Tauri needs: Xcode Command Line Tools +- Audio: CoreAudio via sounddevice +- GPU: MPS (Apple Silicon) detected by `device_utils.py` +- `Info.plist` must include `NSMicrophoneUsageDescription` for mic access +- No CUDA builds — CPU/MPS only ## Related Documentation -- [README.md](README.md) - User-facing documentation -- [BUILD.md](BUILD.md) - Detailed build instructions -- [INSTALL.md](INSTALL.md) - Installation guide -- [NEXT_STEPS.md](NEXT_STEPS.md) - Future enhancements -- [server/nodejs/README.md](server/nodejs/README.md) - Node.js server setup and deployment +- [README.md](README.md) — User-facing documentation +- [BUILD.md](BUILD.md) — Detailed build instructions +- [INSTALL.md](INSTALL.md) — Installation guide +- [server/nodejs/README.md](server/nodejs/README.md) — Node.js server setup diff --git a/README.md b/README.md index c77516a..085f0ef 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # Local Transcription -A real-time speech-to-text desktop application for streamers. Run locally on your machine with GPU or CPU, display transcriptions via OBS browser source, and optionally sync with other users through a multi-user server. +A real-time speech-to-text desktop application for streamers. Runs locally on your machine with GPU or CPU, displays transcriptions via OBS browser source, and optionally syncs with other users through a multi-user server. **Version 1.4.0** ## Features - **Real-Time Transcription**: Live speech-to-text using Whisper models with minimal latency -- **Standalone Desktop App**: PySide6/Qt GUI that works without any server +- **Cross-Platform**: Native desktop app for Windows, macOS, and Linux via [Tauri](https://tauri.app/) +- **Dual Transcription Modes**: Local (Whisper) or cloud (Deepgram) with managed billing or BYOK - **CPU & GPU Support**: Automatic detection of CUDA (NVIDIA), MPS (Apple Silicon), or CPU fallback - **Advanced Voice Detection**: Dual-layer VAD (WebRTC + Silero) for accurate speech detection - **OBS Integration**: Built-in web server for browser source capture at `http://localhost:8080` @@ -16,36 +17,70 @@ A real-time speech-to-text desktop application for streamers. Run locally on you - **Customizable Colors**: User-configurable colors for name, text, and background - **Noise Suppression**: Built-in audio preprocessing to reduce background noise - **Auto-Updates**: Automatic update checking with release notes display -- **Cross-Platform**: Builds available for Windows and Linux + +## Architecture + +The application uses a two-process architecture: + +1. **Tauri Shell** (Svelte 5 frontend) — lightweight native window (~50MB) rendering the UI +2. **Python Backend** (sidecar) — headless process running transcription, audio capture, and the OBS web server + +The Tauri frontend communicates with the Python backend via REST API and WebSocket, following the same pattern as [voice-to-notes](https://repo.anhonesthost.net/MacroPad/voice-to-notes). + +``` +Tauri App (user launches this) + └─ Spawns Python backend as sidecar + ├─ FastAPI REST API (control endpoints) + ├─ WebSocket /ws/control (real-time state + transcriptions) + ├─ OBS web display at http://localhost:8080 + └─ Transcription engine (Whisper or Deepgram) +``` + +> **Legacy GUI**: The original PySide6/Qt desktop GUI (`main.py`) still works alongside the new Tauri frontend during the transition period. ## Quick Start ### Running from Source ```bash -# Install dependencies +# Install Python dependencies uv sync -# Run the application +# Run the Tauri app (frontend + backend) +npm install +npm run tauri dev + +# Or run just the headless backend (for development) +uv run python -m backend.main_headless + +# Or run the legacy PySide6 GUI uv run python main.py ``` ### Using Pre-Built Executables -Download the latest release from the [releases page](https://repo.anhonesthost.net/streamer-tools/local-transcription/releases) and run the executable for your platform. +Download the latest release from the [releases page](https://repo.anhonesthost.net/streamer-tools/local-transcription/releases): + +- **App installer** (Tauri shell): `.msi` (Windows), `.dmg` (macOS), `.deb`/`.rpm`/`.AppImage` (Linux) +- **Sidecar** (Python backend): Download the matching `sidecar-*` zip for your platform (CUDA or CPU) ### Building from Source -**Linux:** ```bash -./build.sh -# Output: dist/LocalTranscription/LocalTranscription -``` +# Build the Tauri app +npm install +npm run tauri build +# Output: src-tauri/target/release/bundle/ -**Windows:** -```cmd +# Build the Python sidecar (headless, no Qt) +uv sync +uv run pyinstaller local-transcription-headless.spec +# Output: dist/local-transcription-backend/ + +# Build the legacy PySide6 app (Linux) +./build.sh +# Build the legacy PySide6 app (Windows) build.bat -# Output: dist\LocalTranscription\LocalTranscription.exe ``` For detailed build instructions, see [BUILD.md](BUILD.md). @@ -57,14 +92,23 @@ For detailed build instructions, see [BUILD.md](BUILD.md). 1. Launch the application 2. Select your microphone from the audio device dropdown 3. Choose a Whisper model (smaller = faster, larger = more accurate): - - `tiny.en` / `tiny` - Fastest, good for quick captions - - `base.en` / `base` - Balanced speed and accuracy - - `small.en` / `small` - Better accuracy - - `medium.en` / `medium` - High accuracy - - `large-v3` - Best accuracy (requires more resources) + - `tiny.en` / `tiny` — Fastest, good for quick captions + - `base.en` / `base` — Balanced speed and accuracy + - `small.en` / `small` — Better accuracy + - `medium.en` / `medium` — High accuracy + - `large-v3` — Best accuracy (requires more resources) 4. Click **Start** to begin transcription 5. Transcriptions appear in the main window and at `http://localhost:8080` +### Remote Transcription (Deepgram) + +Instead of local Whisper models, you can use cloud-based transcription: + +- **Managed mode**: Sign up via the transcription proxy for metered billing +- **BYOK mode**: Bring your own Deepgram API key for direct access + +Configure in Settings > Remote Transcription. + ### OBS Browser Source Setup 1. Start the Local Transcription app @@ -88,7 +132,7 @@ For syncing transcriptions across multiple users (e.g., multi-host streams or tr ## Configuration -Settings are stored at `~/.local-transcription/config.yaml` and can be modified through the GUI settings panel. +Settings are stored at `~/.local-transcription/config.yaml` and can be modified through the GUI settings panel or the REST API. ### Key Settings @@ -100,6 +144,7 @@ Settings are stored at `~/.local-transcription/config.yaml` and can be modified | `transcription.silero_sensitivity` | VAD sensitivity (0-1, lower = more sensitive) | `0.4` | | `transcription.post_speech_silence_duration` | Silence before finalizing (seconds) | `0.3` | | `transcription.continuous_mode` | Fast speaker mode for quick talkers | `false` | +| `remote.mode` | Transcription mode (local/managed/byok) | `local` | | `display.show_timestamps` | Show timestamps with transcriptions | `true` | | `display.fade_after_seconds` | Fade out time (0 = never) | `10` | | `display.font_source` | Font type (System Font/Web-Safe/Google Font/Custom File) | `System Font` | @@ -111,67 +156,114 @@ See [config/default_config.yaml](config/default_config.yaml) for all available o ``` local-transcription/ -├── client/ # Core transcription modules -│ ├── audio_capture.py # Audio input handling -│ ├── transcription_engine_realtime.py # RealtimeSTT integration -│ ├── noise_suppression.py # VAD and noise reduction -│ ├── device_utils.py # CPU/GPU detection -│ ├── config.py # Configuration management -│ ├── server_sync.py # Multi-user server client -│ └── update_checker.py # Auto-update functionality -├── gui/ # Desktop application UI -│ ├── main_window_qt.py # Main application window -│ ├── settings_dialog_qt.py # Settings dialog -│ └── transcription_display_qt.py # Display widget -├── server/ # Web servers -│ ├── web_display.py # Local FastAPI server for OBS -│ └── nodejs/ # Multi-user sync server -│ ├── server.js # Express + WebSocket server -│ └── README.md # Deployment instructions +├── src/ # Svelte 5 frontend (Tauri UI) +│ ├── App.svelte # Main app shell +│ ├── lib/components/ # UI components +│ │ ├── Header.svelte +│ │ ├── StatusBar.svelte +│ │ ├── Controls.svelte +│ │ ├── TranscriptionDisplay.svelte +│ │ └── Settings.svelte +│ └── lib/stores/ # Reactive state management +│ ├── backend.ts # WebSocket + REST API client +│ ├── config.ts # App configuration +│ └── transcriptions.ts # Transcription data +├── src-tauri/ # Tauri v2 Rust shell +│ ├── src/main.rs +│ └── tauri.conf.json +├── backend/ # Headless Python backend (sidecar) +│ ├── app_controller.py # Orchestration logic (engine, sync, config) +│ ├── api_server.py # FastAPI REST + WebSocket control API +│ └── main_headless.py # Headless entry point +├── client/ # Core transcription modules +│ ├── audio_capture.py # Audio input handling +│ ├── transcription_engine_realtime.py # RealtimeSTT / Whisper +│ ├── deepgram_transcription.py # Deepgram cloud transcription +│ ├── noise_suppression.py # VAD and noise reduction +│ ├── device_utils.py # CPU/GPU/MPS detection +│ ├── config.py # Configuration management +│ ├── server_sync.py # Multi-user server client +│ └── update_checker.py # Auto-update functionality +├── gui/ # Legacy PySide6/Qt GUI +│ ├── main_window_qt.py +│ ├── settings_dialog_qt.py +│ └── transcription_display_qt.py +├── server/ # Web servers +│ ├── web_display.py # Local FastAPI server for OBS +│ └── nodejs/ # Multi-user sync server +├── .gitea/workflows/ # CI/CD +│ ├── release.yml # Tauri app builds (all platforms) +│ └── build-sidecar.yml # Python sidecar builds (CUDA + CPU) ├── config/ -│ └── default_config.yaml # Default settings template -├── main.py # GUI entry point -├── main_cli.py # CLI version (for testing) -├── build.sh # Linux build script -├── build.bat # Windows build script -└── local-transcription.spec # PyInstaller configuration +│ └── default_config.yaml # Default settings template +├── main.py # Legacy GUI entry point +├── main_cli.py # CLI version (for testing) +├── local-transcription.spec # PyInstaller config (legacy, with PySide6) +├── local-transcription-headless.spec # PyInstaller config (headless sidecar) +├── pyproject.toml # Python dependencies +└── package.json # Node.js / Tauri dependencies ``` ## Technology Stack -### Desktop Application +### Frontend (Tauri) +- **Tauri v2** — Native cross-platform shell (Rust) +- **Svelte 5** — Reactive UI framework (TypeScript) +- **Vite** — Frontend build tool + +### Backend (Python Sidecar) - **Python 3.9+** -- **PySide6** - Qt6 GUI framework -- **RealtimeSTT** - Real-time speech-to-text with advanced VAD -- **faster-whisper** - Optimized Whisper model inference -- **PyTorch** - ML framework (CUDA-enabled) -- **sounddevice** - Cross-platform audio capture -- **webrtcvad + silero_vad** - Voice activity detection -- **noisereduce** - Noise suppression +- **FastAPI + Uvicorn** — REST API and WebSocket server +- **RealtimeSTT** — Real-time speech-to-text with advanced VAD +- **faster-whisper** — Optimized Whisper model inference (CTranslate2) +- **PyTorch** — ML framework (CUDA-enabled builds available) +- **sounddevice** — Cross-platform audio capture +- **webrtcvad + silero_vad** — Voice activity detection -### Web Servers -- **FastAPI + Uvicorn** - Local web display server -- **Node.js + Express + WebSocket** - Multi-user sync server +### Multi-User Server (Optional) +- **Node.js + Express + WebSocket** — Real-time sync server -### Build Tools -- **PyInstaller** - Executable packaging -- **uv** - Fast Python package manager +### Build & CI/CD +- **PyInstaller** — Python sidecar packaging +- **Tauri CLI** — App bundling (.msi, .dmg, .deb, .rpm, .AppImage) +- **Gitea Actions** — Automated cross-platform builds +- **uv** — Fast Python package manager + +## CI/CD + +Two Gitea Actions workflows in `.gitea/workflows/`: + +| Workflow | Trigger | Produces | +|----------|---------|----------| +| `release.yml` | Push to `main` | Tauri app installers for all platforms | +| `build-sidecar.yml` | Changes to `client/`, `server/`, `backend/`, or `pyproject.toml` | Python sidecar zips (CUDA + CPU) | + +Both workflows require a `BUILD_TOKEN` secret in the repo settings (Gitea API token with release write access). + +### Release Artifacts + +| Platform | App Installer | Sidecar (CUDA) | Sidecar (CPU) | +|----------|--------------|----------------|---------------| +| Linux x86_64 | `.deb`, `.rpm`, `.AppImage` | `sidecar-linux-x86_64-cuda.zip` | `sidecar-linux-x86_64-cpu.zip` | +| Windows x86_64 | `.msi`, `-setup.exe` | `sidecar-windows-x86_64-cuda.zip` | `sidecar-windows-x86_64-cpu.zip` | +| macOS ARM64 | `.dmg` | — | `sidecar-macos-aarch64-cpu.zip` | ## System Requirements ### Minimum -- Python 3.9+ - 4GB RAM - Any modern CPU -### Recommended (for real-time performance) +### Recommended (for local real-time transcription) - 8GB+ RAM - NVIDIA GPU with CUDA support (for GPU acceleration) -- FFmpeg (installed automatically with dependencies) ### For Building -- **Linux**: gcc, Python dev headers -- **Windows**: Visual Studio Build Tools, Python dev headers +- **Tauri app**: Node.js 20+, Rust stable, platform SDK (see [Tauri prerequisites](https://tauri.app/start/prerequisites/)) +- **Python sidecar**: Python 3.9+, uv, PyInstaller +- **Linux**: `libgtk-3-dev`, `libwebkit2gtk-4.1-dev`, `libappindicator3-dev`, `librsvg2-dev`, `patchelf` +- **Windows**: Visual Studio Build Tools, WebView2 +- **macOS**: Xcode Command Line Tools ## Troubleshooting @@ -185,7 +277,7 @@ local-transcription/ # List available audio devices uv run python main_cli.py --list-devices ``` -- Ensure microphone permissions are granted +- Ensure microphone permissions are granted (especially on macOS) - Try different device indices in settings ### GPU Not Detected @@ -193,13 +285,13 @@ uv run python main_cli.py --list-devices # Check CUDA availability uv run python -c "import torch; print(torch.cuda.is_available())" ``` -- Install NVIDIA drivers (CUDA toolkit is bundled) +- Install NVIDIA drivers (CUDA toolkit is bundled in CUDA sidecar builds) - The app automatically falls back to CPU if no GPU is available ### Web Server Port Conflicts -- Default port is 8080 +- Default port is 8080; the app tries ports 8080-8084 automatically - Change in settings or edit config file -- Check for conflicts: `lsof -i :8080` (Linux) or `netstat -ano | findstr :8080` (Windows) +- Check for conflicts: `lsof -i :8080` (Linux/macOS) or `netstat -ano | findstr :8080` (Windows) ## Use Cases @@ -222,3 +314,5 @@ MIT License - [OpenAI Whisper](https://github.com/openai/whisper) for the speech recognition model - [RealtimeSTT](https://github.com/KoljaB/RealtimeSTT) for real-time transcription capabilities - [faster-whisper](https://github.com/guillaumekln/faster-whisper) for optimized inference +- [Tauri](https://tauri.app/) for the cross-platform desktop framework +- [Deepgram](https://deepgram.com/) for cloud transcription API -- 2.47.3 From 4c519a109a40f669812d1ef00f4af3f4ed2685f8 Mon Sep 17 00:00:00 2001 From: Developer Date: Mon, 6 Apr 2026 13:42:31 -0700 Subject: [PATCH 5/5] Add missing Svelte components and stores, fix .gitignore lib/ pattern The src/lib/ directory was being excluded by a Python .gitignore rule for lib/ (meant for Python's build output). Changed to /lib/ so it only matches root-level lib/ and doesn't block src/lib/. Adds 8 files that were created but missed in the initial commit: - 5 Svelte components (Header, StatusBar, Controls, TranscriptionDisplay, Settings) - 3 TypeScript stores (backend, config, transcriptions) Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 4 +- src/lib/components/Controls.svelte | 116 +++ src/lib/components/Header.svelte | 82 ++ src/lib/components/Settings.svelte | 780 ++++++++++++++++++ src/lib/components/StatusBar.svelte | 106 +++ .../components/TranscriptionDisplay.svelte | 110 +++ src/lib/stores/backend.ts | 266 ++++++ src/lib/stores/config.ts | 243 ++++++ src/lib/stores/transcriptions.ts | 109 +++ 9 files changed, 1814 insertions(+), 2 deletions(-) create mode 100644 src/lib/components/Controls.svelte create mode 100644 src/lib/components/Header.svelte create mode 100644 src/lib/components/Settings.svelte create mode 100644 src/lib/components/StatusBar.svelte create mode 100644 src/lib/components/TranscriptionDisplay.svelte create mode 100644 src/lib/stores/backend.ts create mode 100644 src/lib/stores/config.ts create mode 100644 src/lib/stores/transcriptions.ts diff --git a/.gitignore b/.gitignore index 0ec9028..ac27095 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,8 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ +/lib/ +/lib64/ parts/ sdist/ var/ diff --git a/src/lib/components/Controls.svelte b/src/lib/components/Controls.svelte new file mode 100644 index 0000000..d3679eb --- /dev/null +++ b/src/lib/components/Controls.svelte @@ -0,0 +1,116 @@ + + +
+ + + + + +
+ + diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte new file mode 100644 index 0000000..1fcc632 --- /dev/null +++ b/src/lib/components/Header.svelte @@ -0,0 +1,82 @@ + + +
+

Local Transcription

+ +
+ + diff --git a/src/lib/components/Settings.svelte b/src/lib/components/Settings.svelte new file mode 100644 index 0000000..7cb7162 --- /dev/null +++ b/src/lib/components/Settings.svelte @@ -0,0 +1,780 @@ + + + + + + + + diff --git a/src/lib/components/StatusBar.svelte b/src/lib/components/StatusBar.svelte new file mode 100644 index 0000000..cc482df --- /dev/null +++ b/src/lib/components/StatusBar.svelte @@ -0,0 +1,106 @@ + + +
+
+ + {backendStore.stateMessage} +
+
+ {#if backendStore.deviceInfo} + {backendStore.deviceInfo} + | + {/if} + {userName} +
+
+ + diff --git a/src/lib/components/TranscriptionDisplay.svelte b/src/lib/components/TranscriptionDisplay.svelte new file mode 100644 index 0000000..77d86a5 --- /dev/null +++ b/src/lib/components/TranscriptionDisplay.svelte @@ -0,0 +1,110 @@ + + +
+ {#each items as item (item.id)} +
+ {#if showTimestamps && item.timestamp} + [{item.timestamp}] + {/if} + {#if item.userName} + {item.userName}: + {/if} + {#if item.isPreview} + [...] + {/if} + {item.text} +
+ {:else} +
+ Transcriptions will appear here... +
+ {/each} +
+ + diff --git a/src/lib/stores/backend.ts b/src/lib/stores/backend.ts new file mode 100644 index 0000000..f0293bc --- /dev/null +++ b/src/lib/stores/backend.ts @@ -0,0 +1,266 @@ +/** + * Backend store - manages WebSocket connection and REST API communication + * with the Python backend server running on localhost. + * + * The backend port defaults to 8081 but can be updated at runtime via + * `setPort()`. The WebSocket connects to /ws/control for real-time push + * of transcriptions, previews, and state changes. + */ + +export type ConnectionState = "connecting" | "connected" | "disconnected" | "error"; +export type AppState = "initializing" | "ready" | "transcribing" | "reloading" | "error"; + +interface BackendState { + port: number; + connectionState: ConnectionState; + appState: AppState; + stateMessage: string; + deviceInfo: string; + wsConnection: WebSocket | null; + version: string; + lastError: string; +} + +let state = $state({ + port: 8081, + connectionState: "disconnected", + appState: "initializing", + stateMessage: "Connecting to backend...", + deviceInfo: "", + wsConnection: null, + version: "1.4.0", + lastError: "", +}); + +let reconnectTimer: ReturnType | null = null; +let reconnectAttempts = 0; +const MAX_RECONNECT_DELAY_MS = 30_000; +const BASE_RECONNECT_DELAY_MS = 1_000; + +// ── URL helpers ────────────────────────────────────────────────────── + +function apiUrl(path: string): string { + const normalised = path.startsWith("/") ? path : `/${path}`; + return `http://localhost:${state.port}${normalised}`; +} + +async function apiFetch(path: string, options?: RequestInit): Promise { + const url = apiUrl(path); + const method = options?.method?.toUpperCase() ?? "GET"; + const headers = new Headers(options?.headers); + if (method !== "GET" && !headers.has("Content-Type")) { + headers.set("Content-Type", "application/json"); + } + return fetch(url, { ...options, headers }); +} + +// ── WebSocket management ───────────────────────────────────────────── + +function connectWebSocket() { + // Tear down any existing connection + disconnect(); + + state.connectionState = "connecting"; + reconnectAttempts = 0; + + _openSocket(); +} + +function _openSocket() { + const wsUrl = `ws://localhost:${state.port}/ws/control`; + + try { + const ws = new WebSocket(wsUrl); + + ws.onopen = () => { + state.connectionState = "connected"; + state.lastError = ""; + reconnectAttempts = 0; + if (reconnectTimer) { + clearTimeout(reconnectTimer); + reconnectTimer = null; + } + }; + + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + handleWebSocketMessage(data); + } catch { + // ignore parse errors + } + }; + + ws.onclose = () => { + state.wsConnection = null; + if (state.connectionState !== "disconnected") { + state.connectionState = "error"; + state.stateMessage = "Disconnected from backend"; + _scheduleReconnect(); + } + }; + + ws.onerror = () => { + state.lastError = "WebSocket error"; + // onclose fires after this, which handles reconnect + }; + + state.wsConnection = ws; + } catch { + state.connectionState = "error"; + state.stateMessage = "Failed to connect"; + _scheduleReconnect(); + } +} + +function _scheduleReconnect() { + if (reconnectTimer) return; + + const delay = Math.min( + BASE_RECONNECT_DELAY_MS * Math.pow(2, reconnectAttempts), + MAX_RECONNECT_DELAY_MS, + ); + reconnectAttempts++; + + reconnectTimer = setTimeout(() => { + reconnectTimer = null; + if (state.connectionState !== "disconnected") { + state.connectionState = "connecting"; + _openSocket(); + } + }, delay); +} + +function disconnect() { + if (reconnectTimer) { + clearTimeout(reconnectTimer); + reconnectTimer = null; + } + state.connectionState = "disconnected"; + if (state.wsConnection) { + const ws = state.wsConnection; + ws.onclose = null; + ws.onerror = null; + ws.close(); + state.wsConnection = null; + } +} + +// ── WebSocket message handling ─────────────────────────────────────── + +function handleWebSocketMessage(data: Record) { + // Handle state changes locally + if (data.type === "state_changed") { + if (data.state) { + state.appState = data.state as AppState; + } + if (data.message) { + state.stateMessage = data.message as string; + } + } + + if (data.type === "error") { + state.lastError = (data.message as string) ?? "Unknown error"; + } + + // Dispatch to window for other stores (transcriptions, etc.) + if (data.type === "transcription") { + window.dispatchEvent( + new CustomEvent("backend:transcription", { detail: data }) + ); + } else if (data.type === "preview") { + window.dispatchEvent( + new CustomEvent("backend:preview", { detail: data }) + ); + } else if (data.type === "credits_low") { + window.dispatchEvent( + new CustomEvent("backend:credits_low", { detail: data }) + ); + } +} + +// ── Port management ────────────────────────────────────────────────── + +function setPort(newPort: number) { + if (newPort === state.port) return; + state.port = newPort; + // Reconnect with new port if we had a connection + if (state.connectionState !== "disconnected") { + connectWebSocket(); + } +} + +// ── Typed REST helpers ─────────────────────────────────────────────── + +async function apiGet(path: string): Promise { + const resp = await apiFetch(path); + if (!resp.ok) throw new Error(`GET ${path} failed: ${resp.status}`); + return resp.json(); +} + +async function apiPost( + path: string, + body?: unknown +): Promise { + const resp = await apiFetch(path, { + method: "POST", + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + if (!resp.ok) throw new Error(`POST ${path} failed: ${resp.status}`); + return resp.json(); +} + +async function apiPut( + path: string, + body?: unknown +): Promise { + const resp = await apiFetch(path, { + method: "PUT", + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + if (!resp.ok) throw new Error(`PUT ${path} failed: ${resp.status}`); + return resp.json(); +} + +// ── Public API ─────────────────────────────────────────────────────── + +export const backendStore = { + get port() { + return state.port; + }, + get connectionState() { + return state.connectionState; + }, + get connected() { + return state.connectionState === "connected"; + }, + get appState() { + return state.appState; + }, + get stateMessage() { + return state.stateMessage; + }, + get deviceInfo() { + return state.deviceInfo; + }, + get version() { + return state.version; + }, + get lastError() { + return state.lastError; + }, + get apiBaseUrl() { + return `http://localhost:${state.port}`; + }, + get wsUrl() { + return `ws://localhost:${state.port}/ws/control`; + }, + setPort, + connect: connectWebSocket, + disconnect, + apiUrl, + apiFetch, + apiGet, + apiPost, + apiPut, +}; diff --git a/src/lib/stores/config.ts b/src/lib/stores/config.ts new file mode 100644 index 0000000..9312bba --- /dev/null +++ b/src/lib/stores/config.ts @@ -0,0 +1,243 @@ +/** + * Config store - manages application configuration loaded from + * and saved to the Python backend via the backend store's API helpers. + * + * The backend accepts PUT /api/config with `{ settings: { "dot.key": value } }`. + */ + +import { backendStore } from "$lib/stores/backend"; + +export interface AppConfig { + user: { + name: string; + id: string; + }; + audio: { + input_device: string; + sample_rate: number; + }; + transcription: { + model: string; + device: string; + language: string; + compute_type: string; + enable_realtime_transcription: boolean; + realtime_model: string; + realtime_processing_pause: number; + silero_sensitivity: number; + silero_use_onnx: boolean; + webrtc_sensitivity: number; + post_speech_silence_duration: number; + min_length_of_recording: number; + min_gap_between_recordings: number; + pre_recording_buffer_duration: number; + beam_size: number; + initial_prompt: string; + no_log_file: boolean; + continuous_mode: boolean; + }; + server_sync: { + enabled: boolean; + url: string; + room: string; + passphrase: string; + }; + display: { + show_timestamps: boolean; + max_lines: number; + font_source: string; + font_family: string; + websafe_font: string; + google_font: string; + custom_font_file: string; + font_size: number; + theme: string; + fade_after_seconds: number; + user_color: string; + text_color: string; + background_color: string; + }; + web_server: { + port: number; + host: string; + }; + remote: { + mode: string; + server_url: string; + auth_token: string; + byok_api_key: string; + deepgram_model: string; + language: string; + fallback_to_local: boolean; + }; + updates: { + auto_check: boolean; + gitea_url: string; + owner: string; + repo: string; + skipped_versions: string[]; + last_check: string; + check_interval_hours: number; + }; +} + +function getDefaultConfig(): AppConfig { + return { + user: { name: "User", id: "" }, + audio: { input_device: "default", sample_rate: 16000 }, + transcription: { + model: "base.en", + device: "auto", + language: "en", + compute_type: "default", + enable_realtime_transcription: false, + realtime_model: "tiny.en", + realtime_processing_pause: 0.1, + silero_sensitivity: 0.4, + silero_use_onnx: true, + webrtc_sensitivity: 3, + post_speech_silence_duration: 0.3, + min_length_of_recording: 0.5, + min_gap_between_recordings: 0, + pre_recording_buffer_duration: 0.2, + beam_size: 5, + initial_prompt: "", + no_log_file: true, + continuous_mode: false, + }, + server_sync: { + enabled: false, + url: "http://localhost:3000/api/send", + room: "default", + passphrase: "", + }, + display: { + show_timestamps: true, + max_lines: 100, + font_source: "System Font", + font_family: "Courier", + websafe_font: "Arial", + google_font: "Roboto", + custom_font_file: "", + font_size: 12, + theme: "dark", + fade_after_seconds: 10, + user_color: "#4CAF50", + text_color: "#FFFFFF", + background_color: "#000000B3", + }, + web_server: { port: 8080, host: "127.0.0.1" }, + remote: { + mode: "local", + server_url: "", + auth_token: "", + byok_api_key: "", + deepgram_model: "nova-2", + language: "en-US", + fallback_to_local: true, + }, + updates: { + auto_check: true, + gitea_url: "https://repo.anhonesthost.net", + owner: "streamer-tools", + repo: "local-transcription", + skipped_versions: [], + last_check: "", + check_interval_hours: 24, + }, + }; +} + +let config = $state(getDefaultConfig()); +let loading = $state(false); +let error = $state(""); + +/** + * Fetch the full configuration tree from the backend. + * GET /api/config + */ +async function fetchConfig(): Promise { + loading = true; + error = ""; + + try { + const data = await backendStore.apiGet>("/api/config"); + // Deep merge with defaults to ensure all keys exist + config = deepMerge(getDefaultConfig(), data) as AppConfig; + } catch (err) { + error = err instanceof Error ? err.message : String(err); + console.error("[config] fetchConfig failed:", error); + } finally { + loading = false; + } +} + +function deepMerge(target: Record, source: Record): Record { + const result = { ...target }; + for (const key of Object.keys(source)) { + if ( + source[key] && + typeof source[key] === "object" && + !Array.isArray(source[key]) && + target[key] && + typeof target[key] === "object" && + !Array.isArray(target[key]) + ) { + result[key] = deepMerge( + target[key] as Record, + source[key] as Record + ); + } else { + result[key] = source[key]; + } + } + return result; +} + +/** + * Send a batch of setting updates to the backend. + * PUT /api/config with body `{ settings: { "dot.key": value, ... } }` + * + * Keys use dot-notation, e.g. `{ "transcription.model": "small.en" }`. + * + * Returns the response payload on success, or throws on failure. + */ +async function updateConfig( + settings: Record, +): Promise<{ status: string; message: string; engine_reloaded: boolean }> { + loading = true; + error = ""; + + try { + const result = await backendStore.apiPut<{ + status: string; + message: string; + engine_reloaded: boolean; + }>("/api/config", { settings }); + + // Refresh the local config tree so the UI stays in sync + await fetchConfig(); + + return result; + } catch (err) { + error = err instanceof Error ? err.message : String(err); + console.error("[config] updateConfig failed:", error); + throw err; + } finally { + loading = false; + } +} + +export const configStore = { + get config() { + return config; + }, + get loading() { + return loading; + }, + get error() { + return error; + }, + fetchConfig, + updateConfig, +}; diff --git a/src/lib/stores/transcriptions.ts b/src/lib/stores/transcriptions.ts new file mode 100644 index 0000000..f7c43c7 --- /dev/null +++ b/src/lib/stores/transcriptions.ts @@ -0,0 +1,109 @@ +/** + * Transcriptions store - manages the list of transcription items + * received from the backend via WebSocket. + */ + +export interface TranscriptionItem { + id: string; + text: string; + userName: string; + timestamp: string; + isPreview: boolean; +} + +let items = $state([]); +let nextId = 0; + +function generateId(): string { + return `t-${Date.now()}-${nextId++}`; +} + +function addTranscription(data: { + text?: string; + user_name?: string; + timestamp?: string; +}) { + // When a final transcription arrives, remove any existing preview + const previewIndex = items.findIndex((item) => item.isPreview); + if (previewIndex !== -1) { + items.splice(previewIndex, 1); + } + + items.push({ + id: generateId(), + text: data.text ?? "", + userName: data.user_name ?? "", + timestamp: data.timestamp ?? "", + isPreview: false, + }); + + // Keep a reasonable limit + if (items.length > 500) { + items.splice(0, items.length - 500); + } +} + +function setPreview(data: { + text?: string; + user_name?: string; + timestamp?: string; +}) { + const existingIndex = items.findIndex((item) => item.isPreview); + const previewItem: TranscriptionItem = { + id: existingIndex !== -1 ? items[existingIndex].id : generateId(), + text: data.text ?? "", + userName: data.user_name ?? "", + timestamp: data.timestamp ?? "", + isPreview: true, + }; + + if (existingIndex !== -1) { + items[existingIndex] = previewItem; + } else { + items.push(previewItem); + } +} + +function clearAll() { + items.length = 0; +} + +function getPlainText(): string { + return items + .filter((item) => !item.isPreview) + .map((item) => { + let line = ""; + if (item.timestamp) line += `[${item.timestamp}] `; + if (item.userName) line += `${item.userName}: `; + line += item.text; + return line; + }) + .join("\n"); +} + +// Listen for backend events +if (typeof window !== "undefined") { + window.addEventListener("backend:transcription", ((e: CustomEvent) => { + addTranscription(e.detail); + }) as EventListener); + + window.addEventListener("backend:preview", ((e: CustomEvent) => { + setPreview(e.detail); + }) as EventListener); +} + +export const transcriptionStore = { + get items() { + return items; + }, + get currentPreview(): TranscriptionItem | null { + return items.find((item) => item.isPreview) ?? null; + }, + get transcriptions(): TranscriptionItem[] { + return items.filter((item) => !item.isPreview); + }, + addTranscription, + setPreview, + clearAll, + getPlainText, +}; -- 2.47.3
+
+ + + + + + + +
v{backendStore.version}
+