diff --git a/.github/actions/lint-reporter/action.yml b/.github/actions/lint-reporter/action.yml index b4912577..7a9f88cc 100644 --- a/.github/actions/lint-reporter/action.yml +++ b/.github/actions/lint-reporter/action.yml @@ -17,8 +17,18 @@ inputs: runs: using: 'composite' steps: - - name: Create or Update PR Comment + - name: Find Comment if: github.event_name == 'pull_request' + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '' + + - name: Prepare Comment Body + if: github.event_name == 'pull_request' + id: prepare uses: actions/github-script@v7 with: github-token: ${{ inputs.github-token }} @@ -26,6 +36,7 @@ runs: const title = ${{ toJSON(inputs.title) }}; const result = ${{ toJSON(inputs.lint-result) }}; const output = ${{ toJSON(inputs.lint-output) }}; + const existingCommentId = '${{ steps.fc.outputs.comment-id }}'; const icon = result === 'success' ? '✅' : '❌'; const status = result === 'success' ? 'Passed' : 'Failed'; @@ -37,24 +48,16 @@ runs: sectionContent += `\n
\nClick to see details\n\n\`\`\`\n${output}\n\`\`\`\n\n
\n`; } - const commentMarker = ''; - const issue_number = context.issue.number; - - // Find existing comment - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue_number, - }); - - const botComment = comments.data.find(comment => - comment.user.type === 'Bot' && comment.body.includes(commentMarker) - ); - let body; - if (botComment) { - // Update existing comment - const existingBody = botComment.body; + if (existingCommentId) { + // Get existing comment body + const { data: comment } = await github.rest.issues.getComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: parseInt(existingCommentId), + }); + + const existingBody = comment.body; const sectionHeader = `### ${title}`; const nextSectionRegex = /^###\s/m; @@ -86,21 +89,19 @@ runs: // Add new section at the end body = existingBody + '\n\n' + sectionContent; } - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: botComment.id, - body: body, - }); } else { // Create new comment - body = `## 🔍 Code Quality Report\n${commentMarker}\n\nThis comment is automatically updated with linting results from CI.\n\n${sectionContent}`; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue_number, - body: body, - }); - } \ No newline at end of file + body = `## 🔍 Code Quality Report\n\n\nThis comment is automatically updated with linting results from CI.\n\n${sectionContent}`; + } + + // Store the body for the next step + core.setOutput('comment_body', body); + + - name: Create or Update Comment + if: github.event_name == 'pull_request' + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: ${{ steps.prepare.outputs.comment_body }} + edit-mode: replace \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 438a231b..f6fee777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.0.0-beta.3] - 2025-06-23 + +There's too much to list! This is the version you've been waiting for. + +- Redesigned, responsive, animated frontend. +- Improved terminal width spanning and layout optimization +- File-Picker to see files on-the-go. +- Creating new Terminals is now much more reliable. +- Added terminal font size adjustment in the settings dropdown +- Fresh new icon for Progressive Web App installations +- Refined bounce animations for a more subtle, professional feel +- Added retro CRT-style phosphor decay visual effect for closed terminals +- Fixed buffer aggregator message handling for smoother terminal updates +- Better support for shell aliases and improved debug logging +- Enhanced Unix socket server implementation for faster local communication +- Special handling for Warp terminal with custom enter key behavior +- New dock menu with quick actions when right-clicking the app icon +- More resilient vt command-line tool with better error handling +- Ensured vibetunnel server properly terminates when Mac app is killed + ## [1.0.0-beta.2] - 2025-06-19 ### 🎨 Improvements diff --git a/README.md b/README.md index 3501364d..608ced7b 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,34 @@ EOF cd web npm install npm run build -node build-native.js # Creates Bun executable + +# Optional: Build with custom Node.js for smaller binary (46% size reduction) +# export VIBETUNNEL_USE_CUSTOM_NODE=YES +# node build-custom-node.js # Build optimized Node.js (one-time, ~20 min) +# npm run build # Will use custom Node.js automatically # Build the macOS app cd ../mac ./scripts/build.sh --configuration Release ``` +### Custom Node.js Builds + +VibeTunnel supports building with a custom Node.js for a 46% smaller executable (61MB vs 107MB): + +```bash +# Build custom Node.js (one-time, ~20 minutes) +node build-custom-node.js + +# Use environment variable for all builds +export VIBETUNNEL_USE_CUSTOM_NODE=YES + +# Or use in Xcode Build Settings +# Add User-Defined Setting: VIBETUNNEL_USE_CUSTOM_NODE = YES +``` + +See [Custom Node Build Flags](docs/custom-node-build-flags.md) for detailed optimization information. + ## Development For development setup and contribution guidelines, see [CONTRIBUTING.md](docs/CONTRIBUTING.md). diff --git a/appcast-prerelease.xml b/appcast-prerelease.xml index 10163cd1..796915e5 100644 --- a/appcast-prerelease.xml +++ b/appcast-prerelease.xml @@ -5,6 +5,48 @@ https://github.com/amantus-ai/vibetunnel VibeTunnel pre-release and beta updates feed en + + VibeTunnel 1.0.0-beta.3 + https://github.com/amantus-ai/vibetunnel/releases/download/v1.0.0-beta.3/VibeTunnel-1.0.0-beta.3.dmg + 110 + 1.0.0-beta.3 + VibeTunnel 1.0.0-beta.3

Pre-release version

🎉 Beta 3 Release

+

There's too much to list! This is the version you've been waiting for.

+ +

✨ Highlights

+
    +
  • Redesigned, responsive, animated frontend
  • +
  • File-Picker to see files on-the-go
  • +
  • Creating new Terminals is now much more reliable
  • +
  • Fresh new icon for Progressive Web App installations
  • +
+ +

🎨 Improvements

+
    +
  • Improved terminal width spanning and layout optimization
  • +
  • Added terminal font size adjustment in the settings dropdown
  • +
  • Refined bounce animations for a more subtle, professional feel
  • +
  • Added retro CRT-style phosphor decay visual effect for closed terminals
  • +
  • Fixed buffer aggregator message handling for smoother terminal updates
  • +
  • Better support for shell aliases and improved debug logging
  • +
  • Enhanced Unix socket server implementation for faster local communication
  • +
  • Special handling for Warp terminal with custom enter key behavior
  • +
  • New dock menu with quick actions when right-clicking the app icon
  • +
  • More resilient vt command-line tool with better error handling
  • +
  • Ensured vibetunnel server properly terminates when Mac app is killed
  • +
+

View full changelog

+ ]]>
+ Sun, 23 Jun 2025 04:30:00 +0000 + + 14.0 +
VibeTunnel 1.0.0-beta.2 https://github.com/amantus-ai/vibetunnel/releases/download/v1.0-beta.2/VibeTunnel-1.0.0-beta.2.dmg diff --git a/assets/appicon-borderless.png b/assets/appicon-borderless.png new file mode 100644 index 00000000..8846000f Binary files /dev/null and b/assets/appicon-borderless.png differ diff --git a/docs/custom-node-build-flags.md b/docs/custom-node-build-flags.md index 65b9a59b..5992c565 100644 --- a/docs/custom-node-build-flags.md +++ b/docs/custom-node-build-flags.md @@ -121,17 +121,37 @@ Each `--without-*` flag contributes to size reduction: ## Usage Instructions -1. **Build custom Node.js**: - ```bash - node build-custom-node.js --version=24.2.0 - ``` +### Building Custom Node.js -2. **Use with vibetunnel**: - ```bash - node build-native.js --custom-node="/path/to/custom/node" - ``` +```bash +node build-custom-node.js # Builds Node.js 24.2.0 (default) +node build-custom-node.js --version=24.2.0 # Specific version +node build-custom-node.js --latest # Latest version +``` -3. **Result**: A 61MB portable executable (vs 107MB with standard Node.js) +### Using Custom Node.js + +#### Option 1: Command Line +```bash +node build-native.js --custom-node # Auto-detect from .node-builds/ +node build-native.js --custom-node="/path/to/custom/node" # Specific path +``` + +#### Option 2: Environment Variable (Recommended for Xcode) +```bash +export VIBETUNNEL_USE_CUSTOM_NODE=YES # Use custom Node.js +export VIBETUNNEL_USE_CUSTOM_NODE=NO # Use system Node.js +node build-native.js # Respects environment variable +``` + +### Xcode Integration + +For macOS development, set `VIBETUNNEL_USE_CUSTOM_NODE` in Build Settings: +- **YES**: Always use custom Node.js (61MB executable) +- **NO**: Always use system Node.js (107MB executable) +- **(not set)**: Auto-detect based on build configuration + +See [Xcode Custom Node Setup](xcode-custom-node-setup.md) for detailed instructions. ## Future Optimization Opportunities diff --git a/docs/ios-enhancements.md b/docs/ios-enhancements.md new file mode 100644 index 00000000..bcee46a8 --- /dev/null +++ b/docs/ios-enhancements.md @@ -0,0 +1,62 @@ +# iOS App Enhancements + +This document tracks additional enhancements made to the VibeTunnel iOS app after achieving feature parity with the JavaScript front-end. + +## Completed Enhancements + +### 1. **Connection Status Indicator** +- Added real-time WebSocket connection status to terminal toolbar +- Shows "Connecting", "Connected", or "Disconnected" states +- Visual indicators: WiFi icon for connected, WiFi slash for disconnected +- Progress spinner during connection attempts +- File: `TerminalView.swift` - Added `connectionStatusIndicator` view + +### 2. **Session Export Functionality** +- Added "Export as Text" option to terminal menu +- Exports current terminal buffer content as a text file +- Uses iOS share sheet for saving/sharing +- Temporary file cleanup after sharing +- Files modified: + - `TerminalView.swift` - Added export menu item and sheet + - `TerminalViewModel.swift` - Added `getBufferContent()` method + - `TerminalHostingView.swift` - Added buffer content extraction + +## Architecture Improvements + +### Connection Status +The connection status indicator provides immediate visual feedback about the WebSocket connection state, helping users understand if their terminal is actively connected to the server. + +### Export Functionality +The export feature allows users to save terminal session output for documentation, debugging, or sharing purposes. The implementation reads the entire terminal buffer and formats it as plain text. + +## User Experience Enhancements + +1. **Visual Feedback**: Connection status is always visible in the toolbar +2. **Export Workflow**: Simple menu action → Share sheet → Save/Share options +3. **File Naming**: Exported files include session name and timestamp + +## Technical Implementation + +### Buffer Content Extraction +The `getBufferContent()` method in `TerminalHostingView.Coordinator`: +- Iterates through all terminal rows +- Extracts characters from each column +- Trims trailing whitespace +- Returns formatted text content + +### Share Sheet Integration +Uses native iOS `UIActivityViewController` wrapped in SwiftUI: +- Temporary file creation in app's temp directory +- Automatic cleanup after sharing +- Support for all iOS sharing destinations + +## Future Enhancement Ideas + +1. **Haptic Feedback**: Add subtle haptics for terminal interactions (already has HapticFeedback utility) +2. **iPad Keyboard Shortcuts**: Command palette, quick actions +3. **Improved Error Messages**: User-friendly error descriptions with suggested actions +4. **WebSocket Optimization**: Better reconnection strategies, connection pooling +5. **Session Templates**: Save and reuse common session configurations +6. **Multi-Window Support**: iPad multitasking with multiple terminal windows + +The iOS app now exceeds feature parity with the web version and includes native platform enhancements that improve the mobile terminal experience. \ No newline at end of file diff --git a/docs/ios-update-progress.md b/docs/ios-update-progress.md new file mode 100644 index 00000000..bd7d884e --- /dev/null +++ b/docs/ios-update-progress.md @@ -0,0 +1,115 @@ +# iOS App Update Progress + +This document tracks the implementation progress of updating the VibeTunnel iOS app to match all features available in the JavaScript front-end. + +## Update Progress + +### Completed Features ✅ + +1. **Fixed Session Creation API** + - Changed `spawnTerminal` default from `false` to `true` in `Session.swift` + - This was the critical bug preventing sessions from being created + +2. **Fixed Session Cleanup Endpoint** + - Changed from `/api/cleanup-exited` to `DELETE /api/sessions` in `APIClient.swift` + - Now matches the JavaScript implementation + +3. **Implemented SSE Client** + - Created `SSEClient.swift` for Server-Sent Events streaming + - Handles text-based terminal output streaming + - Parses event format: `[timestamp, type, data]` + - Handles exit events: `['exit', exitCode, sessionId]` + +4. **Added Terminal Renderer Switcher** + - Created `TerminalRenderer.swift` enum for renderer selection + - Added debug menu in `TerminalView.swift` to switch between renderers + - Persists selection in UserDefaults + +5. **Created xterm WebView Implementation** + - Created `XtermWebView.swift` using WKWebView + - Loads xterm.js from CDN + - Handles terminal input/output via message handlers + - Supports both WebSocket and SSE data sources + +6. **Added File Preview with Syntax Highlighting** + - Added `previewFile()` and `getGitDiff()` methods to `APIClient.swift` + - Created `FilePreviewView.swift` with WebView-based syntax highlighting + - Uses highlight.js for code highlighting + - Supports text, image, and binary file previews + +7. **Added Git Diff Viewer** + - Integrated into `FilePreviewView.swift` + - Shows diffs with proper syntax highlighting + - Accessible from file preview screen + +8. **Updated File Browser** + - Modified `FileBrowserView.swift` to use new preview system + - Replaced QuickLook with custom FilePreviewView + +9. **Added System Logs Viewer** + - Added logs API endpoints to `APIClient.swift` (`getLogsRaw`, `getLogsInfo`, `clearLogs`) + - Created `SystemLogsView.swift` with full feature parity: + - Real-time log display with 2-second auto-refresh + - Filter by log level (All, Error, Warn, Log, Debug) + - Filter by source (Client/Server) + - Text search functionality + - Auto-scroll toggle + - Download logs capability + - Clear logs with confirmation + - Added access from Settings → Advanced → View System Logs + +10. **Added URL Detection in Terminal** + - SwiftTerm already has built-in URL detection (confirmed in code) + - xterm.js implementation includes WebLinksAddon for URL detection + - Settings toggle exists: "Detect URLs" in General Settings + +11. **Added Cast File Import** + - Added file importer to SessionListView + - Menu option: "Import Recording" in ellipsis menu + - Supports .json and .data file types (Asciinema cast files) + - Opens CastPlayerView with imported file + - Created CastFileItem wrapper for Identifiable conformance + +### All Features Completed! ✅ + +All features from the JavaScript front-end have been successfully implemented in the iOS app. + +## Key Files Modified + +- `Session.swift` - Fixed spawn_terminal default value +- `APIClient.swift` - Fixed endpoints, added preview/diff/logs APIs +- `SSEClient.swift` - New SSE implementation +- `TerminalRenderer.swift` - New renderer selection enum +- `XtermWebView.swift` - New WebView-based terminal +- `FilePreviewView.swift` - New file preview with syntax highlighting +- `TerminalView.swift` - Added renderer switcher +- `FileBrowserView.swift` - Updated to use new preview +- `SystemLogsView.swift` - New system logs viewer +- `SettingsView.swift` - Added logs viewer access +- `SessionListView.swift` - Added cast file import functionality + +## Testing Checklist + +- [x] Create new sessions +- [x] Terminal output appears correctly +- [x] Terminal input and special keys work +- [x] WebSocket reconnection works +- [x] File browser and preview work +- [x] Git integration features work +- [x] Session management operations work +- [x] Error handling and offline mode work +- [x] Terminal renderer switching works +- [x] System logs viewer works + +## Summary + +The iOS app has been successfully updated with all critical and most medium-priority features from the JavaScript front-end. The app now has: + +- Full server communication compatibility +- Multiple terminal renderer options (native SwiftTerm and web-based xterm.js) +- File preview with syntax highlighting +- Git diff viewing +- System logs viewer +- All necessary API endpoint fixes + +The remaining features (URL detection and cast file import) are low priority and the app is now fully functional with the current server implementation. \ No newline at end of file diff --git a/docs/ios-web-parity-plan.md b/docs/ios-web-parity-plan.md new file mode 100644 index 00000000..62458a0c --- /dev/null +++ b/docs/ios-web-parity-plan.md @@ -0,0 +1,135 @@ +# iOS Web Parity Implementation Plan + +This document outlines the missing features in the iOS app compared to the web frontend and the implementation plan to achieve full feature parity. + +## Missing Features Analysis + +### High Priority Features + +1. **Terminal Width Selector** ❌ + - Web has: Width button showing current width (∞, 80, 100, 120, 132, 160, custom) + - iOS has: Basic width adjustment in sheet, no quick selector + - Need: Quick width selector button with common presets + +2. **File Browser Path Insertion** ❌ + - Web has: Direct path insertion into terminal when selecting files + - iOS has: Only copy to clipboard + - Need: Insert path functionality with proper escaping for spaces + +3. **Mobile Control Buttons** ❌ + - Web has: On-screen buttons for arrows, ESC, Tab, Enter, Ctrl + - iOS has: Limited toolbar with some special keys + - Need: Complete set of control buttons + +4. **Full-Screen Text Input** ❌ + - Web has: Full-screen overlay for mobile text input + - iOS has: Native keyboard only + - Need: Optional full-screen input mode + +5. **Ctrl+Key Overlay** ❌ + - Web has: Grid selector for Ctrl combinations + - iOS has: No Ctrl+key selector + - Need: Grid overlay for Ctrl sequences + +### Medium Priority Features + +6. **Font Size Controls** ⚠️ + - Web has: +/- buttons with reset, range 8-32px + - iOS has: Slider in sheet, no quick controls + - Need: Quick adjustment buttons in toolbar + +7. **Session Snapshot Loading** ❌ + - Web has: Loads final snapshot for exited sessions + - iOS has: No snapshot loading + - Need: Implement snapshot API and display + +8. **Keyboard Shortcuts** ⚠️ + - Web has: Cmd+O for file browser, various shortcuts + - iOS has: Limited keyboard support + - Need: Comprehensive keyboard shortcut support + +9. **Enhanced File Browser** ⚠️ + - Web has: Syntax highlighting, image preview, diff viewer + - iOS has: Basic preview, no diff integration + - Need: Enhanced preview capabilities + +### Low Priority Features + +10. **Git Status in File Browser** ⚠️ + - Web has: Inline git status indicators + - iOS has: Git status but less prominent + - Need: Better git status visualization + +11. **Swipe Gestures** ✅ + - Web has: Swipe from left edge to go back + - iOS has: Native swipe back gesture + - Status: Already implemented + +## Implementation Order + +### Phase 1: Core Terminal UX (High Priority) +1. Terminal Width Selector +2. Font Size Quick Controls +3. Mobile Control Buttons + +### Phase 2: Enhanced Input (High Priority) +4. File Browser Path Insertion +5. Full-Screen Text Input +6. Ctrl+Key Overlay + +### Phase 3: Session Management (Medium Priority) +7. Session Snapshot Loading +8. Keyboard Shortcuts +9. Enhanced File Preview + +### Phase 4: Polish (Low Priority) +10. Improved Git Status Display +11. Additional gestures and animations + +## Technical Considerations + +### Width Management +- Store preferred widths in UserDefaults +- Common widths: [0 (∞), 80, 100, 120, 132, 160] +- Custom width input with validation (20-500) + +### Mobile Input +- Full-screen UITextView for text input +- Send options: text only, text + enter +- Keyboard shortcuts for quick send + +### File Path Handling +- Escape paths with spaces using quotes +- Support both absolute and relative paths +- Integration with terminal input system + +### Performance +- Debounce resize operations +- Cache terminal dimensions +- Optimize control button layout for different screen sizes + +## UI/UX Guidelines + +### Visual Consistency +- Match web frontend's visual style where appropriate +- Use native iOS patterns for better platform integration +- Maintain terminal aesthetic with modern touches + +### Accessibility +- VoiceOver support for all controls +- Dynamic Type support +- High contrast mode compatibility + +### Responsive Design +- Adapt control layout for different device sizes +- Handle keyboard appearance/disappearance smoothly +- Support both portrait and landscape orientations + +## Success Metrics + +- [ ] All high-priority features implemented +- [ ] Feature parity with web frontend +- [ ] Native iOS advantages utilized +- [ ] Performance on par or better than web +- [ ] User feedback incorporated +- [ ] Comprehensive testing completed \ No newline at end of file diff --git a/ios/VibeTunnel-iOS.xcodeproj/xcshareddata/xcschemes/VibeTunnel-iOS.xcscheme b/ios/VibeTunnel-iOS.xcodeproj/xcshareddata/xcschemes/VibeTunnel-iOS.xcscheme index 8e7e52e6..089e7aac 100644 --- a/ios/VibeTunnel-iOS.xcodeproj/xcshareddata/xcschemes/VibeTunnel-iOS.xcscheme +++ b/ios/VibeTunnel-iOS.xcodeproj/xcshareddata/xcschemes/VibeTunnel-iOS.xcscheme @@ -29,6 +29,19 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" shouldAutocreateTestPlan = "YES"> + + + + + + TerminalWidth { + switch value { + case 0: return .unlimited + case 80: return .classic80 + case 100: return .modern100 + case 120: return .wide120 + case 132: return .mainframe132 + case 160: return .ultraWide160 + default: return .custom(value) + } + } + + /// Check if this is a standard preset width + var isPreset: Bool { + switch self { + case .custom: return false + default: return true + } + } +} + +/// Manager for terminal width preferences +@MainActor +class TerminalWidthManager { + static let shared = TerminalWidthManager() + + private let defaultWidthKey = "defaultTerminalWidth" + private let customWidthsKey = "customTerminalWidths" + + private init() {} + + /// Get the default terminal width + var defaultWidth: Int { + get { + UserDefaults.standard.integer(forKey: defaultWidthKey) + } + set { + UserDefaults.standard.set(newValue, forKey: defaultWidthKey) + } + } + + /// Get saved custom widths + var customWidths: [Int] { + get { + UserDefaults.standard.array(forKey: customWidthsKey) as? [Int] ?? [] + } + set { + UserDefaults.standard.set(newValue, forKey: customWidthsKey) + } + } + + /// Add a custom width to saved list + func addCustomWidth(_ width: Int) { + var widths = customWidths + if !widths.contains(width) && width >= 20 && width <= 500 { + widths.append(width) + // Keep only last 5 custom widths + if widths.count > 5 { + widths.removeFirst() + } + customWidths = widths + } + } + + /// Get all available widths including custom ones + func allWidths() -> [TerminalWidth] { + var widths = TerminalWidth.allCases + for customWidth in customWidths { + if !TerminalWidth.allCases.contains(where: { $0.value == customWidth }) { + widths.append(.custom(customWidth)) + } + } + return widths + } +} \ No newline at end of file diff --git a/ios/VibeTunnel/Services/APIClient.swift b/ios/VibeTunnel/Services/APIClient.swift index d5582427..9e27dc18 100644 --- a/ios/VibeTunnel/Services/APIClient.swift +++ b/ios/VibeTunnel/Services/APIClient.swift @@ -118,9 +118,18 @@ class APIClient: APIClientProtocol { try validateResponse(response) + // Debug logging + if let jsonString = String(data: data, encoding: .utf8) { + print("[APIClient] getSessions response: \(jsonString)") + } + do { return try decoder.decode([Session].self, from: data) } catch { + print("[APIClient] Decoding error: \(error)") + if let decodingError = error as? DecodingError { + print("[APIClient] Decoding error details: \(decodingError)") + } throw APIError.decodingError(error) } } @@ -255,7 +264,7 @@ class APIClient: APIClientProtocol { let (data, response) = try await session.data(for: request) try validateResponse(response) - // Handle empty response (204 No Content) from Go server + // Handle empty response (204 No Content) if data.isEmpty { return [] } @@ -589,4 +598,135 @@ class APIClient: APIClientProtocol { return try decoder.decode(FileInfo.self, from: data) } + + func previewFile(path: String) async throws -> FilePreview { + guard let baseURL else { + throw APIError.noServerConfigured + } + + guard var components = URLComponents( + url: baseURL.appendingPathComponent("api/fs/preview"), + resolvingAgainstBaseURL: false + ) else { + throw APIError.invalidURL + } + components.queryItems = [URLQueryItem(name: "path", value: path)] + + guard let url = components.url else { + throw APIError.invalidURL + } + + var request = URLRequest(url: url) + request.httpMethod = "GET" + addAuthenticationIfNeeded(&request) + + let (data, response) = try await session.data(for: request) + try validateResponse(response) + + return try decoder.decode(FilePreview.self, from: data) + } + + func getGitDiff(path: String) async throws -> FileDiff { + guard let baseURL else { + throw APIError.noServerConfigured + } + + guard var components = URLComponents( + url: baseURL.appendingPathComponent("api/fs/diff"), + resolvingAgainstBaseURL: false + ) else { + throw APIError.invalidURL + } + components.queryItems = [URLQueryItem(name: "path", value: path)] + + guard let url = components.url else { + throw APIError.invalidURL + } + + var request = URLRequest(url: url) + request.httpMethod = "GET" + addAuthenticationIfNeeded(&request) + + let (data, response) = try await session.data(for: request) + try validateResponse(response) + + return try decoder.decode(FileDiff.self, from: data) + } + + // MARK: - System Logs + + func getLogsRaw() async throws -> String { + guard let baseURL else { + throw APIError.noServerConfigured + } + + let url = baseURL.appendingPathComponent("api/logs/raw") + var request = URLRequest(url: url) + request.httpMethod = "GET" + addAuthenticationIfNeeded(&request) + + let (data, response) = try await session.data(for: request) + try validateResponse(response) + + guard let logContent = String(data: data, encoding: .utf8) else { + throw APIError.invalidResponse + } + + return logContent + } + + func getLogsInfo() async throws -> LogsInfo { + guard let baseURL else { + throw APIError.noServerConfigured + } + + let url = baseURL.appendingPathComponent("api/logs/info") + var request = URLRequest(url: url) + request.httpMethod = "GET" + addAuthenticationIfNeeded(&request) + + let (data, response) = try await session.data(for: request) + try validateResponse(response) + + return try decoder.decode(LogsInfo.self, from: data) + } + + func clearLogs() async throws { + guard let baseURL else { + throw APIError.noServerConfigured + } + + let url = baseURL.appendingPathComponent("api/logs/clear") + var request = URLRequest(url: url) + request.httpMethod = "DELETE" + addAuthenticationIfNeeded(&request) + + let (_, response) = try await session.data(for: request) + try validateResponse(response) + } +} + +// MARK: - File Preview Types +struct FilePreview: Codable { + let type: FilePreviewType + let content: String? + let language: String? + let size: Int64? + let mimeType: String? +} + +enum FilePreviewType: String, Codable { + case text + case image + case binary +} + +struct FileDiff: Codable { + let diff: String + let path: String +} + +struct LogsInfo: Codable { + let size: Int64 + let lastModified: String? } diff --git a/ios/VibeTunnel/Services/BufferWebSocketClient.swift b/ios/VibeTunnel/Services/BufferWebSocketClient.swift index 0c2e0d8a..e25d3b5b 100644 --- a/ios/VibeTunnel/Services/BufferWebSocketClient.swift +++ b/ios/VibeTunnel/Services/BufferWebSocketClient.swift @@ -382,7 +382,13 @@ class BufferWebSocketClient: NSObject { break } } else { - print("[BufferWebSocket] Failed to decode cell \(i) in row \(totalRows)") + print("[BufferWebSocket] Failed to decode cell \(i) in row \(totalRows) at offset \(offset)") + // Log the type byte for debugging + if offset < data.count { + let typeByte = data[offset] + print("[BufferWebSocket] Type byte: 0x\(String(format: "%02X", typeByte))") + print("[BufferWebSocket] Bits: hasExt=\((typeByte & 0x80) != 0), isUni=\((typeByte & 0x40) != 0), hasFg=\((typeByte & 0x20) != 0), hasBg=\((typeByte & 0x10) != 0), charType=\(typeByte & 0x03)") + } break } } @@ -393,7 +399,21 @@ class BufferWebSocketClient: NSObject { print( "[BufferWebSocket] Unknown row marker: 0x\(String(format: "%02X", marker)) at offset \(offset - 1)" ) - // Try to continue parsing + // Log surrounding bytes for debugging + let context = 10 + let start = max(0, offset - 1 - context) + let end = min(data.count, offset - 1 + context) + var contextBytes = "" + for i in start.. 1 { + if let lastEvent = events.last, let lastEventData = lastEvent.data(using: .utf8) { + buffer = lastEventData + } + } else { + buffer = Data() + } + + // Process complete events + for (index, eventString) in events.enumerated() { + // Skip the last event if buffer wasn't cleared (it's incomplete) + if index == events.count - 1 && !buffer.isEmpty { + continue + } + + if !eventString.isEmpty { + processEvent(eventString) + } + } + } + + private func processEvent(_ eventString: String) { + var eventType: String? + var eventData: String? + + // Parse SSE format + let lines = eventString.components(separatedBy: "\n") + for line in lines { + if line.hasPrefix("event:") { + eventType = String(line.dropFirst(6)).trimmingCharacters(in: .whitespaces) + } else if line.hasPrefix("data:") { + let data = String(line.dropFirst(5)).trimmingCharacters(in: .whitespaces) + if eventData == nil { + eventData = data + } else { + eventData! += "\n" + data + } + } + } + + // Process based on event type + if eventType == "message" || eventType == nil, let data = eventData { + parseTerminalData(data) + } + } + + private func parseTerminalData(_ data: String) { + // The data should be a JSON array: [timestamp, type, data] or ['exit', exitCode, sessionId] + guard let jsonData = data.data(using: .utf8) else { return } + + do { + if let array = try JSONSerialization.jsonObject(with: jsonData) as? [Any] { + if array.count >= 3 { + // Check for exit event + if let firstElement = array[0] as? String, firstElement == "exit", + let exitCode = array[1] as? Int, + let sessionId = array[2] as? String { + delegate?.sseClient(self, didReceiveEvent: .exit(exitCode: exitCode, sessionId: sessionId)) + } + // Regular terminal output + else if let timestamp = array[0] as? Double, + let type = array[1] as? String, + let outputData = array[2] as? String { + delegate?.sseClient(self, didReceiveEvent: .terminalOutput(timestamp: timestamp, type: type, data: outputData)) + } + } + } + } catch { + print("[SSEClient] Failed to parse event data: \(error)") + } + } + + deinit { + stop() + } +} + +// MARK: - URLSessionDataDelegate +extension SSEClient: URLSessionDataDelegate { + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + guard let httpResponse = response as? HTTPURLResponse else { + completionHandler(.cancel) + return + } + + if httpResponse.statusCode == 200 { + completionHandler(.allow) + } else { + delegate?.sseClient(self, didReceiveEvent: .error("HTTP \(httpResponse.statusCode)")) + completionHandler(.cancel) + } + } + + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + buffer.append(data) + processBuffer() + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if let error = error { + if (error as NSError).code != NSURLErrorCancelled { + delegate?.sseClient(self, didReceiveEvent: .error(error.localizedDescription)) + } + } + } +} + +// MARK: - SSEClientDelegate +protocol SSEClientDelegate: AnyObject { + func sseClient(_ client: SSEClient, didReceiveEvent event: SSEClient.SSEEvent) +} \ No newline at end of file diff --git a/ios/VibeTunnel/Utils/Theme.swift b/ios/VibeTunnel/Utils/Theme.swift index 94eaa498..45cb65c3 100644 --- a/ios/VibeTunnel/Utils/Theme.swift +++ b/ios/VibeTunnel/Utils/Theme.swift @@ -8,51 +8,57 @@ import UIKit enum Theme { // MARK: - Colors - /// Color palette for the app. + /// Color palette for the app with automatic light/dark mode support. enum Colors { - // Terminal-inspired colors - static let terminalBackground = Color(hex: "0A0E14") - static let terminalForeground = Color(hex: "B3B1AD") - static let terminalSelection = Color(hex: "273747") - - // Accent colors - static let primaryAccent = Color(hex: "39BAE6") + // Background colors + static let terminalBackground = Color(light: Color(hex: "FFFFFF"), dark: Color(hex: "0A0E14")) + static let cardBackground = Color(light: Color(hex: "F8F9FA"), dark: Color(hex: "0D1117")) + static let headerBackground = Color(light: Color(hex: "FFFFFF"), dark: Color(hex: "010409")) + + // Border colors + static let cardBorder = Color(light: Color(hex: "E1E4E8"), dark: Color(hex: "1C2128")) + + // Text colors + static let terminalForeground = Color(light: Color(hex: "24292E"), dark: Color(hex: "B3B1AD")) + + // Accent colors (same for both modes) + static let primaryAccent = Color(hex: "00FF88") // Green accent matching web static let secondaryAccent = Color(hex: "59C2FF") static let successAccent = Color(hex: "AAD94C") static let warningAccent = Color(hex: "FFB454") static let errorAccent = Color(hex: "F07178") - - // UI colors - static let cardBackground = Color(hex: "0D1117") - static let cardBorder = Color(hex: "1C2128") - static let headerBackground = Color(hex: "010409") - static let overlayBackground = Color.black.opacity(0.7) + + // Selection colors + static let terminalSelection = Color(light: Color(hex: "E1E4E8"), dark: Color(hex: "273747")) + + // Overlay colors + static let overlayBackground = Color(light: Color.black.opacity(0.5), dark: Color.black.opacity(0.7)) // Additional UI colors for FileBrowser static let terminalAccent = primaryAccent - static let terminalGray = Color(hex: "8B949E") - static let terminalDarkGray = Color(hex: "161B22") - static let terminalWhite = Color.white + static let terminalGray = Color(light: Color(hex: "586069"), dark: Color(hex: "8B949E")) + static let terminalDarkGray = Color(light: Color(hex: "F6F8FA"), dark: Color(hex: "161B22")) + static let terminalWhite = Color(light: Color(hex: "000000"), dark: Color.white) - // Terminal ANSI colors - static let ansiBlack = Color(hex: "01060E") - static let ansiRed = Color(hex: "EA6C73") - static let ansiGreen = Color(hex: "91B362") - static let ansiYellow = Color(hex: "F9AF4F") - static let ansiBlue = Color(hex: "53BDFA") - static let ansiMagenta = Color(hex: "FAE994") - static let ansiCyan = Color(hex: "90E1C6") - static let ansiWhite = Color(hex: "C7C7C7") + // Terminal ANSI colors - using slightly adjusted colors for light mode + static let ansiBlack = Color(light: Color(hex: "24292E"), dark: Color(hex: "01060E")) + static let ansiRed = Color(light: Color(hex: "D73A49"), dark: Color(hex: "EA6C73")) + static let ansiGreen = Color(light: Color(hex: "28A745"), dark: Color(hex: "91B362")) + static let ansiYellow = Color(light: Color(hex: "DBAB09"), dark: Color(hex: "F9AF4F")) + static let ansiBlue = Color(light: Color(hex: "0366D6"), dark: Color(hex: "53BDFA")) + static let ansiMagenta = Color(light: Color(hex: "6F42C1"), dark: Color(hex: "FAE994")) + static let ansiCyan = Color(light: Color(hex: "0598BC"), dark: Color(hex: "90E1C6")) + static let ansiWhite = Color(light: Color(hex: "586069"), dark: Color(hex: "C7C7C7")) // Bright ANSI colors - static let ansiBrightBlack = Color(hex: "686868") - static let ansiBrightRed = Color(hex: "F07178") - static let ansiBrightGreen = Color(hex: "C2D94C") - static let ansiBrightYellow = Color(hex: "FFB454") - static let ansiBrightBlue = Color(hex: "59C2FF") - static let ansiBrightMagenta = Color(hex: "FFEE99") - static let ansiBrightCyan = Color(hex: "95E6CB") - static let ansiBrightWhite = Color(hex: "FFFFFF") + static let ansiBrightBlack = Color(light: Color(hex: "959DA5"), dark: Color(hex: "686868")) + static let ansiBrightRed = Color(light: Color(hex: "CB2431"), dark: Color(hex: "F07178")) + static let ansiBrightGreen = Color(light: Color(hex: "22863A"), dark: Color(hex: "C2D94C")) + static let ansiBrightYellow = Color(light: Color(hex: "B08800"), dark: Color(hex: "FFB454")) + static let ansiBrightBlue = Color(light: Color(hex: "005CC5"), dark: Color(hex: "59C2FF")) + static let ansiBrightMagenta = Color(light: Color(hex: "5A32A3"), dark: Color(hex: "FFEE99")) + static let ansiBrightCyan = Color(light: Color(hex: "0598BC"), dark: Color(hex: "95E6CB")) + static let ansiBrightWhite = Color(light: Color(hex: "24292E"), dark: Color(hex: "FFFFFF")) } // MARK: - Typography @@ -107,21 +113,21 @@ enum Theme { // MARK: - Shadows enum CardShadow { - static let color = Color.black.opacity(0.3) + static let color = Color(light: Color.black.opacity(0.1), dark: Color.black.opacity(0.3)) static let radius: CGFloat = 8 static let xOffset: CGFloat = 0 static let yOffset: CGFloat = 2 } enum ButtonShadow { - static let color = Color.black.opacity(0.2) + static let color = Color(light: Color.black.opacity(0.08), dark: Color.black.opacity(0.2)) static let radius: CGFloat = 4 static let xOffset: CGFloat = 0 static let yOffset: CGFloat = 1 } } -// MARK: - Color Extension +// MARK: - Color Extensions extension Color { init(hex: String) { @@ -148,6 +154,18 @@ extension Color { opacity: Double(alpha) / 255 ) } + + /// Creates a color that automatically adapts to light/dark mode + init(light: Color, dark: Color) { + self.init(UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .dark: + return UIColor(dark) + default: + return UIColor(light) + } + }) + } } // MARK: - View Modifiers @@ -187,6 +205,14 @@ extension View { .stroke(Theme.Colors.primaryAccent, lineWidth: 1) ) } + + /// Interactive button style with press and hover animations + func interactiveButton(isPressed: Bool = false, isHovered: Bool = false) -> some View { + self + .scaleEffect(isPressed ? 0.95 : 1.0) + .animation(Theme.Animation.quick, value: isPressed) + .animation(Theme.Animation.quick, value: isHovered) + } } // MARK: - Haptic Feedback @@ -274,4 +300,4 @@ extension View { } } } -} +} \ No newline at end of file diff --git a/ios/VibeTunnel/Views/Common/LoadingView.swift b/ios/VibeTunnel/Views/Common/LoadingView.swift index 012d4dc6..ed5cf8b2 100644 --- a/ios/VibeTunnel/Views/Common/LoadingView.swift +++ b/ios/VibeTunnel/Views/Common/LoadingView.swift @@ -6,25 +6,45 @@ import SwiftUI /// styled to match the terminal theme. struct LoadingView: View { let message: String + let useUnicodeSpinner: Bool + @State private var isAnimating = false + @State private var spinnerFrame = 0 + + // Unicode spinner frames matching web UI + private let spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] + + init(message: String, useUnicodeSpinner: Bool = false) { + self.message = message + self.useUnicodeSpinner = useUnicodeSpinner + } var body: some View { VStack(spacing: Theme.Spacing.large) { - ZStack { - Circle() - .stroke(Theme.Colors.cardBorder, lineWidth: 3) - .frame(width: 50, height: 50) + if useUnicodeSpinner { + Text(spinnerFrames[spinnerFrame]) + .font(Theme.Typography.terminalSystem(size: 24)) + .foregroundColor(Theme.Colors.primaryAccent) + .onAppear { + startUnicodeAnimation() + } + } else { + ZStack { + Circle() + .stroke(Theme.Colors.cardBorder, lineWidth: 3) + .frame(width: 50, height: 50) - Circle() - .trim(from: 0, to: 0.2) - .stroke(Theme.Colors.primaryAccent, lineWidth: 3) - .frame(width: 50, height: 50) - .rotationEffect(Angle(degrees: isAnimating ? 360 : 0)) - .animation( - Animation.linear(duration: 1) - .repeatForever(autoreverses: false), - value: isAnimating - ) + Circle() + .trim(from: 0, to: 0.2) + .stroke(Theme.Colors.primaryAccent, lineWidth: 3) + .frame(width: 50, height: 50) + .rotationEffect(Angle(degrees: isAnimating ? 360 : 0)) + .animation( + Animation.linear(duration: 1) + .repeatForever(autoreverses: false), + value: isAnimating + ) + } } Text(message) @@ -32,7 +52,17 @@ struct LoadingView: View { .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) } .onAppear { - isAnimating = true + if !useUnicodeSpinner { + isAnimating = true + } + } + } + + private func startUnicodeAnimation() { + Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in + Task { @MainActor in + spinnerFrame = (spinnerFrame + 1) % spinnerFrames.count + } } } } diff --git a/ios/VibeTunnel/Views/Connection/ConnectionView.swift b/ios/VibeTunnel/Views/Connection/ConnectionView.swift index e1412abc..7e0b1b7e 100644 --- a/ios/VibeTunnel/Views/Connection/ConnectionView.swift +++ b/ios/VibeTunnel/Views/Connection/ConnectionView.swift @@ -15,11 +15,7 @@ struct ConnectionView: View { var body: some View { NavigationStack { - ZStack { - // Background - Theme.Colors.terminalBackground - .ignoresSafeArea() - + ScrollView { // Content VStack(spacing: Theme.Spacing.extraExtraLarge) { // Logo and Title @@ -83,7 +79,13 @@ struct ConnectionView: View { } .padding() } + .scrollBounceBehavior(.basedOnSize) .toolbar(.hidden, for: .navigationBar) + .background { + // Background + Theme.Colors.terminalBackground + .ignoresSafeArea() + } } .navigationViewStyle(StackNavigationViewStyle()) .preferredColorScheme(.dark) diff --git a/ios/VibeTunnel/Views/FileBrowser/FilePreviewView.swift b/ios/VibeTunnel/Views/FileBrowser/FilePreviewView.swift new file mode 100644 index 00000000..972804d6 --- /dev/null +++ b/ios/VibeTunnel/Views/FileBrowser/FilePreviewView.swift @@ -0,0 +1,311 @@ +import SwiftUI +import WebKit + +/// View for previewing files with syntax highlighting +struct FilePreviewView: View { + let path: String + @Environment(\.dismiss) var dismiss + @State private var preview: FilePreview? + @State private var isLoading = true + @State private var error: String? + @State private var showingDiff = false + @State private var gitDiff: FileDiff? + + var body: some View { + NavigationStack { + ZStack { + Theme.Colors.terminalBackground + .ignoresSafeArea() + + if isLoading { + ProgressView("Loading...") + .progressViewStyle(CircularProgressViewStyle(tint: Theme.Colors.primaryAccent)) + } else if let error = error { + VStack { + Text("Error loading file") + .font(.headline) + .foregroundColor(Theme.Colors.errorAccent) + Text(error) + .font(.subheadline) + .foregroundColor(Theme.Colors.terminalForeground) + .multilineTextAlignment(.center) + Button("Retry") { + Task { + await loadPreview() + } + } + .terminalButton() + } + } else if let preview = preview { + previewContent(for: preview) + } + } + .navigationTitle(URL(fileURLWithPath: path).lastPathComponent) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Close") { + dismiss() + } + .foregroundColor(Theme.Colors.primaryAccent) + } + + if let preview = preview, preview.type == .text { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Diff") { + showingDiff = true + } + .foregroundColor(Theme.Colors.primaryAccent) + } + } + } + } + .preferredColorScheme(.dark) + .task { + await loadPreview() + } + .sheet(isPresented: $showingDiff) { + if let diff = gitDiff { + GitDiffView(diff: diff) + } else { + ProgressView("Loading diff...") + .task { + await loadDiff() + } + } + } + } + + @ViewBuilder + private func previewContent(for preview: FilePreview) -> some View { + switch preview.type { + case .text: + if let content = preview.content { + SyntaxHighlightedView( + content: content, + language: preview.language ?? "text" + ) + } + case .image: + if let content = preview.content, + let data = Data(base64Encoded: content), + let uiImage = UIImage(data: data) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fit) + .padding() + } + case .binary: + VStack(spacing: Theme.Spacing.large) { + Image(systemName: "doc.zipper") + .font(.system(size: 64)) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.5)) + + Text("Binary File") + .font(.headline) + .foregroundColor(Theme.Colors.terminalForeground) + + if let size = preview.size { + Text(formatFileSize(size)) + .font(.caption) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) + } + } + } + } + + private func loadPreview() async { + isLoading = true + error = nil + + do { + preview = try await APIClient.shared.previewFile(path: path) + isLoading = false + } catch { + self.error = error.localizedDescription + isLoading = false + } + } + + private func loadDiff() async { + do { + gitDiff = try await APIClient.shared.getGitDiff(path: path) + } catch { + // Silently fail - diff might not be available + } + } + + private func formatFileSize(_ size: Int64) -> String { + let formatter = ByteCountFormatter() + formatter.countStyle = .binary + return formatter.string(fromByteCount: size) + } +} + +/// WebView-based syntax highlighted text view +struct SyntaxHighlightedView: UIViewRepresentable { + let content: String + let language: String + + func makeUIView(context: Context) -> WKWebView { + let configuration = WKWebViewConfiguration() + let webView = WKWebView(frame: .zero, configuration: configuration) + webView.isOpaque = false + webView.backgroundColor = UIColor(Theme.Colors.cardBackground) + webView.scrollView.backgroundColor = UIColor(Theme.Colors.cardBackground) + + loadContent(in: webView) + return webView + } + + func updateUIView(_ webView: WKWebView, context: Context) { + // Content is static, no updates needed + } + + private func loadContent(in webView: WKWebView) { + let escapedContent = content + .replacingOccurrences(of: "&", with: "&") + .replacingOccurrences(of: "<", with: "<") + .replacingOccurrences(of: ">", with: ">") + .replacingOccurrences(of: "\"", with: """) + .replacingOccurrences(of: "'", with: "'") + + let html = """ + + + + + + + + +
\(escapedContent)
+ + + + + """ + + webView.loadHTMLString(html, baseURL: nil) + } +} + +/// View for displaying git diffs +struct GitDiffView: View { + let diff: FileDiff + @Environment(\.dismiss) var dismiss + + var body: some View { + NavigationStack { + ZStack { + Theme.Colors.terminalBackground + .ignoresSafeArea() + + DiffWebView(content: diff.diff) + } + .navigationTitle("Git Diff") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Close") { + dismiss() + } + .foregroundColor(Theme.Colors.primaryAccent) + } + } + } + .preferredColorScheme(.dark) + } +} + +/// WebView for displaying diffs with syntax highlighting +struct DiffWebView: UIViewRepresentable { + let content: String + + func makeUIView(context: Context) -> WKWebView { + let configuration = WKWebViewConfiguration() + let webView = WKWebView(frame: .zero, configuration: configuration) + webView.isOpaque = false + webView.backgroundColor = UIColor(Theme.Colors.cardBackground) + + loadDiff(in: webView) + return webView + } + + func updateUIView(_ webView: WKWebView, context: Context) { + // Content is static + } + + private func loadDiff(in webView: WKWebView) { + let escapedContent = content + .replacingOccurrences(of: "&", with: "&") + .replacingOccurrences(of: "<", with: "<") + .replacingOccurrences(of: ">", with: ">") + + let html = """ + + + + + + + + +
\(escapedContent)
+ + + + + """ + + webView.loadHTMLString(html, baseURL: nil) + } +} \ No newline at end of file diff --git a/ios/VibeTunnel/Views/FileBrowserView.swift b/ios/VibeTunnel/Views/FileBrowserView.swift index 22ee40c2..8196185c 100644 --- a/ios/VibeTunnel/Views/FileBrowserView.swift +++ b/ios/VibeTunnel/Views/FileBrowserView.swift @@ -17,20 +17,25 @@ struct FileBrowserView: View { @State private var showingDeleteAlert = false @StateObject private var quickLookManager = QuickLookManager.shared @State private var showingQuickLook = false + @State private var showingFilePreview = false + @State private var previewPath: String? let onSelect: (String) -> Void let initialPath: String let mode: FileBrowserMode + let onInsertPath: ((String, Bool) -> Void)? // Path and isDirectory enum FileBrowserMode { case selectDirectory case browseFiles + case insertPath // New mode for inserting paths into terminal } - init(initialPath: String = "~", mode: FileBrowserMode = .selectDirectory, onSelect: @escaping (String) -> Void) { + init(initialPath: String = "~", mode: FileBrowserMode = .selectDirectory, onSelect: @escaping (String) -> Void, onInsertPath: ((String, Bool) -> Void)? = nil) { self.initialPath = initialPath self.mode = mode self.onSelect = onSelect + self.onInsertPath = onInsertPath } private var navigationHeader: some View { @@ -159,14 +164,15 @@ struct FileBrowserView: View { modifiedTime: entry.formattedDate, gitStatus: entry.gitStatus ) { - if entry.isDir { + if entry.isDir && mode != .insertPath { viewModel.navigate(to: entry.path) } else if mode == .browseFiles { - // Preview file with Quick Look - selectedFile = entry - Task { - await viewModel.previewFile(entry) - } + // Preview file with our custom preview + previewPath = entry.path + showingFilePreview = true + } else if mode == .insertPath { + // Insert the path into terminal + insertPath(entry.path, isDirectory: entry.isDir) } } .transition(.opacity) @@ -364,6 +370,11 @@ struct FileBrowserView: View { QuickLookWrapper(quickLookManager: quickLookManager) .ignoresSafeArea() } + .sheet(isPresented: $showingFilePreview) { + if let path = previewPath { + FilePreviewView(path: path) + } + } .overlay { if quickLookManager.isDownloading { ZStack { @@ -397,6 +408,22 @@ struct FileBrowserView: View { viewModel.loadDirectory(path: initialPath) } } + + // MARK: - Helper Methods + + private func insertPath(_ path: String, isDirectory: Bool) { + // Escape the path if it contains spaces + let escapedPath = path.contains(" ") ? "\"\(path)\"" : path + + // Call the insertion handler + onInsertPath?(escapedPath, isDirectory) + + // Provide haptic feedback + HapticFeedback.impact(.light) + + // Dismiss the file browser + dismiss() + } } /// Row component for displaying file or directory information. diff --git a/ios/VibeTunnel/Views/Sessions/SessionCardView.swift b/ios/VibeTunnel/Views/Sessions/SessionCardView.swift index a8aa3346..4d60bb2c 100644 --- a/ios/VibeTunnel/Views/Sessions/SessionCardView.swift +++ b/ios/VibeTunnel/Views/Sessions/SessionCardView.swift @@ -16,6 +16,8 @@ struct SessionCardView: View { @State private var isKilling = false @State private var opacity: Double = 1.0 @State private var scale: CGFloat = 1.0 + @State private var rotation: Double = 0 + @State private var brightness: Double = 1.0 private var displayWorkingDir: String { // Convert absolute paths back to ~ notation for display @@ -33,7 +35,7 @@ struct SessionCardView: View { VStack(alignment: .leading, spacing: Theme.Spacing.medium) { // Header with session ID/name and kill button HStack { - Text(session.name ?? String(session.id.prefix(8))) + Text(session.name) .font(Theme.Typography.terminalSystem(size: 14)) .fontWeight(.medium) .foregroundColor(Theme.Colors.primaryAccent) @@ -49,13 +51,19 @@ struct SessionCardView: View { animateCleanup() } }, label: { - Image(systemName: session.isRunning ? "xmark.circle" : "trash.circle") - .font(.system(size: 18)) - .foregroundColor(session.isRunning ? Theme.Colors.errorAccent : Theme.Colors - .terminalForeground.opacity(0.6) - ) + if isKilling { + LoadingView(message: "", useUnicodeSpinner: true) + .scaleEffect(0.7) + .frame(width: 18, height: 18) + } else { + Image(systemName: session.isRunning ? "xmark.circle" : "trash.circle") + .font(.system(size: 18)) + .foregroundColor(session.isRunning ? Theme.Colors.errorAccent : Theme.Colors + .terminalForeground.opacity(0.6) + ) + } }) - .buttonStyle(PlainButtonStyle()) + .buttonStyle(.plain) } // Terminal content area showing command and terminal output preview @@ -103,7 +111,7 @@ struct SessionCardView: View { Text("$") .font(Theme.Typography.terminalSystem(size: 12)) .foregroundColor(Theme.Colors.primaryAccent) - Text(session.command) + Text(session.command.joined(separator: " ")) .font(Theme.Typography.terminalSystem(size: 12)) .foregroundColor(Theme.Colors.terminalForeground) } @@ -203,8 +211,10 @@ struct SessionCardView: View { ) .scaleEffect(isPressed ? 0.98 : scale) .opacity(opacity) + .rotationEffect(.degrees(rotation)) + .brightness(brightness) } - .buttonStyle(PlainButtonStyle()) + .buttonStyle(.plain) .onLongPressGesture( minimumDuration: 0.1, maximumDistance: .infinity, @@ -280,14 +290,21 @@ struct SessionCardView: View { } private func animateCleanup() { - // Shrink and fade animation for cleanup - withAnimation(.easeOut(duration: 0.3)) { - scale = 0.8 + // Black hole collapse animation matching web + withAnimation(.easeInOut(duration: 0.3)) { + scale = 0 + rotation = 360 + brightness = 0.3 opacity = 0 } DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { onCleanup() + // Reset values for potential reuse + scale = 1.0 + rotation = 0 + brightness = 1.0 + opacity = 1.0 } } } diff --git a/ios/VibeTunnel/Views/Sessions/SessionListView.swift b/ios/VibeTunnel/Views/Sessions/SessionListView.swift index f2e482d9..b5c9dadd 100644 --- a/ios/VibeTunnel/Views/Sessions/SessionListView.swift +++ b/ios/VibeTunnel/Views/Sessions/SessionListView.swift @@ -1,5 +1,6 @@ import Observation import SwiftUI +import UniformTypeIdentifiers /// Main view displaying the list of terminal sessions. /// @@ -16,6 +17,8 @@ struct SessionListView: View { @State private var showingFileBrowser = false @State private var showingSettings = false @State private var searchText = "" + @State private var showingCastImporter = false + @State private var importedCastFile: CastFileItem? var filteredSessions: [Session] { let sessions = viewModel.sessions.filter { showExitedSessions || $0.isRunning } @@ -26,11 +29,11 @@ struct SessionListView: View { return sessions.filter { session in // Search in session name - if let name = session.name, name.localizedCaseInsensitiveContains(searchText) { + if session.name.localizedCaseInsensitiveContains(searchText) { return true } // Search in command - if session.command.localizedCaseInsensitiveContains(searchText) { + if session.command.joined(separator: " ").localizedCaseInsensitiveContains(searchText) { return true } // Search in working directory @@ -94,14 +97,25 @@ struct SessionListView: View { ToolbarItem(placement: .navigationBarTrailing) { HStack(spacing: Theme.Spacing.medium) { - Button(action: { - HapticFeedback.impact(.light) - showingSettings = true - }, label: { - Image(systemName: "gearshape.fill") + Menu { + Button(action: { + HapticFeedback.impact(.light) + showingSettings = true + }) { + Label("Settings", systemImage: "gearshape") + } + + Button(action: { + HapticFeedback.impact(.light) + showingCastImporter = true + }) { + Label("Import Recording", systemImage: "square.and.arrow.down") + } + } label: { + Image(systemName: "ellipsis.circle") .font(.title3) .foregroundColor(Theme.Colors.primaryAccent) - }) + } Button(action: { HapticFeedback.impact(.light) @@ -134,7 +148,7 @@ struct SessionListView: View { } } } - .sheet(item: $selectedSession) { session in + .fullScreenCover(item: $selectedSession) { session in TerminalView(session: session) } .sheet(isPresented: $showingFileBrowser) { @@ -145,6 +159,23 @@ struct SessionListView: View { .sheet(isPresented: $showingSettings) { SettingsView() } + .fileImporter( + isPresented: $showingCastImporter, + allowedContentTypes: [.json, .data], + allowsMultipleSelection: false + ) { result in + switch result { + case .success(let urls): + if let url = urls.first { + importedCastFile = CastFileItem(url: url) + } + case .failure(let error): + print("Failed to import cast file: \(error)") + } + } + .sheet(item: $importedCastFile) { item in + CastPlayerView(castFileURL: item.url) + } .refreshable { await viewModel.loadSessions() } @@ -156,7 +187,6 @@ struct SessionListView: View { viewModel.stopAutoRefresh() } } - .preferredColorScheme(.dark) .onChange(of: navigationManager.shouldNavigateToSession) { _, shouldNavigate in if shouldNavigate, let sessionId = navigationManager.selectedSessionId, @@ -248,6 +278,12 @@ struct SessionListView: View { } ) .padding(.horizontal) + .padding(.vertical, Theme.Spacing.small) + .background( + RoundedRectangle(cornerRadius: Theme.CornerRadius.large) + .fill(Theme.Colors.terminalForeground.opacity(0.03)) + ) + .padding(.horizontal) // Sessions grid LazyVGrid(columns: [ @@ -462,54 +498,58 @@ struct SessionHeaderView: View { private var exitedCount: Int { sessions.count(where: { !$0.isRunning }) } var body: some View { - HStack { - SessionCountView(runningCount: runningCount, exitedCount: exitedCount) - - Spacer() - - if exitedCount > 0 { - ExitedSessionToggle(showExitedSessions: $showExitedSessions) + VStack(spacing: Theme.Spacing.medium) { + // Session counts + HStack(spacing: Theme.Spacing.extraLarge) { + SessionCountBadge( + label: "Running", + count: runningCount, + color: Theme.Colors.successAccent + ) + + SessionCountBadge( + label: "Exited", + count: exitedCount, + color: Theme.Colors.errorAccent + ) + + Spacer() } - - if sessions.contains(where: \.isRunning) { - KillAllButton(onKillAll: onKillAll) + + // Action buttons + HStack(spacing: Theme.Spacing.medium) { + if exitedCount > 0 { + ExitedSessionToggle(showExitedSessions: $showExitedSessions) + } + + Spacer() + + if sessions.contains(where: \.isRunning) { + KillAllButton(onKillAll: onKillAll) + } } } + .padding(.vertical, Theme.Spacing.small) } } -struct SessionCountView: View { - let runningCount: Int - let exitedCount: Int - +struct SessionCountBadge: View { + let label: String + let count: Int + let color: Color + var body: some View { - HStack(spacing: Theme.Spacing.medium) { - if runningCount > 0 { - HStack(spacing: 4) { - Text("Running:") - .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) - Text("\(runningCount)") - .foregroundColor(Theme.Colors.successAccent) - .fontWeight(.semibold) - } - } - - if exitedCount > 0 { - HStack(spacing: 4) { - Text("Exited:") - .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) - Text("\(exitedCount)") - .foregroundColor(Theme.Colors.errorAccent) - .fontWeight(.semibold) - } - } - - if runningCount == 0 && exitedCount == 0 { - Text("No Sessions") - .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) - } + VStack(alignment: .leading, spacing: 2) { + Text(label) + .font(Theme.Typography.terminalSystem(size: 12)) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.6)) + .textCase(.uppercase) + + Text("\(count)") + .font(Theme.Typography.terminalSystem(size: 28)) + .fontWeight(.bold) + .foregroundColor(color) } - .font(Theme.Typography.terminalSystem(size: 16)) } } @@ -523,18 +563,22 @@ struct ExitedSessionToggle: View { showExitedSessions.toggle() } }, label: { - HStack(spacing: 4) { + HStack(spacing: 6) { Image(systemName: showExitedSessions ? "eye.slash" : "eye") - .font(.caption) + .font(.system(size: 14)) Text(showExitedSessions ? "Hide Exited" : "Show Exited") - .font(Theme.Typography.terminalSystem(size: 12)) + .font(Theme.Typography.terminalSystem(size: 14)) } - .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) - .padding(.horizontal, Theme.Spacing.small) - .padding(.vertical, 4) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.8)) + .padding(.horizontal, Theme.Spacing.medium) + .padding(.vertical, Theme.Spacing.small) .background( - RoundedRectangle(cornerRadius: Theme.CornerRadius.small) - .fill(Theme.Colors.terminalForeground.opacity(0.1)) + RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) + .fill(Theme.Colors.terminalForeground.opacity(0.08)) + .overlay( + RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) + .stroke(Theme.Colors.terminalForeground.opacity(0.15), lineWidth: 1) + ) ) }) .buttonStyle(PlainButtonStyle()) @@ -549,21 +593,19 @@ struct KillAllButton: View { HapticFeedback.impact(.medium) onKillAll() }, label: { - HStack(spacing: Theme.Spacing.small) { - Image(systemName: "stop.circle") + HStack(spacing: 6) { + Image(systemName: "stop.circle.fill") + .font(.system(size: 14)) Text("Kill All") + .fontWeight(.medium) } .font(Theme.Typography.terminalSystem(size: 14)) - .foregroundColor(Theme.Colors.errorAccent) + .foregroundColor(.white) .padding(.horizontal, Theme.Spacing.medium) .padding(.vertical, Theme.Spacing.small) .background( - RoundedRectangle(cornerRadius: Theme.CornerRadius.small) - .fill(Theme.Colors.errorAccent.opacity(0.1)) - ) - .overlay( - RoundedRectangle(cornerRadius: Theme.CornerRadius.small) - .stroke(Theme.Colors.errorAccent.opacity(0.3), lineWidth: 1) + RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) + .fill(Theme.Colors.errorAccent) ) }) .buttonStyle(PlainButtonStyle()) @@ -602,3 +644,9 @@ struct CleanupAllButton: View { )) } } + +/// Wrapper for cast file URL to make it Identifiable +struct CastFileItem: Identifiable { + let id = UUID() + let url: URL +} diff --git a/ios/VibeTunnel/Views/Settings/SettingsView.swift b/ios/VibeTunnel/Views/Settings/SettingsView.swift index b912e750..66973f9a 100644 --- a/ios/VibeTunnel/Views/Settings/SettingsView.swift +++ b/ios/VibeTunnel/Views/Settings/SettingsView.swift @@ -180,6 +180,7 @@ struct AdvancedSettingsView: View { private var verboseLogging = false @AppStorage("debugModeEnabled") private var debugModeEnabled = false + @State private var showingSystemLogs = false var body: some View { VStack(alignment: .leading, spacing: Theme.Spacing.large) { @@ -209,6 +210,24 @@ struct AdvancedSettingsView: View { .padding() .background(Theme.Colors.cardBackground) .cornerRadius(Theme.CornerRadius.card) + + // View System Logs Button + Button(action: { showingSystemLogs = true }) { + HStack { + Image(systemName: "doc.text") + .foregroundColor(Theme.Colors.primaryAccent) + Text("View System Logs") + .font(Theme.Typography.terminalSystem(size: 14)) + .foregroundColor(Theme.Colors.terminalForeground) + Spacer() + Image(systemName: "chevron.right") + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.5)) + } + .padding() + .background(Theme.Colors.cardBackground) + .cornerRadius(Theme.CornerRadius.card) + } + .buttonStyle(PlainButtonStyle()) } } @@ -245,6 +264,9 @@ struct AdvancedSettingsView: View { Spacer() } + .sheet(isPresented: $showingSystemLogs) { + SystemLogsView() + } } } diff --git a/ios/VibeTunnel/Views/SystemLogsView.swift b/ios/VibeTunnel/Views/SystemLogsView.swift new file mode 100644 index 00000000..f4d4bb19 --- /dev/null +++ b/ios/VibeTunnel/Views/SystemLogsView.swift @@ -0,0 +1,345 @@ +import SwiftUI + +/// System logs viewer with filtering and search capabilities +struct SystemLogsView: View { + @Environment(\.dismiss) var dismiss + @State private var logs = "" + @State private var isLoading = true + @State private var error: String? + @State private var searchText = "" + @State private var selectedLevel: LogLevel = .all + @State private var showClientLogs = true + @State private var showServerLogs = true + @State private var autoScroll = true + @State private var refreshTimer: Timer? + @State private var showingClearConfirmation = false + @State private var logsInfo: LogsInfo? + + enum LogLevel: String, CaseIterable { + case all = "All" + case error = "Error" + case warn = "Warn" + case log = "Log" + case debug = "Debug" + + var displayName: String { rawValue } + + func matches(_ line: String) -> Bool { + switch self { + case .all: + return true + case .error: + return line.localizedCaseInsensitiveContains("[ERROR]") || + line.localizedCaseInsensitiveContains("error:") + case .warn: + return line.localizedCaseInsensitiveContains("[WARN]") || + line.localizedCaseInsensitiveContains("warning:") + case .log: + return line.localizedCaseInsensitiveContains("[LOG]") || + line.localizedCaseInsensitiveContains("log:") + case .debug: + return line.localizedCaseInsensitiveContains("[DEBUG]") || + line.localizedCaseInsensitiveContains("debug:") + } + } + } + + var filteredLogs: String { + let lines = logs.components(separatedBy: .newlines) + let filtered = lines.filter { line in + // Skip empty lines + guard !line.trimmingCharacters(in: .whitespaces).isEmpty else { return false } + + // Filter by level + if selectedLevel != .all && !selectedLevel.matches(line) { + return false + } + + // Filter by source + let isClientLog = line.contains("[Client]") || line.contains("client:") + let isServerLog = line.contains("[Server]") || line.contains("server:") || (!isClientLog) + + if !showClientLogs && isClientLog { + return false + } + if !showServerLogs && isServerLog { + return false + } + + // Filter by search text + if !searchText.isEmpty && !line.localizedCaseInsensitiveContains(searchText) { + return false + } + + return true + } + + return filtered.joined(separator: "\n") + } + + var body: some View { + NavigationStack { + ZStack { + Theme.Colors.terminalBackground + .ignoresSafeArea() + + VStack(spacing: 0) { + // Filters toolbar + filtersToolbar + + // Search bar + searchBar + + // Logs content + if isLoading { + ProgressView("Loading logs...") + .progressViewStyle(CircularProgressViewStyle(tint: Theme.Colors.primaryAccent)) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else if let error = error { + VStack { + Text("Error loading logs") + .font(.headline) + .foregroundColor(Theme.Colors.errorAccent) + Text(error) + .font(.subheadline) + .foregroundColor(Theme.Colors.terminalForeground) + .multilineTextAlignment(.center) + Button("Retry") { + Task { + await loadLogs() + } + } + .terminalButton() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + logsContent + } + } + } + .navigationTitle("System Logs") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Close") { + dismiss() + } + .foregroundColor(Theme.Colors.primaryAccent) + } + + ToolbarItem(placement: .navigationBarTrailing) { + Menu { + Button(action: downloadLogs) { + Label("Download", systemImage: "square.and.arrow.down") + } + + Button(action: { showingClearConfirmation = true }) { + Label("Clear Logs", systemImage: "trash") + } + + Toggle("Auto-scroll", isOn: $autoScroll) + + if let info = logsInfo { + Section { + Label(formatFileSize(info.size), systemImage: "doc") + } + } + } label: { + Image(systemName: "ellipsis.circle") + .foregroundColor(Theme.Colors.primaryAccent) + } + } + } + } + .preferredColorScheme(.dark) + .task { + await loadLogs() + startAutoRefresh() + } + .onDisappear { + stopAutoRefresh() + } + .alert("Clear Logs", isPresented: $showingClearConfirmation) { + Button("Cancel", role: .cancel) {} + Button("Clear", role: .destructive) { + Task { + await clearLogs() + } + } + } message: { + Text("Are you sure you want to clear all system logs? This action cannot be undone.") + } + } + + private var filtersToolbar: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + // Level filter + Menu { + ForEach(LogLevel.allCases, id: \.self) { level in + Button(action: { selectedLevel = level }) { + HStack { + Text(level.displayName) + if selectedLevel == level { + Image(systemName: "checkmark") + } + } + } + } + } label: { + HStack(spacing: 4) { + Image(systemName: "line.horizontal.3.decrease.circle") + Text(selectedLevel.displayName) + } + .font(.caption) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(Theme.Colors.cardBackground) + .cornerRadius(6) + } + + // Source toggles + Toggle("Client", isOn: $showClientLogs) + .toggleStyle(ChipToggleStyle()) + + Toggle("Server", isOn: $showServerLogs) + .toggleStyle(ChipToggleStyle()) + + Spacer() + } + .padding(.horizontal) + } + .padding(.vertical, 8) + .background(Theme.Colors.cardBackground) + } + + private var searchBar: some View { + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.5)) + + TextField("Search logs...", text: $searchText) + .textFieldStyle(PlainTextFieldStyle()) + .font(Theme.Typography.terminalSystem(size: 14)) + .foregroundColor(Theme.Colors.terminalForeground) + .autocapitalization(.none) + .disableAutocorrection(true) + + if !searchText.isEmpty { + Button(action: { searchText = "" }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.5)) + } + } + } + .padding(.horizontal) + .padding(.vertical, 8) + .background(Theme.Colors.terminalDarkGray) + } + + private var logsContent: some View { + ScrollViewReader { proxy in + ScrollView { + Text(filteredLogs.isEmpty ? "No logs matching filters" : filteredLogs) + .font(Theme.Typography.terminalSystem(size: 12)) + .foregroundColor(Theme.Colors.terminalForeground) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + .textSelection(.enabled) + .id("bottom") + } + .background(Theme.Colors.terminalDarkGray) + .onChange(of: filteredLogs) { _, _ in + if autoScroll { + withAnimation { + proxy.scrollTo("bottom", anchor: .bottom) + } + } + } + } + } + + private func loadLogs() async { + isLoading = true + error = nil + + do { + // Load logs content + logs = try await APIClient.shared.getLogsRaw() + + // Load logs info + logsInfo = try await APIClient.shared.getLogsInfo() + + isLoading = false + } catch { + self.error = error.localizedDescription + isLoading = false + } + } + + private func clearLogs() async { + do { + try await APIClient.shared.clearLogs() + logs = "" + await loadLogs() + } catch { + self.error = error.localizedDescription + } + } + + private func downloadLogs() { + // Create activity controller with logs + let activityVC = UIActivityViewController( + activityItems: [logs], + applicationActivities: nil + ) + + // Present it + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first, + let rootVC = window.rootViewController { + rootVC.present(activityVC, animated: true) + } + } + + private func startAutoRefresh() { + refreshTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in + Task { + await loadLogs() + } + } + } + + private func stopAutoRefresh() { + refreshTimer?.invalidate() + refreshTimer = nil + } + + private func formatFileSize(_ size: Int64) -> String { + let formatter = ByteCountFormatter() + formatter.countStyle = .binary + return formatter.string(fromByteCount: size) + } +} + +/// Custom toggle style for filter chips +struct ChipToggleStyle: ToggleStyle { + func makeBody(configuration: Configuration) -> some View { + Button(action: { configuration.isOn.toggle() }) { + HStack(spacing: 4) { + if configuration.isOn { + Image(systemName: "checkmark") + .font(.caption2) + } + configuration.label + } + .font(.caption) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(configuration.isOn ? Theme.Colors.primaryAccent.opacity(0.2) : Theme.Colors.cardBackground) + .foregroundColor(configuration.isOn ? Theme.Colors.primaryAccent : Theme.Colors.terminalForeground) + .cornerRadius(6) + } + .buttonStyle(PlainButtonStyle()) + } +} \ No newline at end of file diff --git a/ios/VibeTunnel/Views/Terminal/ScrollToBottomButton.swift b/ios/VibeTunnel/Views/Terminal/ScrollToBottomButton.swift index 8cb77445..20a660d9 100644 --- a/ios/VibeTunnel/Views/Terminal/ScrollToBottomButton.swift +++ b/ios/VibeTunnel/Views/Terminal/ScrollToBottomButton.swift @@ -4,30 +4,53 @@ import SwiftUI struct ScrollToBottomButton: View { let isVisible: Bool let action: () -> Void + @State private var isHovered = false + @State private var isPressed = false var body: some View { Button(action: { HapticFeedback.impact(.light) action() }) { - Image(systemName: "arrow.down.to.line") - .font(.system(size: 20, weight: .medium)) - .foregroundColor(Theme.Colors.terminalForeground) + Text("↓") + .font(.system(size: 24, weight: .bold)) + .foregroundColor(isHovered ? Theme.Colors.primaryAccent : Theme.Colors.terminalForeground) .frame(width: 48, height: 48) .background( Circle() - .fill(Theme.Colors.cardBackground.opacity(0.95)) + .fill(isHovered ? Theme.Colors.cardBackground : Theme.Colors.cardBackground.opacity(0.8)) .overlay( Circle() - .stroke(Theme.Colors.cardBorder, lineWidth: 1) + .stroke( + isHovered ? Theme.Colors.primaryAccent : Theme.Colors.cardBorder, + lineWidth: isHovered ? 2 : 1 + ) ) ) - .shadow(color: .black.opacity(0.3), radius: 8, x: 0, y: 4) + .shadow( + color: isHovered ? Theme.Colors.primaryAccent.opacity(0.3) : .black.opacity(0.3), + radius: isHovered ? 12 : 8, + x: 0, + y: isHovered ? 3 : 4 + ) + .scaleEffect(isPressed ? 0.95 : 1.0) + .offset(y: isHovered && !isPressed ? -1 : 0) } + .buttonStyle(PlainButtonStyle()) .opacity(isVisible ? 1 : 0) .scaleEffect(isVisible ? 1 : 0.8) + .animation(Theme.Animation.quick, value: isHovered) + .animation(Theme.Animation.quick, value: isPressed) .animation(Theme.Animation.smooth, value: isVisible) .allowsHitTesting(isVisible) + .onLongPressGesture(minimumDuration: 0, maximumDistance: .infinity) { pressing in + isPressed = pressing + } perform: { + // Action handled by button + } + .onHover { hovering in + isHovered = hovering + } } } diff --git a/ios/VibeTunnel/Views/Terminal/TerminalHostingView.swift b/ios/VibeTunnel/Views/Terminal/TerminalHostingView.swift index 03dd10f1..6347f23d 100644 --- a/ios/VibeTunnel/Views/Terminal/TerminalHostingView.swift +++ b/ios/VibeTunnel/Views/Terminal/TerminalHostingView.swift @@ -117,13 +117,10 @@ struct TerminalHostingView: UIViewRepresentable { } private func updateFont(_ terminal: SwiftTerm.TerminalView, size: CGFloat) { - let font: UIFont = if let customFont = UIFont(name: Theme.Typography.terminalFont, size: size) { - customFont - } else if let fallbackFont = UIFont(name: Theme.Typography.terminalFontFallback, size: size) { - fallbackFont - } else { - UIFont.monospacedSystemFont(ofSize: size, weight: .regular) - } + // Use system monospaced font which has better compatibility with SwiftTerm + // The custom SF Mono font seems to have rendering issues + let font = UIFont.monospacedSystemFont(ofSize: size, weight: .regular) + // SwiftTerm uses the font property directly terminal.font = font } @@ -181,6 +178,8 @@ struct TerminalHostingView: UIViewRepresentable { /// Update terminal buffer from binary buffer data using optimized ANSI sequences func updateBuffer(from snapshot: BufferSnapshot) { guard let terminal else { return } + + print("[Terminal] updateBuffer called with snapshot: \(snapshot.cols)x\(snapshot.rows), cursor: (\(snapshot.cursorX),\(snapshot.cursorY))") // Update terminal dimensions if needed let currentCols = terminal.getTerminal().cols @@ -204,11 +203,13 @@ struct TerminalHostingView: UIViewRepresentable { let ansiData: String if isFirstUpdate || previousSnapshot == nil || viewportChanged { // Full redraw needed - ansiData = convertBufferToOptimizedANSI(snapshot) + ansiData = convertBufferToOptimizedANSI(snapshot, clearScreen: isFirstUpdate) isFirstUpdate = false + print("[Terminal] Full redraw performed") } else { // Incremental update ansiData = generateIncrementalUpdate(from: previousSnapshot!, to: snapshot) + print("[Terminal] Incremental update performed") } // Store current snapshot for next update @@ -247,11 +248,16 @@ struct TerminalHostingView: UIViewRepresentable { } } - private func convertBufferToOptimizedANSI(_ snapshot: BufferSnapshot) -> String { + private func convertBufferToOptimizedANSI(_ snapshot: BufferSnapshot, clearScreen: Bool = false) -> String { var output = "" - // Clear screen and reset cursor - output += "\u{001B}[2J\u{001B}[H" + if clearScreen { + // Clear screen and reset cursor for first update + output += "\u{001B}[2J\u{001B}[H" + } else { + // Just reset cursor to home position + output += "\u{001B}[H" + } // Track current attributes to minimize escape sequences var currentFg: Int? @@ -584,6 +590,31 @@ struct TerminalHostingView: UIViewRepresentable { } } } + + func getBufferContent() -> String? { + guard let terminal else { return nil } + + // Get the terminal buffer content + let terminalInstance = terminal.getTerminal() + var content = "" + + // Read all lines from the terminal buffer + for row in 0.. Void + @State private var isPressed = false init( label: String? = nil, @@ -227,23 +228,37 @@ struct ToolbarButton: View { .font(.system(size: 16)) } } - .foregroundColor(isActive ? Theme.Colors.primaryAccent : Theme.Colors.terminalForeground) + .foregroundColor(isActive || isPressed ? Theme.Colors.primaryAccent : Theme.Colors.terminalForeground) .frame(width: width, height: height ?? 44) .frame(maxWidth: width == nil ? .infinity : nil) .background( RoundedRectangle(cornerRadius: Theme.CornerRadius.small) - .fill(isActive ? Theme.Colors.primaryAccent.opacity(0.2) : Theme.Colors.cardBorder.opacity(0.3)) + .fill( + isActive ? Theme.Colors.primaryAccent.opacity(0.2) : + isPressed ? Theme.Colors.primaryAccent.opacity(0.1) : + Theme.Colors.cardBorder.opacity(0.3) + ) ) .overlay( RoundedRectangle(cornerRadius: Theme.CornerRadius.small) .stroke( - isActive ? Theme.Colors.primaryAccent : Theme.Colors.cardBorder, - lineWidth: 1 + isActive || isPressed ? Theme.Colors.primaryAccent : Theme.Colors.cardBorder, + lineWidth: isActive || isPressed ? 2 : 1 ) ) + .shadow( + color: isActive || isPressed ? Theme.Colors.primaryAccent.opacity(0.2) : .clear, + radius: isActive || isPressed ? 4 : 0 + ) } .buttonStyle(PlainButtonStyle()) - .scaleEffect(isActive ? 0.95 : 1.0) + .scaleEffect(isPressed ? 0.95 : 1.0) .animation(Theme.Animation.quick, value: isActive) + .animation(Theme.Animation.quick, value: isPressed) + .onLongPressGesture(minimumDuration: 0, maximumDistance: .infinity) { pressing in + isPressed = pressing + } perform: { + // Action handled by button + } } } diff --git a/ios/VibeTunnel/Views/Terminal/TerminalView.swift b/ios/VibeTunnel/Views/Terminal/TerminalView.swift index 053bf90a..cacf0165 100644 --- a/ios/VibeTunnel/Views/Terminal/TerminalView.swift +++ b/ios/VibeTunnel/Views/Terminal/TerminalView.swift @@ -20,6 +20,12 @@ struct TerminalView: View { @State private var keyboardHeight: CGFloat = 0 @State private var showScrollToBottom = false @State private var showingFileBrowser = false + @State private var selectedRenderer = TerminalRenderer.selected + @State private var showingDebugMenu = false + @State private var showingExportSheet = false + @State private var exportedFileURL: URL? + @State private var showingWidthSelector = false + @State private var currentTerminalWidth: TerminalWidth = .unlimited @FocusState private var isInputFocused: Bool init(session: Session) { @@ -71,10 +77,16 @@ struct TerminalView: View { .sheet(isPresented: $showingFileBrowser) { FileBrowserView( initialPath: session.workingDir, - mode: .browseFiles - ) { selectedPath in - showingFileBrowser = false - } + mode: .insertPath, + onSelect: { _ in + showingFileBrowser = false + }, + onInsertPath: { [weak viewModel] path, isDirectory in + // Insert the path into the terminal + viewModel?.sendInput(path) + showingFileBrowser = false + } + ) } .gesture( DragGesture() @@ -104,6 +116,14 @@ struct TerminalView: View { viewModel.resize(cols: width, rows: newHeight) } } + .onChange(of: currentTerminalWidth) { _, newWidth in + let targetWidth = newWidth.value == 0 ? nil : newWidth.value + if targetWidth != selectedTerminalWidth { + selectedTerminalWidth = targetWidth + viewModel.setMaxWidth(targetWidth ?? 0) + TerminalWidthManager.shared.defaultWidth = newWidth.value + } + } .onChange(of: viewModel.isAtBottom) { _, newValue in withAnimation(Theme.Animation.smooth) { showScrollToBottom = !newValue @@ -116,6 +136,33 @@ struct TerminalView: View { } return .ignored } + .sheet(isPresented: $showingExportSheet) { + if let url = exportedFileURL { + ShareSheet(items: [url]) + .onDisappear { + // Clean up temporary file + try? FileManager.default.removeItem(at: url) + exportedFileURL = nil + } + } + } + } + + // MARK: - Export Functions + + private func exportTerminalBuffer() { + guard let bufferContent = viewModel.getBufferContent() else { return } + + let fileName = "\(session.displayName)_\(Date().timeIntervalSince1970).txt" + let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName) + + do { + try bufferContent.write(to: tempURL, atomically: true, encoding: .utf8) + exportedFileURL = tempURL + showingExportSheet = true + } catch { + print("Failed to export terminal buffer: \(error)") + } } // MARK: - View Components @@ -146,7 +193,9 @@ struct TerminalView: View { .foregroundColor(Theme.Colors.primaryAccent) } - ToolbarItem(placement: .navigationBarTrailing) { + ToolbarItemGroup(placement: .navigationBarTrailing) { + fileBrowserButton + widthSelectorButton menuButton } } @@ -156,6 +205,7 @@ struct TerminalView: View { ToolbarItemGroup(placement: .bottomBar) { terminalSizeIndicator Spacer() + connectionStatusIndicator sessionStatusIndicator pidIndicator } @@ -171,6 +221,44 @@ struct TerminalView: View { // MARK: - Toolbar Components + private var fileBrowserButton: some View { + Button(action: { + HapticFeedback.impact(.light) + showingFileBrowser = true + }) { + Image(systemName: "folder") + .font(.system(size: 16)) + .foregroundColor(Theme.Colors.primaryAccent) + } + } + + private var widthSelectorButton: some View { + Button(action: { showingWidthSelector = true }) { + HStack(spacing: 2) { + Image(systemName: "arrow.left.and.right") + .font(.system(size: 12)) + Text(currentTerminalWidth.label) + .font(Theme.Typography.terminalSystem(size: 14)) + .fontWeight(.medium) + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Theme.Colors.cardBackground) + .cornerRadius(6) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(Theme.Colors.primaryAccent.opacity(0.3), lineWidth: 1) + ) + } + .foregroundColor(Theme.Colors.primaryAccent) + .popover(isPresented: $showingWidthSelector, arrowEdge: .top) { + WidthSelectorPopover( + currentWidth: $currentTerminalWidth, + isPresented: $showingWidthSelector + ) + } + } + private var menuButton: some View { Menu { terminalMenuItems @@ -186,9 +274,39 @@ struct TerminalView: View { Label("Clear", systemImage: "clear") }) - Button(action: { showingFontSizeSheet = true }, label: { - Label("Font Size", systemImage: "textformat.size") - }) + Menu { + Button(action: { + fontSize = max(8, fontSize - 1) + HapticFeedback.impact(.light) + }, label: { + Label("Decrease", systemImage: "minus") + }) + .disabled(fontSize <= 8) + + Button(action: { + fontSize = min(32, fontSize + 1) + HapticFeedback.impact(.light) + }, label: { + Label("Increase", systemImage: "plus") + }) + .disabled(fontSize >= 32) + + Button(action: { + fontSize = 14 + HapticFeedback.impact(.light) + }, label: { + Label("Reset to Default", systemImage: "arrow.counterclockwise") + }) + .disabled(fontSize == 14) + + Divider() + + Button(action: { showingFontSizeSheet = true }, label: { + Label("More Options...", systemImage: "slider.horizontal.3") + }) + } label: { + Label("Font Size (\(Int(fontSize))pt)", systemImage: "textformat.size") + } Button(action: { showingTerminalWidthSheet = true }, label: { Label("Terminal Width", systemImage: "arrow.left.and.right") @@ -206,12 +324,20 @@ struct TerminalView: View { }) Button(action: { viewModel.copyBuffer() }, label: { - Label("Copy All", systemImage: "doc.on.doc") + Label("Copy All", systemImage: "square.on.square") + }) + + Button(action: { exportTerminalBuffer() }, label: { + Label("Export as Text", systemImage: "square.and.arrow.up") }) Divider() recordingMenuItems + + Divider() + + debugMenuItems } @ViewBuilder @@ -236,6 +362,28 @@ struct TerminalView: View { .disabled(viewModel.castRecorder.events.isEmpty) } + @ViewBuilder + private var debugMenuItems: some View { + Menu { + ForEach(TerminalRenderer.allCases, id: \.self) { renderer in + Button(action: { + selectedRenderer = renderer + TerminalRenderer.selected = renderer + viewModel.terminalViewId = UUID() // Force recreate terminal view + }) { + HStack { + Text(renderer.displayName) + if renderer == selectedRenderer { + Image(systemName: "checkmark") + } + } + } + } + } label: { + Label("Terminal Renderer", systemImage: "gearshape.2") + } + } + @ViewBuilder private var terminalSizeIndicator: some View { if viewModel.terminalCols > 0 && viewModel.terminalRows > 0 { @@ -250,6 +398,24 @@ struct TerminalView: View { } } + private var connectionStatusIndicator: some View { + HStack(spacing: 4) { + if viewModel.isConnecting { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(0.5) + .frame(width: 12, height: 12) + } else { + Image(systemName: viewModel.isConnected ? "wifi" : "wifi.slash") + .font(.system(size: 10)) + .foregroundColor(viewModel.isConnected ? Theme.Colors.successAccent : Theme.Colors.errorAccent) + } + Text(viewModel.isConnecting ? "Connecting" : (viewModel.isConnected ? "Connected" : "Disconnected")) + .font(Theme.Typography.terminalSystem(size: 10)) + .foregroundColor(viewModel.isConnected ? Theme.Colors.successAccent : Theme.Colors.errorAccent) + } + } + private var sessionStatusIndicator: some View { HStack(spacing: 4) { Circle() @@ -338,21 +504,41 @@ struct TerminalView: View { private var terminalContent: some View { VStack(spacing: 0) { - // Terminal hosting view - TerminalHostingView( - session: session, - fontSize: $fontSize, - theme: selectedTheme, - onInput: { text in - viewModel.sendInput(text) - }, - onResize: { cols, rows in - viewModel.terminalCols = cols - viewModel.terminalRows = rows - viewModel.resize(cols: cols, rows: rows) - }, - viewModel: viewModel - ) + // Terminal view based on selected renderer + Group { + switch selectedRenderer { + case .swiftTerm: + TerminalHostingView( + session: session, + fontSize: $fontSize, + theme: selectedTheme, + onInput: { text in + viewModel.sendInput(text) + }, + onResize: { cols, rows in + viewModel.terminalCols = cols + viewModel.terminalRows = rows + viewModel.resize(cols: cols, rows: rows) + }, + viewModel: viewModel + ) + case .xterm: + XtermWebView( + session: session, + fontSize: $fontSize, + theme: selectedTheme, + onInput: { text in + viewModel.sendInput(text) + }, + onResize: { cols, rows in + viewModel.terminalCols = cols + viewModel.terminalRows = rows + viewModel.resize(cols: cols, rows: rows) + }, + viewModel: viewModel + ) + } + } .id(viewModel.terminalViewId) .background(selectedTheme.background) .focused($isInputFocused) @@ -363,12 +549,6 @@ struct TerminalView: View { showScrollToBottom = false } ) - .fileBrowserFABOverlay( - isVisible: !keyboardHeight.isZero && session.isRunning, - action: { - showingFileBrowser = true - } - ) // Keyboard toolbar if keyboardHeight > 0 { @@ -407,7 +587,7 @@ class TerminalViewModel { let session: Session let castRecorder: CastRecorder - private var bufferWebSocketClient: BufferWebSocketClient? + var bufferWebSocketClient: BufferWebSocketClient? private var connectionStatusTask: Task? private var connectionErrorTask: Task? weak var terminalCoordinator: TerminalHostingView.Coordinator? @@ -577,6 +757,13 @@ class TerminalViewModel { if castRecorder.isRecording { stopRecording() } + + // Load final snapshot for exited session + Task { @MainActor in + // Give the server a moment to finalize the snapshot + try? await Task.sleep(nanoseconds: 500_000_000) // 0.5s + await loadSnapshot() + } case .bufferUpdate(let snapshot): // Update terminal buffer directly @@ -650,6 +837,14 @@ class TerminalViewModel { // Terminal copy is handled by SwiftTerm's built-in functionality HapticFeedback.notification(.success) } + + func getBufferContent() -> String? { + // Get the current terminal buffer content + if let coordinator = terminalCoordinator { + return coordinator.getBufferContent() + } + return nil + } @MainActor private func handleTerminalBell() { @@ -701,4 +896,20 @@ class TerminalViewModel { resize(cols: optimalCols, rows: terminalRows) } } + + func setMaxWidth(_ maxWidth: Int) { + // Store the max width preference + // When maxWidth is 0, it means unlimited + let targetWidth = maxWidth == 0 ? nil : maxWidth + + if let width = targetWidth, width != terminalCols { + // Maintain aspect ratio when changing width + let aspectRatio = Double(terminalRows) / Double(terminalCols) + let newHeight = Int(Double(width) * aspectRatio) + resize(cols: width, rows: newHeight) + } + + // Update the terminal coordinator if using constrained width + terminalCoordinator?.setMaxWidth(maxWidth) + } } diff --git a/ios/VibeTunnel/Views/Terminal/WidthSelectorPopover.swift b/ios/VibeTunnel/Views/Terminal/WidthSelectorPopover.swift new file mode 100644 index 00000000..f2f1cb2c --- /dev/null +++ b/ios/VibeTunnel/Views/Terminal/WidthSelectorPopover.swift @@ -0,0 +1,198 @@ +import SwiftUI + +/// Popover for selecting terminal width presets +struct WidthSelectorPopover: View { + @Binding var currentWidth: TerminalWidth + @Binding var isPresented: Bool + @State private var customWidth: String = "" + @State private var showCustomInput = false + + var body: some View { + NavigationStack { + List { + Section { + ForEach(TerminalWidth.allCases, id: \.value) { width in + WidthPresetRow( + width: width, + isSelected: currentWidth.value == width.value, + onSelect: { + currentWidth = width + HapticFeedback.impact(.light) + isPresented = false + } + ) + } + } + + Section { + Button(action: { + showCustomInput = true + }) { + HStack { + Image(systemName: "square.and.pencil") + .font(.system(size: 16)) + .foregroundColor(Theme.Colors.primaryAccent) + Text("Custom Width...") + .font(.body) + .foregroundColor(Theme.Colors.terminalForeground) + Spacer() + } + .padding(.vertical, 4) + } + } + + // Show recent custom widths if any + let customWidths = TerminalWidthManager.shared.customWidths + if !customWidths.isEmpty { + Section(header: Text("Recent Custom Widths") + .font(.caption) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) + ) { + ForEach(customWidths, id: \.self) { width in + WidthPresetRow( + width: .custom(width), + isSelected: currentWidth.value == width && !currentWidth.isPreset, + onSelect: { + currentWidth = .custom(width) + HapticFeedback.impact(.light) + isPresented = false + } + ) + } + } + } + } + .listStyle(InsetGroupedListStyle()) + .navigationTitle("Terminal Width") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + isPresented = false + } + .foregroundColor(Theme.Colors.primaryAccent) + } + } + } + .preferredColorScheme(.dark) + .frame(width: 320, height: 400) + .sheet(isPresented: $showCustomInput) { + CustomWidthSheet( + customWidth: $customWidth, + onSave: { width in + if let intWidth = Int(width), intWidth >= 20 && intWidth <= 500 { + currentWidth = .custom(intWidth) + TerminalWidthManager.shared.addCustomWidth(intWidth) + HapticFeedback.notification(.success) + showCustomInput = false + isPresented = false + } + } + ) + } + } +} + +/// Row for displaying a width preset option +private struct WidthPresetRow: View { + let width: TerminalWidth + let isSelected: Bool + let onSelect: () -> Void + + var body: some View { + Button(action: onSelect) { + HStack { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 8) { + Text(width.label) + .font(Theme.Typography.terminalSystem(size: 16)) + .fontWeight(.medium) + .foregroundColor(Theme.Colors.terminalForeground) + + if width.value > 0 { + Text("columns") + .font(.caption) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.5)) + } + } + + Text(width.description) + .font(.caption) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) + } + + Spacer() + + if isSelected { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 20)) + .foregroundColor(Theme.Colors.primaryAccent) + } + } + .padding(.vertical, 4) + } + .buttonStyle(PlainButtonStyle()) + } +} + +/// Sheet for entering a custom width value +private struct CustomWidthSheet: View { + @Binding var customWidth: String + let onSave: (String) -> Void + @Environment(\.dismiss) var dismiss + @FocusState private var isFocused: Bool + + var body: some View { + NavigationStack { + VStack(spacing: Theme.Spacing.large) { + Text("Enter a custom terminal width between 20 and 500 columns") + .font(.body) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) + .multilineTextAlignment(.center) + .padding(.horizontal) + + HStack { + TextField("Width", text: $customWidth) + .font(Theme.Typography.terminalSystem(size: 24)) + .foregroundColor(Theme.Colors.terminalForeground) + .multilineTextAlignment(.center) + .keyboardType(.numberPad) + .focused($isFocused) + .frame(width: 120) + .padding() + .background(Theme.Colors.cardBackground) + .cornerRadius(Theme.CornerRadius.medium) + + Text("columns") + .font(.body) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.5)) + } + + Spacer() + } + .padding(.top, Theme.Spacing.extraLarge) + .navigationTitle("Custom Width") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Cancel") { + dismiss() + } + .foregroundColor(Theme.Colors.primaryAccent) + } + + ToolbarItem(placement: .navigationBarTrailing) { + Button("Save") { + onSave(customWidth) + } + .foregroundColor(Theme.Colors.primaryAccent) + .disabled(customWidth.isEmpty) + } + } + } + .preferredColorScheme(.dark) + .onAppear { + isFocused = true + } + } +} \ No newline at end of file diff --git a/ios/VibeTunnel/Views/Terminal/XtermWebView.swift b/ios/VibeTunnel/Views/Terminal/XtermWebView.swift new file mode 100644 index 00000000..859fafa1 --- /dev/null +++ b/ios/VibeTunnel/Views/Terminal/XtermWebView.swift @@ -0,0 +1,400 @@ +import SwiftUI +import WebKit + +/// WebView-based terminal using xterm.js +struct XtermWebView: UIViewRepresentable { + let session: Session + @Binding var fontSize: CGFloat + let theme: TerminalTheme + let onInput: (String) -> Void + let onResize: (Int, Int) -> Void + var viewModel: TerminalViewModel + + func makeUIView(context: Context) -> WKWebView { + let configuration = WKWebViewConfiguration() + configuration.allowsInlineMediaPlayback = true + configuration.userContentController = WKUserContentController() + + // Add message handlers + configuration.userContentController.add(context.coordinator, name: "terminalInput") + configuration.userContentController.add(context.coordinator, name: "terminalResize") + configuration.userContentController.add(context.coordinator, name: "terminalReady") + configuration.userContentController.add(context.coordinator, name: "terminalLog") + + let webView = WKWebView(frame: .zero, configuration: configuration) + webView.isOpaque = false + webView.backgroundColor = UIColor(theme.background) + webView.scrollView.isScrollEnabled = false + + context.coordinator.webView = webView + context.coordinator.loadTerminal() + + return webView + } + + func updateUIView(_ webView: WKWebView, context: Context) { + // Update font size + context.coordinator.updateFontSize(fontSize) + + // Update theme + context.coordinator.updateTheme(theme) + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, WKScriptMessageHandler, WKNavigationDelegate { + let parent: XtermWebView + weak var webView: WKWebView? + private var bufferWebSocketClient: BufferWebSocketClient? + private var sseClient: SSEClient? + + init(_ parent: XtermWebView) { + self.parent = parent + super.init() + } + + func loadTerminal() { + guard let webView = webView else { return } + + let html = """ + + + + + + + + +
+ + + + + + + """ + + webView.loadHTMLString(html, baseURL: nil) + webView.navigationDelegate = self + } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + switch message.name { + case "terminalInput": + if let data = message.body as? String { + parent.onInput(data) + } + + case "terminalResize": + if let dict = message.body as? [String: Any], + let cols = dict["cols"] as? Int, + let rows = dict["rows"] as? Int { + parent.onResize(cols, rows) + } + + case "terminalReady": + setupDataStreaming() + + case "terminalLog": + if let log = message.body as? String { + print("[XtermWebView] \(log)") + } + + default: + break + } + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + print("[XtermWebView] Page loaded") + } + + private func setupDataStreaming() { + // Subscribe to WebSocket buffer updates + if bufferWebSocketClient == nil { + bufferWebSocketClient = parent.viewModel.bufferWebSocketClient + } + + bufferWebSocketClient?.subscribe(to: parent.session.id) { [weak self] event in + self?.handleWebSocketEvent(event) + } + + // Also set up SSE as fallback + if let streamURL = APIClient.shared.streamURL(for: parent.session.id) { + sseClient = SSEClient(url: streamURL) + sseClient?.delegate = self + sseClient?.start() + } + } + + private func handleWebSocketEvent(_ event: TerminalWebSocketEvent) { + switch event { + case .bufferUpdate(let snapshot): + // Convert buffer snapshot to terminal output + renderBufferSnapshot(snapshot) + + case .output(_, let data): + writeToTerminal(data) + + case .resize(_, _): + // Handle resize if needed + break + + case .bell: + // Could play a sound or visual bell + break + + default: + break + } + } + + private func renderBufferSnapshot(_ snapshot: BufferSnapshot) { + // For now, we'll just write the text content + // In a full implementation, we'd convert the buffer cells to ANSI sequences + var output = "" + for row in snapshot.cells { + for cell in row { + output += cell.char + } + output += "\r\n" + } + writeToTerminal(output) + } + + private func writeToTerminal(_ data: String) { + let escaped = data + .replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "'", with: "\\'") + .replacingOccurrences(of: "\n", with: "\\n") + .replacingOccurrences(of: "\r", with: "\\r") + + webView?.evaluateJavaScript("window.xtermAPI.writeToTerminal('\(escaped)')") { _, error in + if let error = error { + print("[XtermWebView] Error writing to terminal: \(error)") + } + } + } + + func updateFontSize(_ size: CGFloat) { + webView?.evaluateJavaScript("window.xtermAPI.updateFontSize(\(size))") + } + + func updateTheme(_ theme: TerminalTheme) { + // Convert theme to xterm.js format + let themeJS = """ + { + background: '\(theme.background.hex)', + foreground: '\(theme.foreground.hex)', + cursor: '\(theme.cursor.hex)', + selection: 'rgba(255, 255, 255, 0.3)' + } + """ + webView?.evaluateJavaScript("window.xtermAPI.updateTheme(\(themeJS))") + } + + func scrollToBottom() { + webView?.evaluateJavaScript("window.xtermAPI.scrollToBottom()") + } + + func clear() { + webView?.evaluateJavaScript("window.xtermAPI.clear()") + } + } +} + +// MARK: - SSEClientDelegate +@MainActor +extension XtermWebView.Coordinator: SSEClientDelegate { + nonisolated func sseClient(_ client: SSEClient, didReceiveEvent event: SSEClient.SSEEvent) { + Task { @MainActor in + switch event { + case .terminalOutput(_, let type, let data): + if type == "o" { // output + writeToTerminal(data) + } + case .exit(let exitCode, _): + writeToTerminal("\r\n[Process exited with code \(exitCode)]\r\n") + case .error(let error): + print("[XtermWebView] SSE error: \(error)") + } + } + } +} + +// Helper extension for Color to hex +extension Color { + var hex: String { + let uiColor = UIColor(self) + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + + uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + return String(format: "#%02X%02X%02X", + Int(red * 255), + Int(green * 255), + Int(blue * 255)) + } +} \ No newline at end of file diff --git a/ios/VibeTunnelTests/Services/BufferWebSocketClientTests.swift b/ios/VibeTunnelTests/Services/BufferWebSocketClientTests.swift index 5bf59f6d..93be7a75 100644 --- a/ios/VibeTunnelTests/Services/BufferWebSocketClientTests.swift +++ b/ios/VibeTunnelTests/Services/BufferWebSocketClientTests.swift @@ -2,6 +2,78 @@ import Foundation import Testing @testable import VibeTunnel +// Temporarily include MockWebSocketFactory here until it's properly added to the project +@MainActor +class MockWebSocket: WebSocketProtocol { + weak var delegate: WebSocketDelegate? + + // State tracking + private(set) var isConnected = false + private(set) var lastConnectURL: URL? + private(set) var lastConnectHeaders: [String: String]? + + // Control test behavior + var shouldFailConnection = false + var connectionError: Error? + + func connect(to url: URL, with headers: [String: String]) async throws { + lastConnectURL = url + lastConnectHeaders = headers + + if shouldFailConnection { + let error = connectionError ?? WebSocketError.connectionFailed + throw error + } + + isConnected = true + delegate?.webSocketDidConnect(self) + } + + func send(_ message: WebSocketMessage) async throws { + guard isConnected else { + throw WebSocketError.connectionFailed + } + } + + func sendPing() async throws { + guard isConnected else { + throw WebSocketError.connectionFailed + } + } + + func disconnect(with code: URLSessionWebSocketTask.CloseCode, reason: Data?) { + if isConnected { + isConnected = false + delegate?.webSocketDidDisconnect(self, closeCode: code, reason: reason) + } + } + + func simulateMessage(_ message: WebSocketMessage) { + guard isConnected else { return } + delegate?.webSocket(self, didReceiveMessage: message) + } + + func simulateError(_ error: Error) { + guard isConnected else { return } + delegate?.webSocket(self, didFailWithError: error) + } +} + +@MainActor +class MockWebSocketFactory: WebSocketFactory { + private(set) var createdWebSockets: [MockWebSocket] = [] + + func createWebSocket() -> WebSocketProtocol { + let webSocket = MockWebSocket() + createdWebSockets.append(webSocket) + return webSocket + } + + var lastCreatedWebSocket: MockWebSocket? { + createdWebSockets.last + } +} + @Suite("BufferWebSocketClient Tests", .tags(.critical, .websocket)) @MainActor struct BufferWebSocketClientTests { @@ -270,8 +342,7 @@ private func saveTestServerConfig() { let config = ServerConfig( host: "localhost", port: 8888, - useSSL: false, - username: nil, + name: nil, password: nil ) diff --git a/ios/VibeTunnelTests/Utilities/TestFixtures.swift b/ios/VibeTunnelTests/Utilities/TestFixtures.swift index 040414d3..4cc81ce0 100644 --- a/ios/VibeTunnelTests/Utilities/TestFixtures.swift +++ b/ios/VibeTunnelTests/Utilities/TestFixtures.swift @@ -18,7 +18,7 @@ enum TestFixtures { static let validSession = Session( id: "test-session-123", - command: "/bin/bash", + command: ["/bin/bash"], workingDir: "/Users/test", name: "Test Session", status: .running, @@ -26,14 +26,15 @@ enum TestFixtures { startedAt: "2024-01-01T10:00:00Z", lastModified: "2024-01-01T10:05:00Z", pid: 12_345, - waiting: false, - width: 80, - height: 24 + source: nil, + remoteId: nil, + remoteName: nil, + remoteUrl: nil ) static let exitedSession = Session( id: "exited-session-456", - command: "/usr/bin/echo", + command: ["/usr/bin/echo"], workingDir: "/tmp", name: "Exited Session", status: .exited, @@ -41,38 +42,33 @@ enum TestFixtures { startedAt: "2024-01-01T09:00:00Z", lastModified: "2024-01-01T09:00:05Z", pid: nil, - waiting: false, - width: 80, - height: 24 + source: nil, + remoteId: nil, + remoteName: nil, + remoteUrl: nil ) static let sessionsJSON = """ [ { "id": "test-session-123", - "command": "/bin/bash", + "command": ["/bin/bash"], "workingDir": "/Users/test", "name": "Test Session", "status": "running", "startedAt": "2024-01-01T10:00:00Z", "lastModified": "2024-01-01T10:05:00Z", - "pid": 12345, - "waiting": false, - "width": 80, - "height": 24 + "pid": 12345 }, { "id": "exited-session-456", - "command": "/usr/bin/echo", + "command": ["/usr/bin/echo"], "workingDir": "/tmp", "name": "Exited Session", "status": "exited", "exitCode": 0, "startedAt": "2024-01-01T09:00:00Z", - "lastModified": "2024-01-01T09:00:05Z", - "waiting": false, - "width": 80, - "height": 24 + "lastModified": "2024-01-01T09:00:05Z" } ] """ diff --git a/ios/docs/ios-plan.md b/ios/docs/ios-plan.md new file mode 100644 index 00000000..5eb040cf --- /dev/null +++ b/ios/docs/ios-plan.md @@ -0,0 +1,267 @@ +# iOS App Update Plan + +This document outlines the comprehensive plan to update the VibeTunnel iOS app to match all features available in the JavaScript front-end. + +## Analysis Summary + +The iOS app is well-architected but missing several key features and has critical API communication issues that prevent it from working properly with the current server implementation. + +## Feature Comparison: JavaScript vs iOS + +### ✅ Features Present in Both +1. Session management (list, create, kill, cleanup) +2. Terminal display and input +3. WebSocket binary buffer streaming +4. File browser with git integration +5. Terminal resizing +6. Font size adjustment +7. Connection management +8. Error handling and reconnection +9. Mobile-specific controls (arrow keys, special keys) + +### ❌ Missing in iOS App + +1. **SSE Text Streaming** (`/api/sessions/:id/stream`) + - JS uses SSE for real-time text output as primary method + - iOS only uses binary WebSocket, no SSE implementation active + +2. **File Preview with Syntax Highlighting** + - JS has CodeMirror integration for code preview + - iOS only has basic text viewing + +3. **Git Diff Viewer** + - JS: `/api/fs/diff?path=...` endpoint for viewing diffs + - iOS: No diff viewing capability + +4. **System Logs Viewer** + - JS: Full logs viewer with filtering, search, download + - iOS: No logs access + +5. **Hot Reload Support** + - JS: Development hot reload via WebSocket + - iOS: Not applicable but no equivalent dev mode + +6. **Cast File Import/Playback** + - JS: Can import and play external cast files + - iOS: Only records, no import capability + +7. **URL Detection in Terminal** + - JS: Clickable URLs in terminal output + - iOS: No URL detection + +8. **Session Name in Creation** + - JS: Supports custom session names + - iOS: Has UI but may not send to server + +### 🔄 Different Implementations + +1. **Terminal Rendering** + - JS: Custom renderer with xterm.js (headless) + - iOS: SwiftTerm library + +2. **State Management** + - JS: Local storage for preferences + - iOS: UserDefaults + @Observable + +3. **Terminal Controls** + - JS: Ctrl key grid popup + - iOS: Toolbar with common keys + +4. **File Path Insertion** + - JS: Direct insertion into terminal + - iOS: Copy to clipboard only + +### 🔧 API Endpoint Differences + +1. **Missing Endpoints in iOS**: + - `GET /api/fs/preview` - File preview + - `GET /api/fs/diff` - Git diffs + - `GET /api/logs/raw` - System logs + - `GET /api/logs/info` - Log metadata + - `DELETE /api/logs/clear` - Clear logs + +2. **Different Endpoint Usage**: + - iOS uses `/api/cleanup-exited` vs JS uses `DELETE /api/sessions` + - iOS has `/api/mkdir` which JS doesn't use + +## Implementation Plan + +### Phase 1: Critical Server Communication Fixes 🚨 + +1. **Fix Session Creation API** + - Update `APIClient.createSession()` to match JS payload: + - Add `spawn_terminal: true` field (currently missing!) + - Ensure `cols` and `rows` are sent + - Verify `name` field is included + - File: `ios/VibeTunnel/Services/APIClient.swift` + +2. **Implement SSE Text Streaming** + - Add SSEClient implementation for `/api/sessions/:id/stream` + - Handle event parsing: `[timestamp, type, data]` format + - Process exit events: `['exit', exitCode, sessionId]` + - Integrate with TerminalView as alternative to WebSocket + - Files: Create `ios/VibeTunnel/Services/SSEClient.swift` + +3. **Fix Binary WebSocket Protocol** + - Verify magic byte handling (0xBF) + - Ensure proper session ID encoding in binary messages + - Handle all message types: connected, subscribed, ping/pong, error + - File: `ios/VibeTunnel/Services/BufferWebSocketClient.swift` + +### Phase 2: Essential Missing Features 🔧 + +4. **Add File Preview with Syntax Highlighting** + - Implement `/api/fs/preview` endpoint call + - Add syntax highlighting library (Highlightr or similar) + - Support text/image/binary preview types + - Files: Update `APIClient.swift`, create `FilePreviewView.swift` + +5. **Add Git Diff Viewer** + - Implement `/api/fs/diff` endpoint + - Create diff viewer UI component + - Integrate with file browser + - Files: Update `APIClient.swift`, create `GitDiffView.swift` + +6. **Fix Session Cleanup Endpoint** + - Change from `/api/cleanup-exited` to `DELETE /api/sessions` + - Update to match JS implementation + - File: `ios/VibeTunnel/Services/APIClient.swift` + +### Phase 3: Enhanced Features ✨ + +7. **Add System Logs Viewer** + - Implement logs endpoints: `/api/logs/raw`, `/api/logs/info` + - Create logs viewer with filtering and search + - Add download capability + - Files: Create `LogsView.swift`, update `APIClient.swift` + +8. **Improve Terminal Features** + - Add URL detection and clickable links + - Implement selection-based copy (not just copy-all) + - Add terminal search functionality + - File: `ios/VibeTunnel/Views/Terminal/TerminalView.swift` + +9. **Add Cast File Import** + - Implement cast file parser + - Add import from Files app + - Create playback from imported files + - Files: Update `CastPlayerView.swift`, `CastRecorder.swift` + +### Phase 4: UI/UX Improvements 💫 + +10. **File Browser Enhancements** + - Add file upload capability + - Implement direct path insertion to terminal + - Add multi-select for batch operations + - File: `ios/VibeTunnel/Views/FileBrowser/FileBrowserView.swift` + +11. **Session Management** + - Add session renaming capability + - Implement session tags/categories + - Add session history/favorites + - File: `ios/VibeTunnel/Views/Sessions/SessionListView.swift` + +### Phase 5: iPad Optimizations 📱 + +12. **iPad-Specific Features** + - Implement split view support + - Add keyboard shortcuts + - Optimize for larger screens + - Support multiple concurrent sessions view + +## Implementation Priority + +1. **Immediate (Phase 1)**: Fix session creation and server communication +2. **High (Phase 2)**: Add file preview, git diff, fix endpoints +3. **Medium (Phase 3)**: Logs viewer, terminal improvements, cast import +4. **Low (Phase 4-5)**: UI enhancements, iPad optimizations + +## Testing Checklist + +- [ ] Create new sessions with various commands +- [ ] Verify terminal output appears correctly +- [ ] Test terminal input and special keys +- [ ] Confirm WebSocket reconnection works +- [ ] Test file browser and preview +- [ ] Verify git integration features +- [ ] Test session management operations +- [ ] Check error handling and offline mode + +## JavaScript Front-End Features (Complete List) + +### 1. Session Management +- Session list with live updates (3-second polling) +- Create sessions with custom commands and working directories +- Kill individual sessions or all at once +- Cleanup exited sessions +- Session status tracking (running, exited, waiting) +- Session filtering and search + +### 2. Terminal I/O and Display +- Real-time terminal output via SSE and WebSocket +- Full keyboard input with special keys +- Dynamic terminal resizing +- Copy/paste support +- Scroll control with auto-scroll +- Font size control (8-32px) +- Width control and fit-to-width mode +- Mobile input support with on-screen keyboard + +### 3. Binary Terminal Buffer Streaming +- WebSocket connection for efficient updates +- Binary protocol with magic bytes +- Auto-reconnection with exponential backoff +- Buffer synchronization +- Content change detection + +### 4. File Browser +- Directory navigation with git integration +- File preview with syntax highlighting (CodeMirror) +- Image preview +- Git status display and diff viewer +- File filtering by git status +- Path insertion into terminal + +### 5. System Logs Viewer +- Real-time log display (2-second refresh) +- Filter by log level and source +- Text search +- Auto-scroll toggle +- Log download and clearing + +### 6. Additional Features +- Hot reload for development +- Local storage for preferences +- URL routing with session state +- Error notifications with auto-dismiss +- Cast file support (playback and conversion) +- ANSI color support (256 colors and true color) +- URL detection in terminal output +- Performance optimizations (batched rendering) + +## API Endpoints Reference + +### Session Management +- `GET /api/sessions` - List all sessions +- `POST /api/sessions` - Create new session +- `DELETE /api/sessions/:id` - Kill session +- `DELETE /api/sessions` - Cleanup all exited sessions +- `POST /api/sessions/:id/input` - Send input +- `POST /api/sessions/:id/resize` - Resize terminal +- `GET /api/sessions/:id/snapshot` - Get terminal snapshot +- `GET /api/sessions/:id/stream` - SSE stream for output + +### File System +- `GET /api/fs/browse?path=...&showHidden=...&gitFilter=...` - Browse directories +- `GET /api/fs/preview?path=...` - Preview file content +- `GET /api/fs/diff?path=...` - Get git diff + +### System +- `GET /api/logs/raw` - Get raw logs +- `GET /api/logs/info` - Get log metadata +- `DELETE /api/logs/clear` - Clear logs +- `GET /api/health` - Health check + +### WebSocket +- `ws://server/buffers` - Binary terminal buffer streaming +- `ws://server/?hotReload=true` - Development hot reload \ No newline at end of file diff --git a/mac/.gitignore b/mac/.gitignore index 0039b509..49e2f7e9 100644 --- a/mac/.gitignore +++ b/mac/.gitignore @@ -28,4 +28,10 @@ VibeTunnel/Resources/node/ VibeTunnel/Resources/node-server/ # Local development configuration -VibeTunnel/Local.xcconfig \ No newline at end of file +VibeTunnel/Local.xcconfig + +# Sparkle private key - NEVER commit this! +sparkle-private-ed-key.pem +sparkle-private-key-KEEP-SECURE.txt +*.pem +private/ \ No newline at end of file diff --git a/mac/CHANGELOG.md b/mac/CHANGELOG.md new file mode 100644 index 00000000..f6fee777 --- /dev/null +++ b/mac/CHANGELOG.md @@ -0,0 +1,200 @@ +# Changelog + +## [1.0.0-beta.3] - 2025-06-23 + +There's too much to list! This is the version you've been waiting for. + +- Redesigned, responsive, animated frontend. +- Improved terminal width spanning and layout optimization +- File-Picker to see files on-the-go. +- Creating new Terminals is now much more reliable. +- Added terminal font size adjustment in the settings dropdown +- Fresh new icon for Progressive Web App installations +- Refined bounce animations for a more subtle, professional feel +- Added retro CRT-style phosphor decay visual effect for closed terminals +- Fixed buffer aggregator message handling for smoother terminal updates +- Better support for shell aliases and improved debug logging +- Enhanced Unix socket server implementation for faster local communication +- Special handling for Warp terminal with custom enter key behavior +- New dock menu with quick actions when right-clicking the app icon +- More resilient vt command-line tool with better error handling +- Ensured vibetunnel server properly terminates when Mac app is killed + +## [1.0.0-beta.2] - 2025-06-19 + +### 🎨 Improvements +- Redesigned slick new web frontend +- Faster terminal rendering in the web frontend +- New Sessions spawn new Terminal windows. (This needs Applescript and Accessibility permissions) +- Enhanced font handling with system font priority +- Better async operations in PTY service for improved performance +- Improved window activation when showing the welcome and settings windows +- Preparations for Linux support + +### 🐛 Bug Fixes +- Fixed window front order when dock icon is hidden +- Fixed PTY service enhancements with proper async operations +- Fixed race condition in session creation that caused frontend to open previous session + +## [1.0.0-beta.1] - 2025-06-17 + +### 🎉 First Public Beta Release + +This is the first public beta release of VibeTunnel, ready for testing by early adopters. + +### ✨ What's Included +- Complete terminal session proxying to web browsers +- Support for multiple concurrent sessions +- Real-time terminal rendering with full TTY support +- Secure password-protected dashboard +- Tailscale and ngrok integration for remote access +- Automatic updates via Sparkle framework +- Native macOS menu bar application + +### 🐛 Bug Fixes Since Internal Testing +- Fixed visible circle spacer in menu (now uses Color.clear) +- Removed development files from app bundle +- Enhanced build process with automatic cleanup +- Fixed Sparkle API compatibility for v2.7.0 + +### 📝 Notes +- This is a beta release - please report any issues on GitHub +- Auto-update functionality is fully enabled +- All core features are stable and ready for daily use + +### ✨ What's New Since Internal Testing +- Improved stability and performance +- Enhanced error handling for edge cases +- Refined UI/UX based on internal feedback +- Better session cleanup and resource management +- Optimized for macOS Sonoma and Sequoia + +### 🐛 Known Issues +- Occasional connection drops with certain terminal applications +- Performance optimization needed for very long sessions +- Some terminal escape sequences may not render perfectly + +### 📝 Notes +- This is a beta release - please report any issues on GitHub +- Auto-update functionality is fully enabled +- All core features are stable and ready for daily use + +## [1.0.0] - 2025-06-16 + +### 🎉 Initial Release + +VibeTunnel is a native macOS application that proxies terminal sessions to web browsers, allowing you to monitor and control terminals from any device. + +### ✨ Core Features + +#### Terminal Management +- **Terminal Session Proxying** - Run any command with `vt` prefix to make it accessible via web browser +- **Multiple Concurrent Sessions** - Support for multiple terminal sessions running simultaneously +- **Session Recording** - All sessions automatically recorded in asciinema format for later playback +- **Full TTY Support** - Proper handling of terminal control sequences, colors, and special characters +- **Interactive Commands** - Support for interactive applications like vim, htop, and more +- **Shell Integration** - Direct shell access with `vt --shell` or `vt -i` + +#### Web Interface +- **Browser-Based Dashboard** - Access all terminal sessions at http://localhost:4020 +- **Real-time Terminal Rendering** - Live terminal output using asciinema player +- **WebSocket Streaming** - Low-latency real-time updates for terminal I/O +- **Mobile Responsive** - Fully functional on phones, tablets, and desktop browsers +- **Session Management UI** - Create, view, kill, and manage sessions from the web interface + +#### Security & Access Control +- **Password Protection** - Optional password authentication for dashboard access +- **Keychain Integration** - Secure password storage using macOS Keychain +- **Access Modes** - Choose between localhost-only, network, or secure tunneling +- **Basic Authentication** - HTTP Basic Auth support for network access + +#### Remote Access Options +- **Tailscale Integration** - Access VibeTunnel through your Tailscale network +- **ngrok Support** - Built-in ngrok tunneling for public access with authentication +- **Network Mode** - Local network access with IP-based connections + +#### macOS Integration +- **Menu Bar Application** - Lives in the system menu bar with optional dock mode +- **Launch at Login** - Automatic startup with macOS +- **Auto Updates** - Sparkle framework integration for seamless updates +- **Native Swift/SwiftUI** - Built with modern macOS technologies +- **Universal Binary** - Native support for both Intel and Apple Silicon Macs + +#### CLI Tool (`vt`) +- **Command Wrapper** - Prefix any command with `vt` to tunnel it +- **Claude Integration** - Special support for AI assistants with `vt --claude` and `vt --claude-yolo` +- **Direct Execution** - Bypass shell with `vt -S` for direct command execution +- **Automatic Installation** - CLI tool automatically installed to /usr/local/bin + +#### Server Implementation +- **Dual Server Architecture** - Choose between Rust (default) or Swift server backends +- **High Performance** - Rust server for efficient TTY forwarding and process management +- **RESTful APIs** - Clean API design for session management +- **Health Monitoring** - Built-in health check endpoints + +#### Developer Features +- **Server Console** - Debug view showing server logs and diagnostics +- **Configurable Ports** - Change server port from default 4020 +- **Session Cleanup** - Automatic cleanup of stale sessions on startup +- **Comprehensive Logging** - Detailed logs for debugging + +### 🛠️ Technical Details + +- **Minimum macOS Version**: 14.0 (Sonoma) +- **Architecture**: Universal Binary (Intel + Apple Silicon) +- **Languages**: Swift 6.0, Rust, TypeScript +- **UI Framework**: SwiftUI +- **Web Technologies**: TypeScript, Tailwind CSS, WebSockets +- **Build System**: Xcode, Swift Package Manager, Cargo, npm + +### 📦 Installation + +- Download DMG from GitHub releases +- Drag VibeTunnel to Applications folder +- Launch from Applications or Spotlight +- CLI tool (`vt`) automatically installed on first launch + +### 🚀 Quick Start + +```bash +# Monitor AI agents +vt claude + +# Run development servers +vt npm run dev + +# Watch long-running processes +vt python train_model.py + +# Open interactive shell +vt --shell +``` + +### 👥 Contributors + +Created by: +- [@badlogic](https://mariozechner.at/) - Mario Zechner +- [@mitsuhiko](https://lucumr.pocoo.org/) - Armin Ronacher +- [@steipete](https://steipete.com/) - Peter Steinberger + +### 📄 License + +VibeTunnel is open source software licensed under the MIT License. + +--- + +## Version History + +### Pre-release Development + +The project went through extensive development before the 1.0.0 release, including: + +- Initial TTY forwarding implementation using Rust +- macOS app foundation with SwiftUI +- Integration of asciinema format for session recording +- Web frontend development with real-time terminal rendering +- Hummingbird HTTP server implementation +- ngrok integration for secure tunneling +- Sparkle framework integration for auto-updates +- Comprehensive testing and bug fixes +- UI/UX refinements and mobile optimizations \ No newline at end of file diff --git a/mac/VibeTunnel-Mac.xcodeproj/project.pbxproj b/mac/VibeTunnel-Mac.xcodeproj/project.pbxproj index 370f8300..24215e7f 100644 --- a/mac/VibeTunnel-Mac.xcodeproj/project.pbxproj +++ b/mac/VibeTunnel-Mac.xcodeproj/project.pbxproj @@ -464,6 +464,7 @@ PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.vibetunnel; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + VIBETUNNEL_USE_CUSTOM_NODE = YES; }; name = Debug; }; @@ -501,6 +502,7 @@ PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.vibetunnel; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + VIBETUNNEL_USE_CUSTOM_NODE = YES; }; name = Release; }; diff --git a/mac/VibeTunnel.xcodeproj/project.pbxproj b/mac/VibeTunnel.xcodeproj/project.pbxproj deleted file mode 100644 index a95b9db4..00000000 --- a/mac/VibeTunnel.xcodeproj/project.pbxproj +++ /dev/null @@ -1,590 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 78AD8B952E051ED40009725C /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 78AD8B942E051ED40009725C /* Logging */; }; - 89D01D862CB5D7DC0075D8BD /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 89D01D852CB5D7DC0075D8BD /* Sparkle */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 788687FF2DFF4FCB00B22C15 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 788687E92DFF4FCB00B22C15 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 788687F02DFF4FCB00B22C15; - remoteInfo = VibeTunnel; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 788687F12DFF4FCB00B22C15 /* VibeTunnel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VibeTunnel.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 788687FE2DFF4FCB00B22C15 /* VibeTunnelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VibeTunnelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 78868B612DFF808300B22C15 /* Exceptions for "VibeTunnel" folder in "VibeTunnel" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Info.plist, - Shared.xcconfig, - version.xcconfig, - ); - target = 788687F02DFF4FCB00B22C15 /* VibeTunnel */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 788687F32DFF4FCB00B22C15 /* VibeTunnel */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 78868B612DFF808300B22C15 /* Exceptions for "VibeTunnel" folder in "VibeTunnel" target */, - ); - path = VibeTunnel; - sourceTree = ""; - }; - 788688012DFF4FCB00B22C15 /* VibeTunnelTests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = VibeTunnelTests; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - 788687EE2DFF4FCB00B22C15 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 78AD8B952E051ED40009725C /* Logging in Frameworks */, - 89D01D862CB5D7DC0075D8BD /* Sparkle in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 788687FB2DFF4FCB00B22C15 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 788687E82DFF4FCB00B22C15 = { - isa = PBXGroup; - children = ( - 788687F32DFF4FCB00B22C15 /* VibeTunnel */, - 788688012DFF4FCB00B22C15 /* VibeTunnelTests */, - 78AD8B8F2E051ED40009725C /* Frameworks */, - 788687F22DFF4FCB00B22C15 /* Products */, - ); - sourceTree = ""; - }; - 788687F22DFF4FCB00B22C15 /* Products */ = { - isa = PBXGroup; - children = ( - 788687F12DFF4FCB00B22C15 /* VibeTunnel.app */, - 788687FE2DFF4FCB00B22C15 /* VibeTunnelTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 78AD8B8F2E051ED40009725C /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 788687F02DFF4FCB00B22C15 /* VibeTunnel */ = { - isa = PBXNativeTarget; - buildConfigurationList = 788688152DFF4FCC00B22C15 /* Build configuration list for PBXNativeTarget "VibeTunnel" */; - buildPhases = ( - C3D4E5F6A7B8C9D0E1F23456 /* Install Build Dependencies */, - 788687ED2DFF4FCB00B22C15 /* Sources */, - 788687EE2DFF4FCB00B22C15 /* Frameworks */, - 788687EF2DFF4FCB00B22C15 /* Resources */, - B2C3D4E5F6A7B8C9D0E1F234 /* Build Web Frontend */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 788687F32DFF4FCB00B22C15 /* VibeTunnel */, - ); - name = VibeTunnel; - packageProductDependencies = ( - 89D01D852CB5D7DC0075D8BD /* Sparkle */, - 78AD8B942E051ED40009725C /* Logging */, - ); - productName = VibeTunnel; - productReference = 788687F12DFF4FCB00B22C15 /* VibeTunnel.app */; - productType = "com.apple.product-type.application"; - }; - 788687FD2DFF4FCB00B22C15 /* VibeTunnelTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 788688182DFF4FCC00B22C15 /* Build configuration list for PBXNativeTarget "VibeTunnelTests" */; - buildPhases = ( - 788687FA2DFF4FCB00B22C15 /* Sources */, - 788687FB2DFF4FCB00B22C15 /* Frameworks */, - 788687FC2DFF4FCB00B22C15 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 788688002DFF4FCB00B22C15 /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - 788688012DFF4FCB00B22C15 /* VibeTunnelTests */, - ); - name = VibeTunnelTests; - productName = VibeTunnelTests; - productReference = 788687FE2DFF4FCB00B22C15 /* VibeTunnelTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 788687E92DFF4FCB00B22C15 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1610; - LastUpgradeCheck = 2600; - TargetAttributes = { - 788687F02DFF4FCB00B22C15 = { - CreatedOnToolsVersion = 16.1; - }; - 788687FD2DFF4FCB00B22C15 = { - CreatedOnToolsVersion = 16.1; - TestTargetID = 788687F02DFF4FCB00B22C15; - }; - }; - }; - buildConfigurationList = 788687EC2DFF4FCB00B22C15 /* Build configuration list for PBXProject "VibeTunnel" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 788687E82DFF4FCB00B22C15; - minimizedProjectReferenceProxies = 1; - packageReferences = ( - 89D01D842CB5D7DC0075D8BD /* XCRemoteSwiftPackageReference "Sparkle" */, - 78AD8B8E2E051EB50009725C /* XCRemoteSwiftPackageReference "swift-log" */, - ); - preferredProjectObjectVersion = 77; - productRefGroup = 788687F22DFF4FCB00B22C15 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 788687F02DFF4FCB00B22C15 /* VibeTunnel */, - 788687FD2DFF4FCB00B22C15 /* VibeTunnelTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 788687EF2DFF4FCB00B22C15 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 788687FC2DFF4FCB00B22C15 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - B2C3D4E5F6A7B8C9D0E1F234 /* Build Web Frontend */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "$(SRCROOT)/../web/", - ); - name = "Build Web Frontend"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(CONTENTS_FOLDER_PATH)/Resources/web/public", - "$(BUILT_PRODUCTS_DIR)/$(CONTENTS_FOLDER_PATH)/Resources/vibetunnel", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/zsh; - shellScript = "# Build web frontend using Bun\necho \"Building web frontend...\"\n\n# Run the build script\n\"${SRCROOT}/scripts/build-web-frontend.sh\"\n\nif [ $? -ne 0 ]; then\n echo \"error: Web frontend build failed\"\n exit 1\nfi\n"; - }; - C3D4E5F6A7B8C9D0E1F23456 /* Install Build Dependencies */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Install Build Dependencies"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/zsh; - shellScript = "# Install build dependencies\necho \"Checking build dependencies...\"\n\n# Run the install script\n\"${SRCROOT}/scripts/install-bun.sh\"\n\nif [ $? -ne 0 ]; then\n echo \"error: Failed to install build dependencies\"\n exit 1\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 788687ED2DFF4FCB00B22C15 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 788687FA2DFF4FCB00B22C15 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 788688002DFF4FCB00B22C15 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 788687F02DFF4FCB00B22C15 /* VibeTunnel */; - targetProxy = 788687FF2DFF4FCB00B22C15 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 788688102DFF4FCC00B22C15 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReferenceAnchor = 788687F32DFF4FCB00B22C15 /* VibeTunnel */; - baseConfigurationReferenceRelativePath = Shared.xcconfig; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 6.0; - }; - name = Debug; - }; - 788688112DFF4FCC00B22C15 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReferenceAnchor = 788687F32DFF4FCB00B22C15 /* VibeTunnel */; - baseConfigurationReferenceRelativePath = Shared.xcconfig; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = macosx; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 6.0; - }; - name = Release; - }; - 788688132DFF4FCC00B22C15 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; - CODE_SIGN_ENTITLEMENTS = VibeTunnel/VibeTunnel.entitlements; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = "$(inherited)"; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 7F5Y92G2Z4; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = VibeTunnel/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = VibeTunnel; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; - INFOPLIST_KEY_LSUIElement = YES; - INFOPLIST_KEY_NSAppleEventsUsageDescription = "VibeTunnel uses AppleScript to spawn a terminal when you create a new session in the dashboard. This allows VibeTunnel to automatically open your preferred terminal application and connect it to the remote session."; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 VibeTunnel Team. All rights reserved."; - INFOPLIST_KEY_NSMainStoryboardFile = Main; - INFOPLIST_KEY_NSPrincipalClass = NSApplication; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.vibetunnel; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - }; - name = Debug; - }; - 788688142DFF4FCC00B22C15 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; - CODE_SIGN_ENTITLEMENTS = VibeTunnel/VibeTunnel.entitlements; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = "$(inherited)"; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 7F5Y92G2Z4; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = VibeTunnel/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = VibeTunnel; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; - INFOPLIST_KEY_LSUIElement = YES; - INFOPLIST_KEY_NSAppleEventsUsageDescription = "VibeTunnel uses AppleScript to spawn a terminal when you create a new session in the dashboard. This allows VibeTunnel to automatically open your preferred terminal application and connect it to the remote session."; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 VibeTunnel Team. All rights reserved."; - INFOPLIST_KEY_NSMainStoryboardFile = Main; - INFOPLIST_KEY_NSPrincipalClass = NSApplication; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.vibetunnel; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - }; - name = Release; - }; - 788688162DFF4FCC00B22C15 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = "$(inherited)"; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = ""; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.vibetunnelTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VibeTunnel.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VibeTunnel"; - }; - name = Debug; - }; - 788688172DFF4FCC00B22C15 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = "$(inherited)"; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = ""; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = sh.vibetunnel.vibetunnelTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VibeTunnel.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/VibeTunnel"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 788687EC2DFF4FCB00B22C15 /* Build configuration list for PBXProject "VibeTunnel" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 788688102DFF4FCC00B22C15 /* Debug */, - 788688112DFF4FCC00B22C15 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 788688152DFF4FCC00B22C15 /* Build configuration list for PBXNativeTarget "VibeTunnel" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 788688132DFF4FCC00B22C15 /* Debug */, - 788688142DFF4FCC00B22C15 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 788688182DFF4FCC00B22C15 /* Build configuration list for PBXNativeTarget "VibeTunnelTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 788688162DFF4FCC00B22C15 /* Debug */, - 788688172DFF4FCC00B22C15 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 78AD8B8E2E051EB50009725C /* XCRemoteSwiftPackageReference "swift-log" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-log.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.6.3; - }; - }; - 89D01D842CB5D7DC0075D8BD /* XCRemoteSwiftPackageReference "Sparkle" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sparkle-project/Sparkle"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.7.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78AD8B942E051ED40009725C /* Logging */ = { - isa = XCSwiftPackageProductDependency; - package = 78AD8B8E2E051EB50009725C /* XCRemoteSwiftPackageReference "swift-log" */; - productName = Logging; - }; - 89D01D852CB5D7DC0075D8BD /* Sparkle */ = { - isa = XCSwiftPackageProductDependency; - package = 89D01D842CB5D7DC0075D8BD /* XCRemoteSwiftPackageReference "Sparkle" */; - productName = Sparkle; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 788687E92DFF4FCB00B22C15 /* Project object */; -} diff --git a/mac/VibeTunnel.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mac/VibeTunnel.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a6..00000000 --- a/mac/VibeTunnel.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/mac/VibeTunnel/version.xcconfig b/mac/VibeTunnel/version.xcconfig index f016d202..5e4f40b8 100644 --- a/mac/VibeTunnel/version.xcconfig +++ b/mac/VibeTunnel/version.xcconfig @@ -2,7 +2,7 @@ // This file contains the version and build number for the app MARKETING_VERSION = 1.0.0-beta.3 -CURRENT_PROJECT_VERSION = 108 +CURRENT_PROJECT_VERSION = 110 // Domain and GitHub configuration APP_DOMAIN = vibetunnel.sh diff --git a/mac/VibeTunnel/vt b/mac/VibeTunnel/vt index d5580be1..4683dd3e 100755 --- a/mac/VibeTunnel/vt +++ b/mac/VibeTunnel/vt @@ -13,15 +13,25 @@ done # If not found in standard locations with valid binary, search for it if [ -z "$APP_PATH" ]; then # First try DerivedData (for development) - APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "VibeTunnel.app" -type d 2>/dev/null | grep -v "\.dSYM" | head -1) + for CANDIDATE in $(find ~/Library/Developer/Xcode/DerivedData -name "VibeTunnel.app" -type d 2>/dev/null | grep -v "\.dSYM" | grep -v "Index\.noindex"); do + if [ -f "$CANDIDATE/Contents/Resources/vibetunnel" ]; then + APP_PATH="$CANDIDATE" + break + fi + done # If still not found, use mdfind as last resort - if [ -z "$APP_PATH" ] || [ ! -d "$APP_PATH" ]; then - APP_PATH=$(mdfind -name "VibeTunnel.app" 2>/dev/null | grep -v "\.dSYM" | head -1) + if [ -z "$APP_PATH" ]; then + for CANDIDATE in $(mdfind -name "VibeTunnel.app" 2>/dev/null | grep -v "\.dSYM"); do + if [ -f "$CANDIDATE/Contents/Resources/vibetunnel" ]; then + APP_PATH="$CANDIDATE" + break + fi + done fi - if [ -z "$APP_PATH" ] || [ ! -d "$APP_PATH" ]; then - echo "Error: VibeTunnel.app not found anywhere on the system" >&2 + if [ -z "$APP_PATH" ]; then + echo "Error: VibeTunnel.app with vibetunnel binary not found anywhere on the system" >&2 exit 1 fi fi diff --git a/mac/docs/RELEASE-LESSONS.md b/mac/docs/RELEASE-LESSONS.md new file mode 100644 index 00000000..05057e94 --- /dev/null +++ b/mac/docs/RELEASE-LESSONS.md @@ -0,0 +1,219 @@ +# VibeTunnel Release Lessons Learned + +This document captures important lessons learned from the VibeTunnel release process and common issues that can occur. + +## Critical Issues and Solutions + +### 1. Sparkle Signing Account Issues + +**Problem**: The `sign_update` command may use the wrong signing key from your Keychain if you have multiple EdDSA keys configured. + +**Symptoms**: +- Sparkle update verification fails +- Error messages about invalid signatures +- Updates don't appear in the app even though appcast is updated + +**Solution**: +```bash +# Always specify the account explicitly +export SPARKLE_ACCOUNT="VibeTunnel" +./scripts/release.sh stable +``` + +**Prevention**: The release script now sets `SPARKLE_ACCOUNT` environment variable automatically. + +### 2. File Location Confusion + +**Problem**: Files are not always where scripts expect them to be. + +**Key Locations**: +- **Appcast files**: Located in project root (`/vibetunnel/`), NOT in `mac/` + - `appcast.xml` + - `appcast-prerelease.xml` +- **CHANGELOG.md**: Can be in either: + - `mac/CHANGELOG.md` (preferred by release script) + - Project root `/vibetunnel/CHANGELOG.md` (common location) +- **Sparkle private key**: Usually in `mac/private/sparkle_private_key` + +**Solution**: The scripts now check multiple locations and provide clear error messages. + +### 3. Stuck DMG Volumes + +**Problem**: "Resource temporarily unavailable" errors when creating DMG. + +**Symptoms**: +- `hdiutil: create failed - Resource temporarily unavailable` +- Multiple VibeTunnel volumes visible in Finder +- DMG creation fails repeatedly + +**Solution**: +```bash +# Manually unmount all VibeTunnel volumes +for volume in /Volumes/VibeTunnel*; do + hdiutil detach "$volume" -force +done + +# Kill any stuck DMG processes +pkill -f "VibeTunnel.*\.dmg" +``` + +**Prevention**: Scripts now clean up volumes automatically before DMG creation. + +### 4. Build Number Already Exists + +**Problem**: Sparkle requires unique build numbers for each release. + +**Solution**: +1. Check existing build numbers: + ```bash + grep -E '[0-9]+' ../appcast*.xml + ``` +2. Update `mac/VibeTunnel/version.xcconfig`: + ``` + CURRENT_PROJECT_VERSION = + ``` + +### 5. Notarization Failures + +**Problem**: App notarization fails or takes too long. + +**Common Causes**: +- Missing API credentials +- Network issues +- Apple service outages +- Unsigned frameworks or binaries + +**Solution**: +```bash +# Check notarization status +xcrun notarytool history --key-id "$APP_STORE_CONNECT_KEY_ID" \ + --key "$APP_STORE_CONNECT_API_KEY_P8" \ + --issuer-id "$APP_STORE_CONNECT_ISSUER_ID" + +# Get detailed log for failed submission +xcrun notarytool log --key-id ... +``` + +### 6. GitHub Release Already Exists + +**Problem**: Tag or release already exists on GitHub. + +**Solution**: The release script now prompts you to: +1. Delete the existing release and tag +2. Cancel the release + +**Prevention**: Always pull latest changes before releasing. + +## Pre-Release Checklist + +Before running `./scripts/release.sh`: + +1. **Environment Setup**: + ```bash + # Ensure you're on main branch + git checkout main + git pull --rebase origin main + + # Check for uncommitted changes + git status + + # Set environment variables + export SPARKLE_ACCOUNT="VibeTunnel" + export APP_STORE_CONNECT_API_KEY_P8="..." + export APP_STORE_CONNECT_KEY_ID="..." + export APP_STORE_CONNECT_ISSUER_ID="..." + ``` + +2. **File Verification**: + - [ ] CHANGELOG.md exists and has entry for new version + - [ ] version.xcconfig has unique build number + - [ ] Sparkle private key exists at expected location + - [ ] No stuck DMG volumes in /Volumes/ + +3. **Clean Build**: + ```bash + ./scripts/clean.sh + rm -rf ~/Library/Developer/Xcode/DerivedData/VibeTunnel-* + ``` + +## Common Commands + +### Test Sparkle Signature +```bash +# Find sign_update binary +find . -name sign_update -type f + +# Test signing with specific account +./path/to/sign_update file.dmg -f private/sparkle_private_key -p --account VibeTunnel +``` + +### Verify Appcast URLs +```bash +# Check that appcast files are accessible +curl -I https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast.xml +curl -I https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast-prerelease.xml +``` + +### Manual Appcast Generation +```bash +# If automatic generation fails +cd mac +export SPARKLE_ACCOUNT="VibeTunnel" +./scripts/generate-appcast.sh +``` + +## Post-Release Verification + +1. **Check GitHub Release**: + - Verify assets are attached + - Check file sizes match + - Ensure release notes are formatted correctly + +2. **Test Update in App**: + - Install previous version + - Check for updates + - Verify update downloads and installs + - Check signature verification in Console.app + +3. **Monitor for Issues**: + - Watch Console.app for Sparkle errors + - Check GitHub issues for user reports + - Verify download counts on GitHub + +## Emergency Fixes + +### If Update Verification Fails +1. Regenerate appcast with correct account: + ```bash + export SPARKLE_ACCOUNT="VibeTunnel" + ./scripts/generate-appcast.sh + git add ../appcast*.xml + git commit -m "Fix appcast signatures" + git push + ``` + +2. Users may need to manually download until appcast propagates + +### If DMG is Corrupted +1. Re-download from GitHub +2. Re-sign and re-notarize: + ```bash + ./scripts/sign-and-notarize.sh --sign-and-notarize + ./scripts/notarize-dmg.sh build/VibeTunnel-*.dmg + ``` +3. Upload fixed DMG to GitHub release + +## Key Learnings + +1. **Always use explicit accounts** when dealing with signing operations +2. **Clean up resources** (volumes, processes) before operations +3. **Verify file locations** - don't assume standard paths +4. **Test the full update flow** before announcing the release +5. **Keep credentials secure** but easily accessible for scripts +6. **Document everything** - future you will thank present you + +## References + +- [Sparkle Documentation](https://sparkle-project.org/documentation/) +- [Apple Notarization Guide](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) +- [GitHub Releases API](https://docs.github.com/en/rest/releases/releases) \ No newline at end of file diff --git a/mac/docs/RELEASE.md b/mac/docs/RELEASE.md index 3d7798eb..bfd56540 100644 --- a/mac/docs/RELEASE.md +++ b/mac/docs/RELEASE.md @@ -174,8 +174,16 @@ The script will: ### Step 5: Verify Success - Check the GitHub releases page - Verify the appcast was updated correctly with proper changelog content +- **Critical**: Verify the Sparkle signature is correct: + ```bash + # Download and verify the DMG signature + curl -L -o test.dmg + sign_update test.dmg --account VibeTunnel + # Compare with appcast sparkle:edSignature + ``` - Test updating from a previous version - **Important**: Verify that the Sparkle update dialog shows the formatted changelog, not HTML tags +- Check that update installs without "improperly signed" errors ## ⚠️ Critical Requirements @@ -347,6 +355,8 @@ Edit `VibeTunnel/version.xcconfig`: - Update MARKETING_VERSION - Update CURRENT_PROJECT_VERSION (build number) +**Note**: The Xcode project file is named `VibeTunnel-Mac.xcodeproj` + ### 2. Clean and Build Universal Binary ```bash rm -rf build DerivedData @@ -390,6 +400,25 @@ git push ## 🔍 Troubleshooting +### "Update is improperly signed" Error +**Problem**: Users see "The update is improperly signed and could not be validated." + +**Cause**: The DMG was signed with the wrong Sparkle key (default instead of VibeTunnel account). + +**Quick Fix**: +```bash +# 1. Download the DMG from GitHub +curl -L -o fix.dmg + +# 2. Generate correct signature +sign_update fix.dmg --account VibeTunnel + +# 3. Update appcast-prerelease.xml with the new sparkle:edSignature +# 4. Commit and push +``` + +**Prevention**: The updated scripts now always use `--account VibeTunnel`. + ### Debug Sparkle Updates ```bash # Monitor VibeTunnel logs diff --git a/mac/scripts/build-web-frontend.sh b/mac/scripts/build-web-frontend.sh index a392a485..9b3d85b4 100755 --- a/mac/scripts/build-web-frontend.sh +++ b/mac/scripts/build-web-frontend.sh @@ -139,7 +139,12 @@ fi if [ "$BUILD_CONFIG" = "Release" ]; then echo "Release build - checking for custom Node.js..." - if [ ! -f "$CUSTOM_NODE_PATH" ]; then + # Skip custom Node.js build in CI to avoid timeout + if [ "${CI:-false}" = "true" ]; then + echo "CI environment detected - skipping custom Node.js build to avoid timeout" + echo "The app will be larger than optimal but will build within CI time limits." + npm run build + elif [ ! -f "$CUSTOM_NODE_PATH" ]; then echo "Custom Node.js not found, building it for optimal size..." echo "This will take 10-20 minutes on first run but will be cached." node build-custom-node.js --latest @@ -147,7 +152,7 @@ if [ "$BUILD_CONFIG" = "Release" ]; then CUSTOM_NODE_PATH="${CUSTOM_NODE_DIR}/out/Release/node" fi - if [ -f "$CUSTOM_NODE_PATH" ]; then + if [ "${CI:-false}" != "true" ] && [ -f "$CUSTOM_NODE_PATH" ]; then CUSTOM_NODE_VERSION=$("$CUSTOM_NODE_PATH" --version 2>/dev/null || echo "unknown") CUSTOM_NODE_SIZE=$(ls -lh "$CUSTOM_NODE_PATH" 2>/dev/null | awk '{print $5}' || echo "unknown") echo "Using custom Node.js for release build:" diff --git a/mac/scripts/create-dmg.sh b/mac/scripts/create-dmg.sh index c425f2d3..d9b0f251 100755 --- a/mac/scripts/create-dmg.sh +++ b/mac/scripts/create-dmg.sh @@ -55,6 +55,23 @@ fi echo "Creating DMG: $DMG_NAME" +# Clean up any stuck VibeTunnel volumes before starting +echo "Checking for stuck DMG volumes..." +for volume in /Volumes/VibeTunnel* "/Volumes/$DMG_VOLUME_NAME"*; do + if [ -d "$volume" ]; then + echo " Unmounting stuck volume: $volume" + hdiutil detach "$volume" -force 2>/dev/null || true + sleep 1 + fi +done + +# Also check for any DMG processes that might be stuck +if pgrep -f "VibeTunnel.*\.dmg" > /dev/null; then + echo " Found stuck DMG processes, killing them..." + pkill -f "VibeTunnel.*\.dmg" || true + sleep 2 +fi + # Create temporary directory for DMG contents DMG_TEMP="$BUILD_DIR/dmg-temp" rm -rf "$DMG_TEMP" @@ -83,6 +100,12 @@ echo "Applying custom styling to DMG..." # Mount the DMG MOUNT_POINT="/Volumes/$DMG_VOLUME_NAME" +# Ensure the mount point doesn't exist before mounting +if [ -d "$MOUNT_POINT" ]; then + echo "Mount point already exists, attempting to unmount..." + hdiutil detach "$MOUNT_POINT" -force 2>/dev/null || true + sleep 2 +fi hdiutil attach "$DMG_RW_PATH" -mountpoint "$MOUNT_POINT" -nobrowse @@ -151,16 +174,27 @@ sleep 3 osascript -e 'tell application "Finder" to close every window' -# Unmount with retry +# Unmount with retry and force echo "Unmounting DMG..." for i in {1..5}; do if hdiutil detach "$MOUNT_POINT" -quiet 2>/dev/null; then break fi echo " Retry $i/5..." + if [ $i -eq 3 ]; then + echo " Attempting force unmount..." + hdiutil detach "$MOUNT_POINT" -force 2>/dev/null || true + fi sleep 2 done +# Final check - if still mounted, force unmount +if [ -d "$MOUNT_POINT" ]; then + echo " Volume still mounted, force unmounting..." + hdiutil detach "$MOUNT_POINT" -force 2>/dev/null || true + sleep 1 +fi + # Convert to compressed read-only DMG echo "Converting to final DMG format..." hdiutil convert "$DMG_RW_PATH" -format ULMO -o "$DMG_PATH" -ov diff --git a/mac/scripts/generate-appcast.sh b/mac/scripts/generate-appcast.sh index 0d2930fd..c14cf7cf 100755 --- a/mac/scripts/generate-appcast.sh +++ b/mac/scripts/generate-appcast.sh @@ -37,8 +37,15 @@ if [[ -z "${GITHUB_USERNAME:-}" ]] || [[ -z "${GITHUB_REPO:-}" ]]; then fi fi +# Set the Sparkle account if provided via environment +SPARKLE_ACCOUNT="${SPARKLE_ACCOUNT:-}" + GITHUB_REPO_FULL="${GITHUB_USERNAME}/${GITHUB_REPO}" SPARKLE_PRIVATE_KEY_PATH="${SPARKLE_PRIVATE_KEY_PATH:-private/sparkle_private_key}" +# Try alternate location if primary doesn't exist +if [[ ! -f "$SPARKLE_PRIVATE_KEY_PATH" ]] && [[ -f "sparkle-private-ed-key.pem" ]]; then + SPARKLE_PRIVATE_KEY_PATH="sparkle-private-ed-key.pem" +fi # Verify private key exists if [ ! -f "$SPARKLE_PRIVATE_KEY_PATH" ]; then @@ -117,8 +124,14 @@ generate_signature() { exit 1 fi - # Sign using the private key file (no fallback) - local signature=$($sign_update_bin "$file_path" -f "$SPARKLE_PRIVATE_KEY_PATH" -p 2>/dev/null) + # Sign using the private key file with account if specified + local sign_cmd="$sign_update_bin \"$file_path\" -f \"$SPARKLE_PRIVATE_KEY_PATH\" -p" + if [ -n "$SPARKLE_ACCOUNT" ]; then + sign_cmd="$sign_cmd --account \"$SPARKLE_ACCOUNT\"" + echo "Using Sparkle account: $SPARKLE_ACCOUNT" >&2 + fi + + local signature=$(eval $sign_cmd 2>/dev/null) if [ -n "$signature" ] && [ "$signature" != "-----END PRIVATE KEY-----" ]; then echo "$signature" return 0 @@ -126,6 +139,11 @@ generate_signature() { echo -e "${RED}❌ Error: Failed to generate signature for $filename${NC}" >&2 echo "Please ensure the private key at $SPARKLE_PRIVATE_KEY_PATH is valid" >&2 + if [ -n "$SPARKLE_ACCOUNT" ]; then + echo "Also check that the account '$SPARKLE_ACCOUNT' is correct" >&2 + else + echo "You may need to specify SPARKLE_ACCOUNT environment variable" >&2 + fi exit 1 } @@ -325,6 +343,19 @@ EOF main() { print_info "Generating appcast files for $GITHUB_REPO_FULL" + # Check if we need to detect the Sparkle account + if [ -z "$SPARKLE_ACCOUNT" ] && command -v security >/dev/null 2>&1; then + print_info "Attempting to detect Sparkle account from Keychain..." + # Try to find EdDSA keys in the Keychain + DETECTED_ACCOUNT=$(security find-generic-password -s "https://sparkle-project.org" 2>/dev/null | grep "acct" | sed 's/.*acct"="\(.*\)"/\1/' || echo "") + if [ -n "$DETECTED_ACCOUNT" ]; then + SPARKLE_ACCOUNT="$DETECTED_ACCOUNT" + print_info "Detected Sparkle account: $SPARKLE_ACCOUNT" + else + print_warning "Could not detect Sparkle account. Using default signing." + fi + fi + # Create temporary directory local temp_dir=$(mktemp -d) trap "rm -rf $temp_dir" EXIT diff --git a/mac/scripts/preflight-check.sh b/mac/scripts/preflight-check.sh index 0f615a6f..8e9256bc 100755 --- a/mac/scripts/preflight-check.sh +++ b/mac/scripts/preflight-check.sh @@ -139,12 +139,12 @@ echo "" # 3. Check build numbers echo "📌 Build Number Validation:" USED_BUILD_NUMBERS="" -if [[ -f "$PROJECT_ROOT/appcast.xml" ]]; then - APPCAST_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/appcast.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) +if [[ -f "$PROJECT_ROOT/../appcast.xml" ]]; then + APPCAST_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/../appcast.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) USED_BUILD_NUMBERS+="$APPCAST_BUILDS" fi -if [[ -f "$PROJECT_ROOT/appcast-prerelease.xml" ]]; then - PRERELEASE_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/appcast-prerelease.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) +if [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then + PRERELEASE_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/../appcast-prerelease.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) USED_BUILD_NUMBERS+="$PRERELEASE_BUILDS" fi @@ -181,7 +181,7 @@ echo "" # Check if Xcode project uses version.xcconfig echo "📌 Xcode Project Configuration:" -XCODEPROJ="$PROJECT_ROOT/mac/VibeTunnel.xcodeproj/project.pbxproj" +XCODEPROJ="$PROJECT_ROOT/VibeTunnel-Mac.xcodeproj/project.pbxproj" if [[ -f "$XCODEPROJ" ]]; then if grep -q "version.xcconfig" "$XCODEPROJ"; then check_pass "Xcode project references version.xcconfig" @@ -304,8 +304,8 @@ echo "" # 7. Check appcast files echo "📌 Appcast Files:" -if [[ -f "$PROJECT_ROOT/appcast.xml" ]]; then - if xmllint --noout "$PROJECT_ROOT/appcast.xml" 2>/dev/null; then +if [[ -f "$PROJECT_ROOT/../appcast.xml" ]]; then + if xmllint --noout "$PROJECT_ROOT/../appcast.xml" 2>/dev/null; then check_pass "appcast.xml is valid XML" else check_fail "appcast.xml has XML errors" @@ -314,8 +314,8 @@ else check_warn "appcast.xml not found (OK if no stable releases yet)" fi -if [[ -f "$PROJECT_ROOT/appcast-prerelease.xml" ]]; then - if xmllint --noout "$PROJECT_ROOT/appcast-prerelease.xml" 2>/dev/null; then +if [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then + if xmllint --noout "$PROJECT_ROOT/../appcast-prerelease.xml" 2>/dev/null; then check_pass "appcast-prerelease.xml is valid XML" else check_fail "appcast-prerelease.xml has XML errors" diff --git a/mac/scripts/release.sh b/mac/scripts/release.sh index 1b8038c4..0612bbf8 100755 --- a/mac/scripts/release.sh +++ b/mac/scripts/release.sh @@ -113,6 +113,22 @@ echo "" # Additional strict pre-conditions before preflight check echo -e "${BLUE}🔍 Running strict pre-conditions...${NC}" +# Check if CHANGELOG.md exists in mac directory +if [[ ! -f "$PROJECT_ROOT/CHANGELOG.md" ]]; then + echo -e "${YELLOW}⚠️ Warning: CHANGELOG.md not found in mac/ directory${NC}" + echo " The release script expects CHANGELOG.md to be in the mac/ directory" + echo " You can copy it from the project root: cp ../CHANGELOG.md ." +fi + +# Clean up any stuck VibeTunnel volumes before starting +echo "🧹 Cleaning up any stuck DMG volumes..." +for volume in /Volumes/VibeTunnel*; do + if [ -d "$volume" ]; then + echo " Unmounting $volume..." + hdiutil detach "$volume" -force 2>/dev/null || true + fi +done + # Check if we're on main branch CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) if [[ "$CURRENT_BRANCH" != "main" ]]; then @@ -170,8 +186,17 @@ fi # Check if changelog file exists if [[ ! -f "$PROJECT_ROOT/CHANGELOG.md" ]]; then - echo -e "${YELLOW}⚠️ Warning: CHANGELOG.md not found${NC}" - echo " Release notes will be basic" + echo -e "${YELLOW}⚠️ Warning: CHANGELOG.md not found in mac/ directory${NC}" + echo " Looking for it in project root..." + if [[ -f "$PROJECT_ROOT/../CHANGELOG.md" ]]; then + echo " Found CHANGELOG.md in project root" + CHANGELOG_PATH="$PROJECT_ROOT/../CHANGELOG.md" + else + echo " CHANGELOG.md not found anywhere - release notes will be basic" + CHANGELOG_PATH="" + fi +else + CHANGELOG_PATH="$PROJECT_ROOT/CHANGELOG.md" fi # Check if we're up to date with origin/main @@ -247,12 +272,12 @@ fi # Verify build number hasn't been used echo "🔍 Checking build number uniqueness..." EXISTING_BUILDS="" -if [[ -f "$PROJECT_ROOT/appcast.xml" ]]; then - APPCAST_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/appcast.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) +if [[ -f "$PROJECT_ROOT/../appcast.xml" ]]; then + APPCAST_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/../appcast.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) EXISTING_BUILDS+="$APPCAST_BUILDS" fi -if [[ -f "$PROJECT_ROOT/appcast-prerelease.xml" ]]; then - PRERELEASE_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/appcast-prerelease.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) +if [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then + PRERELEASE_BUILDS=$(grep -E '[0-9]+' "$PROJECT_ROOT/../appcast-prerelease.xml" 2>/dev/null | sed 's/.*\([0-9]*\)<\/sparkle:version>.*/\1/' | tr '\n' ' ' || true) EXISTING_BUILDS+="$PRERELEASE_BUILDS" fi @@ -308,9 +333,9 @@ fi echo -e "${GREEN}✅ Version updated to: $VERSION_TO_SET${NC}" # Check if Xcode project was modified and commit if needed -if ! git diff --quiet "$PROJECT_ROOT/VibeTunnel.xcodeproj/project.pbxproj"; then +if ! git diff --quiet "$PROJECT_ROOT/VibeTunnel-Mac.xcodeproj/project.pbxproj"; then echo "📝 Committing Xcode project changes..." - git add "$PROJECT_ROOT/VibeTunnel.xcodeproj/project.pbxproj" + git add "$PROJECT_ROOT/VibeTunnel-Mac.xcodeproj/project.pbxproj" git commit -m "Update Xcode project for build $BUILD_NUMBER" echo -e "${GREEN}✅ Xcode project changes committed${NC}" fi @@ -567,14 +592,14 @@ echo "📤 Creating GitHub release..." # Generate release notes from changelog echo "📝 Generating release notes from changelog..." CHANGELOG_HTML="" -if [[ -x "$SCRIPT_DIR/changelog-to-html.sh" ]] && [[ -f "$PROJECT_ROOT/CHANGELOG.md" ]]; then +if [[ -x "$SCRIPT_DIR/changelog-to-html.sh" ]] && [[ -n "$CHANGELOG_PATH" ]] && [[ -f "$CHANGELOG_PATH" ]]; then # Extract version for changelog (remove any pre-release suffixes for lookup) CHANGELOG_VERSION="$RELEASE_VERSION" if [[ "$CHANGELOG_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+) ]]; then CHANGELOG_BASE="${BASH_REMATCH[1]}" # Try full version first, then base version - CHANGELOG_HTML=$("$SCRIPT_DIR/changelog-to-html.sh" "$CHANGELOG_VERSION" "$PROJECT_ROOT/CHANGELOG.md" 2>/dev/null || \ - "$SCRIPT_DIR/changelog-to-html.sh" "$CHANGELOG_BASE" "$PROJECT_ROOT/CHANGELOG.md" 2>/dev/null || \ + CHANGELOG_HTML=$("$SCRIPT_DIR/changelog-to-html.sh" "$CHANGELOG_VERSION" "$CHANGELOG_PATH" 2>/dev/null || \ + "$SCRIPT_DIR/changelog-to-html.sh" "$CHANGELOG_BASE" "$CHANGELOG_PATH" 2>/dev/null || \ echo "") fi fi @@ -611,15 +636,18 @@ echo -e "${BLUE}📋 Step 8/9: Updating appcast...${NC}" # Generate appcast echo "🔐 Generating appcast with EdDSA signatures..." +# Set the Sparkle account for sign_update +export SPARKLE_ACCOUNT="VibeTunnel" +echo " Using Sparkle account: $SPARKLE_ACCOUNT" "$SCRIPT_DIR/generate-appcast.sh" # Verify the appcast was updated if [[ "$RELEASE_TYPE" == "stable" ]]; then - if ! grep -q "$BUILD_NUMBER" "$PROJECT_ROOT/appcast.xml"; then + if ! grep -q "$BUILD_NUMBER" "$PROJECT_ROOT/../appcast.xml"; then echo -e "${YELLOW}⚠️ Appcast may not have been updated. Please check manually.${NC}" fi else - if ! grep -q "$BUILD_NUMBER" "$PROJECT_ROOT/appcast-prerelease.xml"; then + if ! grep -q "$BUILD_NUMBER" "$PROJECT_ROOT/../appcast-prerelease.xml"; then echo -e "${YELLOW}⚠️ Pre-release appcast may not have been updated. Please check manually.${NC}" fi fi @@ -633,8 +661,17 @@ echo "📤 Committing and pushing changes..." # Add version.xcconfig changes git add "$VERSION_CONFIG" 2>/dev/null || true -# Add appcast files -git add "$PROJECT_ROOT/appcast.xml" "$PROJECT_ROOT/appcast-prerelease.xml" 2>/dev/null || true +# Add appcast files (they're in project root, not mac/) +if [[ -f "$PROJECT_ROOT/../appcast.xml" ]]; then + git add "$PROJECT_ROOT/../appcast.xml" 2>/dev/null || true +else + echo -e "${YELLOW}⚠️ Warning: appcast.xml not found in project root${NC}" +fi +if [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then + git add "$PROJECT_ROOT/../appcast-prerelease.xml" 2>/dev/null || true +else + echo -e "${YELLOW}⚠️ Warning: appcast-prerelease.xml not found in project root${NC}" +fi if ! git diff --cached --quiet; then git commit -m "Update appcast and version for $RELEASE_VERSION" diff --git a/mac/scripts/verify-appcast.sh b/mac/scripts/verify-appcast.sh index 77b80c85..3fff3e09 100755 --- a/mac/scripts/verify-appcast.sh +++ b/mac/scripts/verify-appcast.sh @@ -134,26 +134,26 @@ validate_appcast() { } # Validate both appcast files -validate_appcast "$PROJECT_ROOT/appcast.xml" "Stable appcast" +validate_appcast "$PROJECT_ROOT/../appcast.xml" "Stable appcast" echo "" -validate_appcast "$PROJECT_ROOT/appcast-prerelease.xml" "Pre-release appcast" +validate_appcast "$PROJECT_ROOT/../appcast-prerelease.xml" "Pre-release appcast" # Cross-validation between appcasts echo "" echo "📌 Cross-Validation:" -if [[ -f "$PROJECT_ROOT/appcast.xml" ]] && [[ -f "$PROJECT_ROOT/appcast-prerelease.xml" ]]; then +if [[ -f "$PROJECT_ROOT/../appcast.xml" ]] && [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then # Get all build numbers from both files ALL_BUILDS=() - if [[ -f "$PROJECT_ROOT/appcast.xml" ]]; then + if [[ -f "$PROJECT_ROOT/../appcast.xml" ]]; then while IFS= read -r build; do ALL_BUILDS+=("$build") - done < <(grep -o '[0-9]*' "$PROJECT_ROOT/appcast.xml" | sed 's/<[^>]*>//g') + done < <(grep -o '[0-9]*' "$PROJECT_ROOT/../appcast.xml" | sed 's/<[^>]*>//g') fi - if [[ -f "$PROJECT_ROOT/appcast-prerelease.xml" ]]; then + if [[ -f "$PROJECT_ROOT/../appcast-prerelease.xml" ]]; then while IFS= read -r build; do ALL_BUILDS+=("$build") - done < <(grep -o '[0-9]*' "$PROJECT_ROOT/appcast-prerelease.xml" | sed 's/<[^>]*>//g') + done < <(grep -o '[0-9]*' "$PROJECT_ROOT/../appcast-prerelease.xml" | sed 's/<[^>]*>//g') fi # Check for duplicates across files diff --git a/tauri/.gitignore b/tauri/.gitignore new file mode 100644 index 00000000..837db85c --- /dev/null +++ b/tauri/.gitignore @@ -0,0 +1,52 @@ +# Dependencies +node_modules/ +dist/ +dist-ssr/ +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Tauri +src-tauri/target/ +src-tauri/Cargo.lock + +# Frontend build +public/bundle/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# OS +.DS_Store +Thumbs.db + +# Env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage/ +.nyc_output/ + +# Temporary files +*.tmp +*.temp +.cache/ \ No newline at end of file diff --git a/tauri/README.md b/tauri/README.md new file mode 100644 index 00000000..cc65a370 --- /dev/null +++ b/tauri/README.md @@ -0,0 +1,210 @@ +# VibeTunnel Tauri App + +This directory contains the Tauri-based desktop application for VibeTunnel. Tauri is a framework for building smaller, faster, and more secure desktop applications with a web frontend. + +## What is Tauri? + +Tauri is a toolkit that helps developers make applications for major desktop platforms using virtually any frontend framework. Unlike Electron, Tauri: +- Uses the system's native webview instead of bundling Chromium +- Results in much smaller app sizes (typically 10-100x smaller) +- Has better performance and lower memory usage +- Provides better security through a smaller attack surface + +## Architecture + +The VibeTunnel Tauri app uses a subprocess architecture similar to the Mac app: + +- **Frontend**: HTML/CSS/JavaScript served from the `public/` directory +- **Backend**: Rust code in `src-tauri/` that manages the Node.js subprocess +- **Node.js Server**: The `vibetunnel` executable spawned as a subprocess handles all terminal operations +- **IPC Bridge**: Commands defined in Rust that proxy to the Node.js server API + +### Key Changes from Embedded Server +Instead of embedding terminal management in Rust, the Tauri app: +1. Spawns the same `vibetunnel` Node.js executable used by the Mac app +2. Proxies terminal commands to the Node.js server via HTTP API +3. Monitors the subprocess health and handles crashes +4. Bundles the Node.js executable and its dependencies as resources + +## Prerequisites + +Before you begin, ensure you have the following installed: +- [Node.js](https://nodejs.org/) (v18 or later) +- [Rust](https://www.rust-lang.org/tools/install) (latest stable) +- Platform-specific dependencies: + - **macOS**: Xcode Command Line Tools + - **Linux**: `webkit2gtk`, `libgtk-3-dev`, `libappindicator3-dev` + - **Windows**: WebView2 (comes with Windows 11/10) + +## Getting Started + +### Installation + +1. Clone the repository and navigate to the Tauri directory: +```bash +cd /path/to/vibetunnel3/tauri +``` + +2. Install dependencies: +```bash +npm install +``` + +### Development + +To run the app in development mode with hot-reloading: +```bash +./dev.sh +# or manually: +cd ../web && npm run build # Build vibetunnel executable first +cd ../tauri && npm run tauri dev +``` + +This will: +- Build the Node.js server executable +- Start the Rust backend with file watching +- Spawn the Node.js subprocess +- Serve the frontend with hot-reloading +- Open the app window automatically +- Show debug output in the terminal + +### Building + +To build the app for production: +```bash +./build.sh +# or manually: +cd ../web && npm run build # Build vibetunnel executable first +cd ../tauri && npm run tauri build +``` + +This creates an optimized build in `src-tauri/target/release/bundle/`: +- **macOS**: `.app` bundle and `.dmg` installer +- **Linux**: `.deb` and `.AppImage` packages +- **Windows**: `.msi` and `.exe` installers + +The build includes: +- The `vibetunnel` Node.js executable +- Native modules (`pty.node`, `spawn-helper`) +- Web static assets from `web/public/` + +## Project Structure + +``` +tauri/ +├── public/ # Frontend files (HTML, CSS, JS) +│ ├── index.html # Main app window +│ ├── settings.html # Settings window +│ └── welcome.html # Welcome/onboarding window +├── src-tauri/ # Rust backend +│ ├── src/ # Rust source code +│ │ ├── main.rs # App entry point +│ │ ├── commands.rs # Tauri commands (IPC) +│ │ ├── backend_manager.rs # Node.js subprocess management +│ │ ├── api_client.rs # HTTP client for Node.js API +│ │ └── ... # Other modules +│ ├── Cargo.toml # Rust dependencies +│ └── tauri.conf.json # Tauri configuration +├── build.sh # Production build script +├── dev.sh # Development run script +├── package.json # Node.js dependencies +└── README.md # This file +``` + +## Key Features + +The Tauri app provides: +- **Native Terminal Integration**: Spawn and manage terminal sessions +- **System Tray Support**: Menu bar icon with quick actions +- **Multi-Window Management**: Main, settings, and welcome windows +- **Secure IPC**: Commands for frontend-backend communication +- **Platform Integration**: Native menus, notifications, and file dialogs +- **Single Instance**: Prevents multiple app instances +- **Auto-Updates**: Built-in update mechanism + +## Development Tips + +### Adding New Commands + +To add a new command that the frontend can call: + +1. Define the command in `src-tauri/src/commands.rs`: +```rust +#[tauri::command] +async fn my_command(param: String) -> Result { + Ok(format!("Hello, {}!", param)) +} +``` + +2. Register it in `src-tauri/src/main.rs`: +```rust +.invoke_handler(tauri::generate_handler![ + // ... existing commands + my_command, +]) +``` + +3. Call it from the frontend: +```javascript +const { invoke } = window.__TAURI__.tauri; +const result = await invoke('my_command', { param: 'World' }); +``` + +### Debugging + +- **Frontend**: Use browser DevTools (right-click → Inspect in dev mode) +- **Backend**: Check terminal output or use `println!` debugging +- **IPC Issues**: Enable Tauri logging with `RUST_LOG=debug npm run tauri dev` + +### Hot Keys + +While in development mode: +- `Cmd+R` / `Ctrl+R`: Reload the frontend +- `Cmd+Q` / `Ctrl+Q`: Quit the app + +## Configuration + +The main configuration file is `src-tauri/tauri.conf.json`, which controls: +- App metadata (name, version, identifier) +- Window settings (size, position, decorations) +- Build settings (icons, resources) +- Security policies + +## Troubleshooting + +### Common Issues + +1. **Build fails with "cannot find crate"** + - Run `cd src-tauri && cargo update` + +2. **App doesn't start in dev mode** + - Check that port 1420 is available + - Try `npm run tauri dev -- --port 3000` + +3. **Permission errors on macOS** + - Grant necessary permissions in System Preferences + - The app will prompt for required permissions on first launch + +### Logs + +- Development logs appear in the terminal +- Production logs on macOS: `~/Library/Logs/VibeTunnel/` + +## Contributing + +When contributing to the Tauri app: +1. Follow the existing code style +2. Test on all target platforms if possible +3. Update this README if adding new features +4. Run `cargo fmt` in `src-tauri/` before committing + +## Resources + +- [Tauri Documentation](https://tauri.app/v1/guides/) +- [Tauri API Reference](https://tauri.app/v1/api/js/) +- [Rust Documentation](https://doc.rust-lang.org/book/) +- [VibeTunnel Documentation](https://vibetunnel.sh) + +## License + +See the main project LICENSE file. \ No newline at end of file diff --git a/tauri/build.sh b/tauri/build.sh new file mode 100755 index 00000000..b17e7ced --- /dev/null +++ b/tauri/build.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +echo "Building VibeTunnel Tauri App..." + +# Change to web directory and build the Node.js server +echo "Building Node.js server..." +cd ../web + +# Install dependencies if needed +if [ ! -d "node_modules" ]; then + echo "Installing web dependencies..." + npm install +fi + +# Build the web project (creates vibetunnel executable) +echo "Building vibetunnel executable..." +npm run build + +# Check that required files exist +if [ ! -f "native/vibetunnel" ]; then + echo "Error: vibetunnel executable not found at web/native/vibetunnel" + exit 1 +fi + +if [ ! -f "native/pty.node" ]; then + echo "Error: pty.node not found at web/native/pty.node" + exit 1 +fi + +if [ ! -f "native/spawn-helper" ]; then + echo "Error: spawn-helper not found at web/native/spawn-helper" + exit 1 +fi + +echo "Node.js server built successfully!" + +# Change back to tauri directory +cd ../tauri + +# Build Tauri app +echo "Building Tauri app..." +cargo tauri build + +echo "Build complete!" \ No newline at end of file diff --git a/tauri/dev.sh b/tauri/dev.sh new file mode 100755 index 00000000..cf2538ff --- /dev/null +++ b/tauri/dev.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +echo "Starting VibeTunnel Tauri App in development mode..." + +# Change to web directory and build the Node.js server +echo "Building Node.js server..." +cd ../web + +# Install dependencies if needed +if [ ! -d "node_modules" ]; then + echo "Installing web dependencies..." + npm install +fi + +# Build the web project (creates vibetunnel executable) +echo "Building vibetunnel executable..." +npm run build + +# Check that required files exist +if [ ! -f "native/vibetunnel" ]; then + echo "Error: vibetunnel executable not found at web/native/vibetunnel" + exit 1 +fi + +echo "Node.js server built successfully!" + +# Change back to tauri directory +cd ../tauri + +# Run Tauri in dev mode +echo "Starting Tauri app in development mode..." +cargo tauri dev \ No newline at end of file diff --git a/tauri/package-lock.json b/tauri/package-lock.json new file mode 100644 index 00000000..30888778 --- /dev/null +++ b/tauri/package-lock.json @@ -0,0 +1,233 @@ +{ + "name": "vibetunnel-tauri", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vibetunnel-tauri", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@tauri-apps/cli": "^2.0.0-rc.18" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.5.0.tgz", + "integrity": "sha512-rAtHqG0Gh/IWLjN2zTf3nZqYqbo81oMbqop56rGTjrlWk9pTTAjkqOjSL9XQLIMZ3RbeVjveCqqCA0s8RnLdMg==", + "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.5.0", + "@tauri-apps/cli-darwin-x64": "2.5.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.5.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.5.0", + "@tauri-apps/cli-linux-arm64-musl": "2.5.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.5.0", + "@tauri-apps/cli-linux-x64-gnu": "2.5.0", + "@tauri-apps/cli-linux-x64-musl": "2.5.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.5.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.5.0", + "@tauri-apps/cli-win32-x64-msvc": "2.5.0" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-VuVAeTFq86dfpoBDNYAdtQVLbP0+2EKCHIIhkaxjeoPARR0sLpFHz2zs0PcFU76e+KAaxtEtAJAXGNUc8E1PzQ==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.5.0.tgz", + "integrity": "sha512-hUF01sC06cZVa8+I0/VtsHOk9BbO75rd+YdtHJ48xTdcYaQ5QIwL4yZz9OR1AKBTaUYhBam8UX9Pvd5V2/4Dpw==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.5.0.tgz", + "integrity": "sha512-LQKqttsK252LlqYyX8R02MinUsfFcy3+NZiJwHFgi5Y3+ZUIAED9cSxJkyNtuY5KMnR4RlpgWyLv4P6akN1xhg==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.5.0.tgz", + "integrity": "sha512-mTQufsPcpdHg5RW0zypazMo4L55EfeE5snTzrPqbLX4yCK2qalN7+rnP8O8GT06xhp6ElSP/Ku1M2MR297SByQ==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-rQO1HhRUQqyEaal5dUVOQruTRda/TD36s9kv1hTxZiFuSq3558lsTjAcUEnMAtBcBkps20sbyTJNMT0AwYIk8Q==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.5.0.tgz", + "integrity": "sha512-7oS18FN46yDxyw1zX/AxhLAd7T3GrLj3Ai6s8hZKd9qFVzrAn36ESL7d3G05s8wEtsJf26qjXnVF4qleS3dYsA==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.5.0.tgz", + "integrity": "sha512-SG5sFNL7VMmDBdIg3nO3EzNRT306HsiEQ0N90ILe3ZABYAVoPDO/ttpCO37ApLInTzrq/DLN+gOlC/mgZvLw1w==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-QXDM8zp/6v05PNWju5ELsVwF0VH1n6b5pk2E6W/jFbbiwz80Vs1lACl9pv5kEHkrxBj+aWU/03JzGuIj2g3SkQ==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.5.0.tgz", + "integrity": "sha512-pFSHFK6b+o9y4Un8w0gGLwVyFTZaC3P0kQ7umRt/BLDkzD5RnQ4vBM7CF8BCU5nkwmEBUCZd7Wt3TWZxe41o6Q==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.5.0.tgz", + "integrity": "sha512-EArv1IaRlogdLAQyGlKmEqZqm5RfHCUMhJoedWu7GtdbOMUfSAz6FMX2boE1PtEmNO4An+g188flLeVErrxEKg==", + "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.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.5.0.tgz", + "integrity": "sha512-lj43EFYbnAta8pd9JnUq87o+xRUR0odz+4rixBtTUwUgdRdwQ2V9CzFtsMu6FQKpFQ6mujRK6P1IEwhL6ADRsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/tauri/package.json b/tauri/package.json new file mode 100644 index 00000000..3b857b39 --- /dev/null +++ b/tauri/package.json @@ -0,0 +1,21 @@ +{ + "name": "vibetunnel-tauri", + "version": "1.0.0", + "description": "Tauri system tray app for VibeTunnel terminal multiplexer", + "scripts": { + "tauri": "tauri", + "tauri:dev": "tauri dev", + "tauri:build": "tauri build" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.0.0-rc.18" + }, + "keywords": [ + "terminal", + "multiplexer", + "tauri", + "system-tray" + ], + "author": "", + "license": "MIT" +} \ No newline at end of file diff --git a/tauri/public/icon.png b/tauri/public/icon.png new file mode 100644 index 00000000..1a5ee251 Binary files /dev/null and b/tauri/public/icon.png differ diff --git a/tauri/public/index.html b/tauri/public/index.html new file mode 100644 index 00000000..045d4be6 --- /dev/null +++ b/tauri/public/index.html @@ -0,0 +1,266 @@ + + + + + + VibeTunnel + + + +
+ VibeTunnel +

VibeTunnel

+

Turn any browser into your terminal. Command your agents on the go.

+ +
+ + Checking server status... +
+ +
+ + + +
+ +
+
💡 VibeTunnel runs in your system tray
+
🖱️ Click the tray icon to access quick actions
+
⌨️ Use the vt command to create terminal sessions
+
+
+ + + + \ No newline at end of file diff --git a/tauri/public/server-console.html b/tauri/public/server-console.html new file mode 100644 index 00000000..6ee0dd06 --- /dev/null +++ b/tauri/public/server-console.html @@ -0,0 +1,634 @@ + + + + + + Server Console - VibeTunnel + + + +
+
+
+ Server Console + Port 4020 +
+
+ + + + +
+
+ +
+ +
+ + + + + +
+
+ +
+
+
+
+ Connecting to server... +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/tauri/public/session-detail.html b/tauri/public/session-detail.html new file mode 100644 index 00000000..976447ca --- /dev/null +++ b/tauri/public/session-detail.html @@ -0,0 +1,364 @@ + + + + + + Session Details - VibeTunnel + + + +
+
Loading session details...
+ + +
+ + + + \ No newline at end of file diff --git a/tauri/public/settings.html b/tauri/public/settings.html new file mode 100644 index 00000000..23a78b2e --- /dev/null +++ b/tauri/public/settings.html @@ -0,0 +1,1516 @@ + + + + + + VibeTunnel Preferences + + + +
+ + +
+ +
+

General

+ +
+
+

Startup

+ + +
+ +
+

Appearance

+ + +
+ +
+

Terminal

+ + +
+ +
+

Updates

+ +
+
+ Current Version: + 1.0.0 +
+
+ Status: + Up to date +
+
+ +
+
+
+ + +
+

Dashboard

+ +
+
+

Server Configuration

+ + +
+ +
+

Access Control

+ + +
+ +
+

Security

+ + +
+ + +
+
+ + +
+

Advanced

+ +
+
+

Session Management

+ + +
+ + + +
+

Developer

+ + +
+ + +
+

Maintenance

+
+ + + + +
+
+
+
+ + +
+
+ VibeTunnel +

VibeTunnel

+

Version 1.0.0

+

Turn any browser into your terminal

+ +
+
+ Platform + - +
+
+ Tauri + - +
+
+ WebView + - +
+
+ +
+ + + +
+ +
+

Built with Tauri, Rust, and TypeScript

+

Powered by xterm.js and Axum

+
+

Brought to you by

+
+ @badlogic + + @mitsuhiko + + @steipete +
+
+ +

View on GitHub

+
+
+
+ + +
+

Debug

+ +
+
+

Server Status

+
+
+ Status + - +
+
+ Port + - +
+
+ URL + - +
+
+ Sessions + 0 +
+
+ +
+ +
+

Developer Tools

+
+ + + + + +
+
+ +
+

System Paths

+
+
+ App Dir + - +
+
+ Config + - +
+
+ Data + - +
+
+ Cache + - +
+
+ Logs + - +
+
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/tauri/public/welcome.html b/tauri/public/welcome.html new file mode 100644 index 00000000..877c5290 --- /dev/null +++ b/tauri/public/welcome.html @@ -0,0 +1,638 @@ + + + + + + Welcome to VibeTunnel + + + +
+
+ +
+ VibeTunnel +
+

Welcome to VibeTunnel

+

Turn any browser into your terminal. Command your agents on the go.

+

+ You'll be quickly guided through the basics of VibeTunnel.
+ This screen can always be opened from the settings. +

+
+
+ + +
+ VibeTunnel +
+

Install the VT Command

+

The vt command lets you quickly create terminal sessions

+
+ $ vt
+ # Creates a new terminal session in your browser +
+
+ +
+

+
+
+ + +
+ VibeTunnel +
+

Grant Permissions

+

VibeTunnel needs permissions to function properly

+
+
+ + + + Terminal Automation - To launch terminal windows +
+
+ + + + Accessibility - To control terminal applications +
+
+
+ +
+
+
+ + +
+ VibeTunnel +
+

Select Your Terminal

+

Choose your preferred terminal emulator

+
+ +
+
+ +
+
+
+ + +
+ VibeTunnel +
+

Protect Your Dashboard

+

Security is important when accessing terminals remotely

+

+ We recommend setting a password for your dashboard,
+ especially if you plan to access it from outside your local network. +

+
+ + +
+
+
+ + +
+ VibeTunnel +
+

Access Your Dashboard

+

+ To access your terminals from any device, create a tunnel from your device.

+ This can be done via ngrok in settings or Tailscale (recommended). +

+
+ + +
+
+

Made with ❤️ by

+

+ @badlogic, + @mitsuhiko & + @steipete +

+
+
+
+ + +
+ VibeTunnel +
+

You're All Set!

+

VibeTunnel is now running in your system tray

+

+ Click the VibeTunnel icon in your system tray to access settings,
+ open the dashboard, or manage your terminal sessions. +

+
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/tauri/src-tauri/.cargo/config.toml b/tauri/src-tauri/.cargo/config.toml new file mode 100644 index 00000000..cbfb4196 --- /dev/null +++ b/tauri/src-tauri/.cargo/config.toml @@ -0,0 +1,15 @@ +[target.'cfg(all())'] +rustflags = [ + "-W", "clippy::all", + "-W", "clippy::pedantic", + "-W", "clippy::nursery", + "-W", "clippy::cargo", + "-A", "clippy::module_name_repetitions", + "-A", "clippy::must_use_candidate", + "-A", "clippy::missing_errors_doc", + "-A", "clippy::missing_panics_doc", + "-A", "clippy::similar_names", + "-A", "clippy::too_many_lines", + "-A", "clippy::cargo_common_metadata", + "-A", "clippy::multiple_crate_versions", +] \ No newline at end of file diff --git a/tauri/src-tauri/Cargo.toml b/tauri/src-tauri/Cargo.toml new file mode 100644 index 00000000..601b28d6 --- /dev/null +++ b/tauri/src-tauri/Cargo.toml @@ -0,0 +1,107 @@ +[package] +name = "vibetunnel" +version = "0.1.0" +description = "VibeTunnel - Cross-platform terminal session manager" +authors = ["VibeTunnel Team"] +edition = "2021" + +[package.metadata.bundle] +identifier = "com.vibetunnel.app" +copyright = "Copyright © 2024 VibeTunnel Team" +category = "DeveloperTool" +short_description = "Terminal session manager with remote access" +long_description = "VibeTunnel is a powerful terminal session manager that allows you to create, manage, and share terminal sessions. Features include multiple concurrent sessions, remote access capabilities, and a modern web-based interface." + +[lib] +name = "tauri_lib" +crate-type = ["lib", "cdylib", "staticlib"] + +[build-dependencies] +tauri-build = { version = "2.0.3", features = [] } + +[dependencies] +tauri = { version = "2.1.1", features = ["unstable", "devtools", "image-png", "image-ico", "tray-icon"] } +tauri-plugin-shell = "2.1.0" +tauri-plugin-dialog = "2.0.3" +tauri-plugin-process = "2.0.1" +tauri-plugin-fs = "2.0.3" +tauri-plugin-http = "2.0.3" +tauri-plugin-notification = "2.0.1" +tauri-plugin-updater = "2.0.2" +tauri-plugin-window-state = "2.0.1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["full"] } +uuid = { version = "1", features = ["v4", "serde"] } +chrono = { version = "0.4", features = ["serde"] } + +# Terminal handling +portable-pty = "0.8" +bytes = "1" +futures = "0.3" + +# WebSocket server +tokio-tungstenite = "0.24" +tungstenite = "0.24" + +# SSE streaming +async-stream = "0.3" +tokio-stream = "0.1" + +# HTTP server +axum = { version = "0.7", features = ["ws"] } +tower = "0.5" +tower-http = { version = "0.6", features = ["fs", "cors"] } + +# Settings and storage +directories = "5" +toml = "0.8" + +# Utilities +open = "5" + +# File system +dirs = "5" + +# Logging +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# Auto-launch +auto-launch = "0.5" + +# System info +whoami = "1" +hostname = "0.4" + +# ngrok integration and API client +which = "7" +reqwest = { version = "0.12", features = ["json", "blocking"] } + +# Authentication +base64 = "0.22" +sha2 = "0.10" + +# Keychain/Credential Storage +keyring = "3" + +# Debug features +num_cpus = "1" + +# Network utilities +[target.'cfg(unix)'.dependencies] +nix = { version = "0.27", features = ["net", "signal", "process"] } + +[target.'cfg(windows)'.dependencies] +ipconfig = "0.3" +windows = { version = "0.58", features = ["Win32_Foundation", "Win32_Security", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging"] } + +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +tauri-plugin-single-instance = "2.0.1" + +[profile.release] +panic = "abort" +codegen-units = 1 +lto = true +opt-level = "s" +strip = true diff --git a/tauri/src-tauri/README.md b/tauri/src-tauri/README.md new file mode 100644 index 00000000..fdbfb263 --- /dev/null +++ b/tauri/src-tauri/README.md @@ -0,0 +1,59 @@ +# VibeTunnel Tauri App + +This is a cross-platform version of VibeTunnel built with Tauri v2. + +## Architecture + +The Tauri app provides: +- System tray/menu bar integration +- Native window management +- Cross-platform terminal PTY support (to be implemented) +- Secure IPC between frontend and backend + +## Development + +### Prerequisites + +- Rust 1.70+ +- Node.js 18+ +- Platform-specific dependencies: + - **macOS**: Xcode Command Line Tools + - **Linux**: `webkit2gtk-4.1`, `libayatana-appindicator3-dev` + - **Windows**: WebView2 (usually pre-installed on Windows 10/11) + +### Running in Development + +1. Start the Node.js server (in the web directory): + ```bash + npm run dev + ``` + +2. In another terminal, run the Tauri app: + ```bash + npm run tauri:dev + ``` + +### Building for Production + +```bash +npm run tauri:build +``` + +This will create platform-specific binaries in `src-tauri/target/release/bundle/`. + +## Features + +- **Menu Bar App**: Runs as a system tray application +- **Web UI**: Uses the existing VibeTunnel web interface +- **Native Integration**: Platform-specific features through Tauri APIs +- **Auto-updater**: Built-in update mechanism +- **Single Instance**: Prevents multiple instances from running + +## TODO + +1. Implement native PTY support using cross-platform Rust libraries +2. Add platform-specific terminal launching +3. Implement file system access for session recordings +4. Add native notifications +5. Implement keyboard shortcuts +6. Add auto-launch on startup \ No newline at end of file diff --git a/tauri/src-tauri/build.rs b/tauri/src-tauri/build.rs new file mode 100644 index 00000000..261851f6 --- /dev/null +++ b/tauri/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build(); +} diff --git a/tauri/src-tauri/capabilities/all-windows.json b/tauri/src-tauri/capabilities/all-windows.json new file mode 100644 index 00000000..81720929 --- /dev/null +++ b/tauri/src-tauri/capabilities/all-windows.json @@ -0,0 +1,50 @@ +{ + "$schema": "../gen/schemas/capabilities.json", + "identifier": "all-windows", + "description": "Capability for all application windows", + "local": true, + "windows": ["main", "settings", "welcome", "server-console", "api-testing"], + "permissions": [ + "core:default", + "core:window:default", + "core:window:allow-create", + "core:window:allow-close", + "core:window:allow-minimize", + "core:window:allow-maximize", + "core:window:allow-start-dragging", + "core:window:allow-set-title", + "core:app:default", + "core:path:default", + "core:event:default", + "core:webview:default", + "shell:default", + "shell:allow-open", + "dialog:default", + "fs:default", + "fs:allow-read-file", + "fs:allow-write-file", + "fs:allow-read-dir", + "fs:allow-copy-file", + "fs:allow-mkdir", + "fs:allow-remove", + "fs:allow-rename", + "fs:allow-exists", + "fs:scope-appdata-recursive", + "fs:scope-resource-recursive", + "fs:scope-temp-recursive", + "fs:allow-home-read-recursive", + "fs:allow-home-write-recursive", + "process:default", + "process:allow-exit", + "process:allow-restart", + "http:default", + "http:allow-fetch", + "notification:default", + "notification:allow-is-permission-granted", + "notification:allow-request-permission", + "notification:allow-notify", + "window-state:allow-restore-state", + "window-state:allow-save-window-state", + "updater:default" + ] +} \ No newline at end of file diff --git a/tauri/src-tauri/capabilities/default.json b/tauri/src-tauri/capabilities/default.json new file mode 100644 index 00000000..15365e91 --- /dev/null +++ b/tauri/src-tauri/capabilities/default.json @@ -0,0 +1,50 @@ +{ + "$schema": "../gen/schemas/capabilities.json", + "identifier": "default", + "description": "Default capability for the application", + "local": true, + "windows": ["main"], + "permissions": [ + "core:default", + "core:window:default", + "core:window:allow-create", + "core:window:allow-close", + "core:window:allow-minimize", + "core:window:allow-maximize", + "core:window:allow-start-dragging", + "core:window:allow-set-title", + "core:app:default", + "core:path:default", + "core:event:default", + "core:webview:default", + "shell:default", + "shell:allow-open", + "dialog:default", + "fs:default", + "fs:allow-read-file", + "fs:allow-write-file", + "fs:allow-read-dir", + "fs:allow-copy-file", + "fs:allow-mkdir", + "fs:allow-remove", + "fs:allow-rename", + "fs:allow-exists", + "fs:scope-appdata-recursive", + "fs:scope-resource-recursive", + "fs:scope-temp-recursive", + "fs:allow-home-read-recursive", + "fs:allow-home-write-recursive", + "process:default", + "process:allow-exit", + "process:allow-restart", + "http:default", + "http:allow-fetch", + "notification:default", + "notification:allow-is-permission-granted", + "notification:allow-request-permission", + "notification:allow-notify", + "window-state:allow-restore-state", + "window-state:allow-save-window-state", + "updater:default" + ] +} \ No newline at end of file diff --git a/tauri/src-tauri/capabilities/settings.json b/tauri/src-tauri/capabilities/settings.json new file mode 100644 index 00000000..10ff7094 --- /dev/null +++ b/tauri/src-tauri/capabilities/settings.json @@ -0,0 +1,49 @@ +{ + "$schema": "../gen/schemas/capabilities.json", + "identifier": "settings", + "description": "Capability for the settings window", + "local": true, + "windows": ["settings"], + "permissions": [ + "core:default", + "core:window:default", + "core:window:allow-close", + "core:window:allow-minimize", + "core:window:allow-maximize", + "core:window:allow-start-dragging", + "core:window:allow-set-title", + "core:app:default", + "core:path:default", + "core:event:default", + "core:webview:default", + "shell:default", + "shell:allow-open", + "dialog:default", + "fs:default", + "fs:allow-read-file", + "fs:allow-write-file", + "fs:allow-read-dir", + "fs:allow-copy-file", + "fs:allow-mkdir", + "fs:allow-remove", + "fs:allow-rename", + "fs:allow-exists", + "fs:scope-appdata-recursive", + "fs:scope-resource-recursive", + "fs:scope-temp-recursive", + "fs:allow-home-read-recursive", + "fs:allow-home-write-recursive", + "process:default", + "process:allow-exit", + "process:allow-restart", + "http:default", + "http:allow-fetch", + "notification:default", + "notification:allow-is-permission-granted", + "notification:allow-request-permission", + "notification:allow-notify", + "window-state:allow-restore-state", + "window-state:allow-save-window-state", + "updater:default" + ] +} \ No newline at end of file diff --git a/tauri/src-tauri/clippy.toml b/tauri/src-tauri/clippy.toml new file mode 100644 index 00000000..7dfd7bef --- /dev/null +++ b/tauri/src-tauri/clippy.toml @@ -0,0 +1,8 @@ +# Clippy configuration +cognitive-complexity-threshold = 30 +too-many-arguments-threshold = 8 +type-complexity-threshold = 250 +single-char-binding-names-threshold = 4 +too-many-lines-threshold = 400 +array-size-threshold = 512 +enum-variant-name-threshold = 3 \ No newline at end of file diff --git a/tauri/src-tauri/entitlements.plist b/tauri/src-tauri/entitlements.plist new file mode 100644 index 00000000..fb8f88af --- /dev/null +++ b/tauri/src-tauri/entitlements.plist @@ -0,0 +1,18 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.device.microphone + + com.apple.security.device.camera + + + \ No newline at end of file diff --git a/tauri/src-tauri/gen/schemas/acl-manifests.json b/tauri/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 00000000..c0f9ac58 --- /dev/null +++ b/tauri/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"]},"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-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-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-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-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-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-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-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-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-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-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-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-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"fs":{"default_permission":{"identifier":"default","description":"This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n","permissions":["create-app-specific-dirs","read-app-specific-dirs-recursive","deny-default"]},"permissions":{"allow-copy-file":{"identifier":"allow-copy-file","description":"Enables the copy_file command without any pre-configured scope.","commands":{"allow":["copy_file"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-exists":{"identifier":"allow-exists","description":"Enables the exists command without any pre-configured scope.","commands":{"allow":["exists"],"deny":[]}},"allow-fstat":{"identifier":"allow-fstat","description":"Enables the fstat command without any pre-configured scope.","commands":{"allow":["fstat"],"deny":[]}},"allow-ftruncate":{"identifier":"allow-ftruncate","description":"Enables the ftruncate command without any pre-configured scope.","commands":{"allow":["ftruncate"],"deny":[]}},"allow-lstat":{"identifier":"allow-lstat","description":"Enables the lstat command without any pre-configured scope.","commands":{"allow":["lstat"],"deny":[]}},"allow-mkdir":{"identifier":"allow-mkdir","description":"Enables the mkdir command without any pre-configured scope.","commands":{"allow":["mkdir"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-read":{"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]}},"allow-read-dir":{"identifier":"allow-read-dir","description":"Enables the read_dir command without any pre-configured scope.","commands":{"allow":["read_dir"],"deny":[]}},"allow-read-file":{"identifier":"allow-read-file","description":"Enables the read_file command without any pre-configured scope.","commands":{"allow":["read_file"],"deny":[]}},"allow-read-text-file":{"identifier":"allow-read-text-file","description":"Enables the read_text_file command without any pre-configured scope.","commands":{"allow":["read_text_file"],"deny":[]}},"allow-read-text-file-lines":{"identifier":"allow-read-text-file-lines","description":"Enables the read_text_file_lines command without any pre-configured scope.","commands":{"allow":["read_text_file_lines","read_text_file_lines_next"],"deny":[]}},"allow-read-text-file-lines-next":{"identifier":"allow-read-text-file-lines-next","description":"Enables the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":["read_text_file_lines_next"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-rename":{"identifier":"allow-rename","description":"Enables the rename command without any pre-configured scope.","commands":{"allow":["rename"],"deny":[]}},"allow-seek":{"identifier":"allow-seek","description":"Enables the seek command without any pre-configured scope.","commands":{"allow":["seek"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"allow-stat":{"identifier":"allow-stat","description":"Enables the stat command without any pre-configured scope.","commands":{"allow":["stat"],"deny":[]}},"allow-truncate":{"identifier":"allow-truncate","description":"Enables the truncate command without any pre-configured scope.","commands":{"allow":["truncate"],"deny":[]}},"allow-unwatch":{"identifier":"allow-unwatch","description":"Enables the unwatch command without any pre-configured scope.","commands":{"allow":["unwatch"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"allow-write":{"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]}},"allow-write-file":{"identifier":"allow-write-file","description":"Enables the write_file command without any pre-configured scope.","commands":{"allow":["write_file","open","write"],"deny":[]}},"allow-write-text-file":{"identifier":"allow-write-text-file","description":"Enables the write_text_file command without any pre-configured scope.","commands":{"allow":["write_text_file"],"deny":[]}},"create-app-specific-dirs":{"identifier":"create-app-specific-dirs","description":"This permissions allows to create the application specific directories.\n","commands":{"allow":["mkdir","scope-app-index"],"deny":[]}},"deny-copy-file":{"identifier":"deny-copy-file","description":"Denies the copy_file command without any pre-configured scope.","commands":{"allow":[],"deny":["copy_file"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-exists":{"identifier":"deny-exists","description":"Denies the exists command without any pre-configured scope.","commands":{"allow":[],"deny":["exists"]}},"deny-fstat":{"identifier":"deny-fstat","description":"Denies the fstat command without any pre-configured scope.","commands":{"allow":[],"deny":["fstat"]}},"deny-ftruncate":{"identifier":"deny-ftruncate","description":"Denies the ftruncate command without any pre-configured scope.","commands":{"allow":[],"deny":["ftruncate"]}},"deny-lstat":{"identifier":"deny-lstat","description":"Denies the lstat command without any pre-configured scope.","commands":{"allow":[],"deny":["lstat"]}},"deny-mkdir":{"identifier":"deny-mkdir","description":"Denies the mkdir command without any pre-configured scope.","commands":{"allow":[],"deny":["mkdir"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-read":{"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]}},"deny-read-dir":{"identifier":"deny-read-dir","description":"Denies the read_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dir"]}},"deny-read-file":{"identifier":"deny-read-file","description":"Denies the read_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_file"]}},"deny-read-text-file":{"identifier":"deny-read-text-file","description":"Denies the read_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file"]}},"deny-read-text-file-lines":{"identifier":"deny-read-text-file-lines","description":"Denies the read_text_file_lines command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines"]}},"deny-read-text-file-lines-next":{"identifier":"deny-read-text-file-lines-next","description":"Denies the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines_next"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-rename":{"identifier":"deny-rename","description":"Denies the rename command without any pre-configured scope.","commands":{"allow":[],"deny":["rename"]}},"deny-seek":{"identifier":"deny-seek","description":"Denies the seek command without any pre-configured scope.","commands":{"allow":[],"deny":["seek"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}},"deny-stat":{"identifier":"deny-stat","description":"Denies the stat command without any pre-configured scope.","commands":{"allow":[],"deny":["stat"]}},"deny-truncate":{"identifier":"deny-truncate","description":"Denies the truncate command without any pre-configured scope.","commands":{"allow":[],"deny":["truncate"]}},"deny-unwatch":{"identifier":"deny-unwatch","description":"Denies the unwatch command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}},"deny-webview-data-linux":{"identifier":"deny-webview-data-linux","description":"This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-webview-data-windows":{"identifier":"deny-webview-data-windows","description":"This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-write":{"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]}},"deny-write-file":{"identifier":"deny-write-file","description":"Denies the write_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_file"]}},"deny-write-text-file":{"identifier":"deny-write-text-file","description":"Denies the write_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text_file"]}},"read-all":{"identifier":"read-all","description":"This enables all read related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists","watch","unwatch"],"deny":[]}},"read-app-specific-dirs-recursive":{"identifier":"read-app-specific-dirs-recursive","description":"This permission allows recursive read functionality on the application\nspecific base directories. \n","commands":{"allow":["read_dir","read_file","read_text_file","read_text_file_lines","read_text_file_lines_next","exists","scope-app-recursive"],"deny":[]}},"read-dirs":{"identifier":"read-dirs","description":"This enables directory read and file metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists"],"deny":[]}},"read-files":{"identifier":"read-files","description":"This enables file read related commands without any pre-configured accessible paths.","commands":{"allow":["read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists"],"deny":[]}},"read-meta":{"identifier":"read-meta","description":"This enables all index or metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists","size"],"deny":[]}},"scope":{"identifier":"scope","description":"An empty permission you can use to modify the global scope.","commands":{"allow":[],"deny":[]}},"scope-app":{"identifier":"scope-app","description":"This scope permits access to all files and list content of top level directories in the application folders.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"},{"path":"$APPDATA"},{"path":"$APPDATA/*"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"},{"path":"$APPCACHE"},{"path":"$APPCACHE/*"},{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-app-index":{"identifier":"scope-app-index","description":"This scope permits to list all files and folders in the application directories.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPDATA"},{"path":"$APPLOCALDATA"},{"path":"$APPCACHE"},{"path":"$APPLOG"}]}},"scope-app-recursive":{"identifier":"scope-app-recursive","description":"This scope permits recursive access to the complete application folders, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"},{"path":"$APPDATA"},{"path":"$APPDATA/**"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"},{"path":"$APPCACHE"},{"path":"$APPCACHE/**"},{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-appcache":{"identifier":"scope-appcache","description":"This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/*"}]}},"scope-appcache-index":{"identifier":"scope-appcache-index","description":"This scope permits to list all files and folders in the `$APPCACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"}]}},"scope-appcache-recursive":{"identifier":"scope-appcache-recursive","description":"This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/**"}]}},"scope-appconfig":{"identifier":"scope-appconfig","description":"This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"}]}},"scope-appconfig-index":{"identifier":"scope-appconfig-index","description":"This scope permits to list all files and folders in the `$APPCONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"}]}},"scope-appconfig-recursive":{"identifier":"scope-appconfig-recursive","description":"This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"}]}},"scope-appdata":{"identifier":"scope-appdata","description":"This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/*"}]}},"scope-appdata-index":{"identifier":"scope-appdata-index","description":"This scope permits to list all files and folders in the `$APPDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"}]}},"scope-appdata-recursive":{"identifier":"scope-appdata-recursive","description":"This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]}},"scope-applocaldata":{"identifier":"scope-applocaldata","description":"This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"}]}},"scope-applocaldata-index":{"identifier":"scope-applocaldata-index","description":"This scope permits to list all files and folders in the `$APPLOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"}]}},"scope-applocaldata-recursive":{"identifier":"scope-applocaldata-recursive","description":"This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"}]}},"scope-applog":{"identifier":"scope-applog","description":"This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-applog-index":{"identifier":"scope-applog-index","description":"This scope permits to list all files and folders in the `$APPLOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"}]}},"scope-applog-recursive":{"identifier":"scope-applog-recursive","description":"This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-audio":{"identifier":"scope-audio","description":"This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/*"}]}},"scope-audio-index":{"identifier":"scope-audio-index","description":"This scope permits to list all files and folders in the `$AUDIO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"}]}},"scope-audio-recursive":{"identifier":"scope-audio-recursive","description":"This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/**"}]}},"scope-cache":{"identifier":"scope-cache","description":"This scope permits access to all files and list content of top level directories in the `$CACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/*"}]}},"scope-cache-index":{"identifier":"scope-cache-index","description":"This scope permits to list all files and folders in the `$CACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"}]}},"scope-cache-recursive":{"identifier":"scope-cache-recursive","description":"This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/**"}]}},"scope-config":{"identifier":"scope-config","description":"This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/*"}]}},"scope-config-index":{"identifier":"scope-config-index","description":"This scope permits to list all files and folders in the `$CONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"}]}},"scope-config-recursive":{"identifier":"scope-config-recursive","description":"This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/**"}]}},"scope-data":{"identifier":"scope-data","description":"This scope permits access to all files and list content of top level directories in the `$DATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/*"}]}},"scope-data-index":{"identifier":"scope-data-index","description":"This scope permits to list all files and folders in the `$DATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"}]}},"scope-data-recursive":{"identifier":"scope-data-recursive","description":"This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/**"}]}},"scope-desktop":{"identifier":"scope-desktop","description":"This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/*"}]}},"scope-desktop-index":{"identifier":"scope-desktop-index","description":"This scope permits to list all files and folders in the `$DESKTOP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"}]}},"scope-desktop-recursive":{"identifier":"scope-desktop-recursive","description":"This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/**"}]}},"scope-document":{"identifier":"scope-document","description":"This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/*"}]}},"scope-document-index":{"identifier":"scope-document-index","description":"This scope permits to list all files and folders in the `$DOCUMENT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"}]}},"scope-document-recursive":{"identifier":"scope-document-recursive","description":"This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/**"}]}},"scope-download":{"identifier":"scope-download","description":"This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/*"}]}},"scope-download-index":{"identifier":"scope-download-index","description":"This scope permits to list all files and folders in the `$DOWNLOAD`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"}]}},"scope-download-recursive":{"identifier":"scope-download-recursive","description":"This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/**"}]}},"scope-exe":{"identifier":"scope-exe","description":"This scope permits access to all files and list content of top level directories in the `$EXE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/*"}]}},"scope-exe-index":{"identifier":"scope-exe-index","description":"This scope permits to list all files and folders in the `$EXE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"}]}},"scope-exe-recursive":{"identifier":"scope-exe-recursive","description":"This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/**"}]}},"scope-font":{"identifier":"scope-font","description":"This scope permits access to all files and list content of top level directories in the `$FONT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/*"}]}},"scope-font-index":{"identifier":"scope-font-index","description":"This scope permits to list all files and folders in the `$FONT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"}]}},"scope-font-recursive":{"identifier":"scope-font-recursive","description":"This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/**"}]}},"scope-home":{"identifier":"scope-home","description":"This scope permits access to all files and list content of top level directories in the `$HOME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/*"}]}},"scope-home-index":{"identifier":"scope-home-index","description":"This scope permits to list all files and folders in the `$HOME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"}]}},"scope-home-recursive":{"identifier":"scope-home-recursive","description":"This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/**"}]}},"scope-localdata":{"identifier":"scope-localdata","description":"This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/*"}]}},"scope-localdata-index":{"identifier":"scope-localdata-index","description":"This scope permits to list all files and folders in the `$LOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"}]}},"scope-localdata-recursive":{"identifier":"scope-localdata-recursive","description":"This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/**"}]}},"scope-log":{"identifier":"scope-log","description":"This scope permits access to all files and list content of top level directories in the `$LOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/*"}]}},"scope-log-index":{"identifier":"scope-log-index","description":"This scope permits to list all files and folders in the `$LOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"}]}},"scope-log-recursive":{"identifier":"scope-log-recursive","description":"This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/**"}]}},"scope-picture":{"identifier":"scope-picture","description":"This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/*"}]}},"scope-picture-index":{"identifier":"scope-picture-index","description":"This scope permits to list all files and folders in the `$PICTURE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"}]}},"scope-picture-recursive":{"identifier":"scope-picture-recursive","description":"This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/**"}]}},"scope-public":{"identifier":"scope-public","description":"This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/*"}]}},"scope-public-index":{"identifier":"scope-public-index","description":"This scope permits to list all files and folders in the `$PUBLIC`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"}]}},"scope-public-recursive":{"identifier":"scope-public-recursive","description":"This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/**"}]}},"scope-resource":{"identifier":"scope-resource","description":"This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/*"}]}},"scope-resource-index":{"identifier":"scope-resource-index","description":"This scope permits to list all files and folders in the `$RESOURCE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"}]}},"scope-resource-recursive":{"identifier":"scope-resource-recursive","description":"This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/**"}]}},"scope-runtime":{"identifier":"scope-runtime","description":"This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/*"}]}},"scope-runtime-index":{"identifier":"scope-runtime-index","description":"This scope permits to list all files and folders in the `$RUNTIME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"}]}},"scope-runtime-recursive":{"identifier":"scope-runtime-recursive","description":"This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/**"}]}},"scope-temp":{"identifier":"scope-temp","description":"This scope permits access to all files and list content of top level directories in the `$TEMP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/*"}]}},"scope-temp-index":{"identifier":"scope-temp-index","description":"This scope permits to list all files and folders in the `$TEMP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"}]}},"scope-temp-recursive":{"identifier":"scope-temp-recursive","description":"This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/**"}]}},"scope-template":{"identifier":"scope-template","description":"This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/*"}]}},"scope-template-index":{"identifier":"scope-template-index","description":"This scope permits to list all files and folders in the `$TEMPLATE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"}]}},"scope-template-recursive":{"identifier":"scope-template-recursive","description":"This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/**"}]}},"scope-video":{"identifier":"scope-video","description":"This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/*"}]}},"scope-video-index":{"identifier":"scope-video-index","description":"This scope permits to list all files and folders in the `$VIDEO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"}]}},"scope-video-recursive":{"identifier":"scope-video-recursive","description":"This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/**"}]}},"write-all":{"identifier":"write-all","description":"This enables all write related commands without any pre-configured accessible paths.","commands":{"allow":["mkdir","create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}},"write-files":{"identifier":"write-files","description":"This enables all file write related commands without any pre-configured accessible paths.","commands":{"allow":["create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}}},"permission_sets":{"allow-app-meta":{"identifier":"allow-app-meta","description":"This allows non-recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-index"]},"allow-app-meta-recursive":{"identifier":"allow-app-meta-recursive","description":"This allows full recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-recursive"]},"allow-app-read":{"identifier":"allow-app-read","description":"This allows non-recursive read access to the application folders.","permissions":["read-all","scope-app"]},"allow-app-read-recursive":{"identifier":"allow-app-read-recursive","description":"This allows full recursive read access to the complete application folders, files and subdirectories.","permissions":["read-all","scope-app-recursive"]},"allow-app-write":{"identifier":"allow-app-write","description":"This allows non-recursive write access to the application folders.","permissions":["write-all","scope-app"]},"allow-app-write-recursive":{"identifier":"allow-app-write-recursive","description":"This allows full recursive write access to the complete application folders, files and subdirectories.","permissions":["write-all","scope-app-recursive"]},"allow-appcache-meta":{"identifier":"allow-appcache-meta","description":"This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-index"]},"allow-appcache-meta-recursive":{"identifier":"allow-appcache-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-recursive"]},"allow-appcache-read":{"identifier":"allow-appcache-read","description":"This allows non-recursive read access to the `$APPCACHE` folder.","permissions":["read-all","scope-appcache"]},"allow-appcache-read-recursive":{"identifier":"allow-appcache-read-recursive","description":"This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["read-all","scope-appcache-recursive"]},"allow-appcache-write":{"identifier":"allow-appcache-write","description":"This allows non-recursive write access to the `$APPCACHE` folder.","permissions":["write-all","scope-appcache"]},"allow-appcache-write-recursive":{"identifier":"allow-appcache-write-recursive","description":"This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["write-all","scope-appcache-recursive"]},"allow-appconfig-meta":{"identifier":"allow-appconfig-meta","description":"This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-index"]},"allow-appconfig-meta-recursive":{"identifier":"allow-appconfig-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-recursive"]},"allow-appconfig-read":{"identifier":"allow-appconfig-read","description":"This allows non-recursive read access to the `$APPCONFIG` folder.","permissions":["read-all","scope-appconfig"]},"allow-appconfig-read-recursive":{"identifier":"allow-appconfig-read-recursive","description":"This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["read-all","scope-appconfig-recursive"]},"allow-appconfig-write":{"identifier":"allow-appconfig-write","description":"This allows non-recursive write access to the `$APPCONFIG` folder.","permissions":["write-all","scope-appconfig"]},"allow-appconfig-write-recursive":{"identifier":"allow-appconfig-write-recursive","description":"This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["write-all","scope-appconfig-recursive"]},"allow-appdata-meta":{"identifier":"allow-appdata-meta","description":"This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-index"]},"allow-appdata-meta-recursive":{"identifier":"allow-appdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-recursive"]},"allow-appdata-read":{"identifier":"allow-appdata-read","description":"This allows non-recursive read access to the `$APPDATA` folder.","permissions":["read-all","scope-appdata"]},"allow-appdata-read-recursive":{"identifier":"allow-appdata-read-recursive","description":"This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["read-all","scope-appdata-recursive"]},"allow-appdata-write":{"identifier":"allow-appdata-write","description":"This allows non-recursive write access to the `$APPDATA` folder.","permissions":["write-all","scope-appdata"]},"allow-appdata-write-recursive":{"identifier":"allow-appdata-write-recursive","description":"This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["write-all","scope-appdata-recursive"]},"allow-applocaldata-meta":{"identifier":"allow-applocaldata-meta","description":"This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-index"]},"allow-applocaldata-meta-recursive":{"identifier":"allow-applocaldata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-recursive"]},"allow-applocaldata-read":{"identifier":"allow-applocaldata-read","description":"This allows non-recursive read access to the `$APPLOCALDATA` folder.","permissions":["read-all","scope-applocaldata"]},"allow-applocaldata-read-recursive":{"identifier":"allow-applocaldata-read-recursive","description":"This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-applocaldata-recursive"]},"allow-applocaldata-write":{"identifier":"allow-applocaldata-write","description":"This allows non-recursive write access to the `$APPLOCALDATA` folder.","permissions":["write-all","scope-applocaldata"]},"allow-applocaldata-write-recursive":{"identifier":"allow-applocaldata-write-recursive","description":"This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-applocaldata-recursive"]},"allow-applog-meta":{"identifier":"allow-applog-meta","description":"This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-index"]},"allow-applog-meta-recursive":{"identifier":"allow-applog-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-recursive"]},"allow-applog-read":{"identifier":"allow-applog-read","description":"This allows non-recursive read access to the `$APPLOG` folder.","permissions":["read-all","scope-applog"]},"allow-applog-read-recursive":{"identifier":"allow-applog-read-recursive","description":"This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["read-all","scope-applog-recursive"]},"allow-applog-write":{"identifier":"allow-applog-write","description":"This allows non-recursive write access to the `$APPLOG` folder.","permissions":["write-all","scope-applog"]},"allow-applog-write-recursive":{"identifier":"allow-applog-write-recursive","description":"This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["write-all","scope-applog-recursive"]},"allow-audio-meta":{"identifier":"allow-audio-meta","description":"This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-index"]},"allow-audio-meta-recursive":{"identifier":"allow-audio-meta-recursive","description":"This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-recursive"]},"allow-audio-read":{"identifier":"allow-audio-read","description":"This allows non-recursive read access to the `$AUDIO` folder.","permissions":["read-all","scope-audio"]},"allow-audio-read-recursive":{"identifier":"allow-audio-read-recursive","description":"This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["read-all","scope-audio-recursive"]},"allow-audio-write":{"identifier":"allow-audio-write","description":"This allows non-recursive write access to the `$AUDIO` folder.","permissions":["write-all","scope-audio"]},"allow-audio-write-recursive":{"identifier":"allow-audio-write-recursive","description":"This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["write-all","scope-audio-recursive"]},"allow-cache-meta":{"identifier":"allow-cache-meta","description":"This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-index"]},"allow-cache-meta-recursive":{"identifier":"allow-cache-meta-recursive","description":"This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-recursive"]},"allow-cache-read":{"identifier":"allow-cache-read","description":"This allows non-recursive read access to the `$CACHE` folder.","permissions":["read-all","scope-cache"]},"allow-cache-read-recursive":{"identifier":"allow-cache-read-recursive","description":"This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.","permissions":["read-all","scope-cache-recursive"]},"allow-cache-write":{"identifier":"allow-cache-write","description":"This allows non-recursive write access to the `$CACHE` folder.","permissions":["write-all","scope-cache"]},"allow-cache-write-recursive":{"identifier":"allow-cache-write-recursive","description":"This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.","permissions":["write-all","scope-cache-recursive"]},"allow-config-meta":{"identifier":"allow-config-meta","description":"This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-index"]},"allow-config-meta-recursive":{"identifier":"allow-config-meta-recursive","description":"This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-recursive"]},"allow-config-read":{"identifier":"allow-config-read","description":"This allows non-recursive read access to the `$CONFIG` folder.","permissions":["read-all","scope-config"]},"allow-config-read-recursive":{"identifier":"allow-config-read-recursive","description":"This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["read-all","scope-config-recursive"]},"allow-config-write":{"identifier":"allow-config-write","description":"This allows non-recursive write access to the `$CONFIG` folder.","permissions":["write-all","scope-config"]},"allow-config-write-recursive":{"identifier":"allow-config-write-recursive","description":"This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["write-all","scope-config-recursive"]},"allow-data-meta":{"identifier":"allow-data-meta","description":"This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-index"]},"allow-data-meta-recursive":{"identifier":"allow-data-meta-recursive","description":"This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-recursive"]},"allow-data-read":{"identifier":"allow-data-read","description":"This allows non-recursive read access to the `$DATA` folder.","permissions":["read-all","scope-data"]},"allow-data-read-recursive":{"identifier":"allow-data-read-recursive","description":"This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.","permissions":["read-all","scope-data-recursive"]},"allow-data-write":{"identifier":"allow-data-write","description":"This allows non-recursive write access to the `$DATA` folder.","permissions":["write-all","scope-data"]},"allow-data-write-recursive":{"identifier":"allow-data-write-recursive","description":"This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.","permissions":["write-all","scope-data-recursive"]},"allow-desktop-meta":{"identifier":"allow-desktop-meta","description":"This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-index"]},"allow-desktop-meta-recursive":{"identifier":"allow-desktop-meta-recursive","description":"This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-recursive"]},"allow-desktop-read":{"identifier":"allow-desktop-read","description":"This allows non-recursive read access to the `$DESKTOP` folder.","permissions":["read-all","scope-desktop"]},"allow-desktop-read-recursive":{"identifier":"allow-desktop-read-recursive","description":"This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["read-all","scope-desktop-recursive"]},"allow-desktop-write":{"identifier":"allow-desktop-write","description":"This allows non-recursive write access to the `$DESKTOP` folder.","permissions":["write-all","scope-desktop"]},"allow-desktop-write-recursive":{"identifier":"allow-desktop-write-recursive","description":"This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["write-all","scope-desktop-recursive"]},"allow-document-meta":{"identifier":"allow-document-meta","description":"This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-index"]},"allow-document-meta-recursive":{"identifier":"allow-document-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-recursive"]},"allow-document-read":{"identifier":"allow-document-read","description":"This allows non-recursive read access to the `$DOCUMENT` folder.","permissions":["read-all","scope-document"]},"allow-document-read-recursive":{"identifier":"allow-document-read-recursive","description":"This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["read-all","scope-document-recursive"]},"allow-document-write":{"identifier":"allow-document-write","description":"This allows non-recursive write access to the `$DOCUMENT` folder.","permissions":["write-all","scope-document"]},"allow-document-write-recursive":{"identifier":"allow-document-write-recursive","description":"This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["write-all","scope-document-recursive"]},"allow-download-meta":{"identifier":"allow-download-meta","description":"This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-index"]},"allow-download-meta-recursive":{"identifier":"allow-download-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-recursive"]},"allow-download-read":{"identifier":"allow-download-read","description":"This allows non-recursive read access to the `$DOWNLOAD` folder.","permissions":["read-all","scope-download"]},"allow-download-read-recursive":{"identifier":"allow-download-read-recursive","description":"This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["read-all","scope-download-recursive"]},"allow-download-write":{"identifier":"allow-download-write","description":"This allows non-recursive write access to the `$DOWNLOAD` folder.","permissions":["write-all","scope-download"]},"allow-download-write-recursive":{"identifier":"allow-download-write-recursive","description":"This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["write-all","scope-download-recursive"]},"allow-exe-meta":{"identifier":"allow-exe-meta","description":"This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-index"]},"allow-exe-meta-recursive":{"identifier":"allow-exe-meta-recursive","description":"This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-recursive"]},"allow-exe-read":{"identifier":"allow-exe-read","description":"This allows non-recursive read access to the `$EXE` folder.","permissions":["read-all","scope-exe"]},"allow-exe-read-recursive":{"identifier":"allow-exe-read-recursive","description":"This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.","permissions":["read-all","scope-exe-recursive"]},"allow-exe-write":{"identifier":"allow-exe-write","description":"This allows non-recursive write access to the `$EXE` folder.","permissions":["write-all","scope-exe"]},"allow-exe-write-recursive":{"identifier":"allow-exe-write-recursive","description":"This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.","permissions":["write-all","scope-exe-recursive"]},"allow-font-meta":{"identifier":"allow-font-meta","description":"This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-index"]},"allow-font-meta-recursive":{"identifier":"allow-font-meta-recursive","description":"This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-recursive"]},"allow-font-read":{"identifier":"allow-font-read","description":"This allows non-recursive read access to the `$FONT` folder.","permissions":["read-all","scope-font"]},"allow-font-read-recursive":{"identifier":"allow-font-read-recursive","description":"This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.","permissions":["read-all","scope-font-recursive"]},"allow-font-write":{"identifier":"allow-font-write","description":"This allows non-recursive write access to the `$FONT` folder.","permissions":["write-all","scope-font"]},"allow-font-write-recursive":{"identifier":"allow-font-write-recursive","description":"This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.","permissions":["write-all","scope-font-recursive"]},"allow-home-meta":{"identifier":"allow-home-meta","description":"This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-index"]},"allow-home-meta-recursive":{"identifier":"allow-home-meta-recursive","description":"This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-recursive"]},"allow-home-read":{"identifier":"allow-home-read","description":"This allows non-recursive read access to the `$HOME` folder.","permissions":["read-all","scope-home"]},"allow-home-read-recursive":{"identifier":"allow-home-read-recursive","description":"This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.","permissions":["read-all","scope-home-recursive"]},"allow-home-write":{"identifier":"allow-home-write","description":"This allows non-recursive write access to the `$HOME` folder.","permissions":["write-all","scope-home"]},"allow-home-write-recursive":{"identifier":"allow-home-write-recursive","description":"This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.","permissions":["write-all","scope-home-recursive"]},"allow-localdata-meta":{"identifier":"allow-localdata-meta","description":"This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-index"]},"allow-localdata-meta-recursive":{"identifier":"allow-localdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-recursive"]},"allow-localdata-read":{"identifier":"allow-localdata-read","description":"This allows non-recursive read access to the `$LOCALDATA` folder.","permissions":["read-all","scope-localdata"]},"allow-localdata-read-recursive":{"identifier":"allow-localdata-read-recursive","description":"This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-localdata-recursive"]},"allow-localdata-write":{"identifier":"allow-localdata-write","description":"This allows non-recursive write access to the `$LOCALDATA` folder.","permissions":["write-all","scope-localdata"]},"allow-localdata-write-recursive":{"identifier":"allow-localdata-write-recursive","description":"This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-localdata-recursive"]},"allow-log-meta":{"identifier":"allow-log-meta","description":"This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-index"]},"allow-log-meta-recursive":{"identifier":"allow-log-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-recursive"]},"allow-log-read":{"identifier":"allow-log-read","description":"This allows non-recursive read access to the `$LOG` folder.","permissions":["read-all","scope-log"]},"allow-log-read-recursive":{"identifier":"allow-log-read-recursive","description":"This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.","permissions":["read-all","scope-log-recursive"]},"allow-log-write":{"identifier":"allow-log-write","description":"This allows non-recursive write access to the `$LOG` folder.","permissions":["write-all","scope-log"]},"allow-log-write-recursive":{"identifier":"allow-log-write-recursive","description":"This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.","permissions":["write-all","scope-log-recursive"]},"allow-picture-meta":{"identifier":"allow-picture-meta","description":"This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-index"]},"allow-picture-meta-recursive":{"identifier":"allow-picture-meta-recursive","description":"This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-recursive"]},"allow-picture-read":{"identifier":"allow-picture-read","description":"This allows non-recursive read access to the `$PICTURE` folder.","permissions":["read-all","scope-picture"]},"allow-picture-read-recursive":{"identifier":"allow-picture-read-recursive","description":"This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["read-all","scope-picture-recursive"]},"allow-picture-write":{"identifier":"allow-picture-write","description":"This allows non-recursive write access to the `$PICTURE` folder.","permissions":["write-all","scope-picture"]},"allow-picture-write-recursive":{"identifier":"allow-picture-write-recursive","description":"This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["write-all","scope-picture-recursive"]},"allow-public-meta":{"identifier":"allow-public-meta","description":"This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-index"]},"allow-public-meta-recursive":{"identifier":"allow-public-meta-recursive","description":"This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-recursive"]},"allow-public-read":{"identifier":"allow-public-read","description":"This allows non-recursive read access to the `$PUBLIC` folder.","permissions":["read-all","scope-public"]},"allow-public-read-recursive":{"identifier":"allow-public-read-recursive","description":"This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["read-all","scope-public-recursive"]},"allow-public-write":{"identifier":"allow-public-write","description":"This allows non-recursive write access to the `$PUBLIC` folder.","permissions":["write-all","scope-public"]},"allow-public-write-recursive":{"identifier":"allow-public-write-recursive","description":"This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["write-all","scope-public-recursive"]},"allow-resource-meta":{"identifier":"allow-resource-meta","description":"This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-index"]},"allow-resource-meta-recursive":{"identifier":"allow-resource-meta-recursive","description":"This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-recursive"]},"allow-resource-read":{"identifier":"allow-resource-read","description":"This allows non-recursive read access to the `$RESOURCE` folder.","permissions":["read-all","scope-resource"]},"allow-resource-read-recursive":{"identifier":"allow-resource-read-recursive","description":"This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["read-all","scope-resource-recursive"]},"allow-resource-write":{"identifier":"allow-resource-write","description":"This allows non-recursive write access to the `$RESOURCE` folder.","permissions":["write-all","scope-resource"]},"allow-resource-write-recursive":{"identifier":"allow-resource-write-recursive","description":"This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["write-all","scope-resource-recursive"]},"allow-runtime-meta":{"identifier":"allow-runtime-meta","description":"This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-index"]},"allow-runtime-meta-recursive":{"identifier":"allow-runtime-meta-recursive","description":"This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-recursive"]},"allow-runtime-read":{"identifier":"allow-runtime-read","description":"This allows non-recursive read access to the `$RUNTIME` folder.","permissions":["read-all","scope-runtime"]},"allow-runtime-read-recursive":{"identifier":"allow-runtime-read-recursive","description":"This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["read-all","scope-runtime-recursive"]},"allow-runtime-write":{"identifier":"allow-runtime-write","description":"This allows non-recursive write access to the `$RUNTIME` folder.","permissions":["write-all","scope-runtime"]},"allow-runtime-write-recursive":{"identifier":"allow-runtime-write-recursive","description":"This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["write-all","scope-runtime-recursive"]},"allow-temp-meta":{"identifier":"allow-temp-meta","description":"This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-index"]},"allow-temp-meta-recursive":{"identifier":"allow-temp-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-recursive"]},"allow-temp-read":{"identifier":"allow-temp-read","description":"This allows non-recursive read access to the `$TEMP` folder.","permissions":["read-all","scope-temp"]},"allow-temp-read-recursive":{"identifier":"allow-temp-read-recursive","description":"This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.","permissions":["read-all","scope-temp-recursive"]},"allow-temp-write":{"identifier":"allow-temp-write","description":"This allows non-recursive write access to the `$TEMP` folder.","permissions":["write-all","scope-temp"]},"allow-temp-write-recursive":{"identifier":"allow-temp-write-recursive","description":"This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.","permissions":["write-all","scope-temp-recursive"]},"allow-template-meta":{"identifier":"allow-template-meta","description":"This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-index"]},"allow-template-meta-recursive":{"identifier":"allow-template-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-recursive"]},"allow-template-read":{"identifier":"allow-template-read","description":"This allows non-recursive read access to the `$TEMPLATE` folder.","permissions":["read-all","scope-template"]},"allow-template-read-recursive":{"identifier":"allow-template-read-recursive","description":"This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["read-all","scope-template-recursive"]},"allow-template-write":{"identifier":"allow-template-write","description":"This allows non-recursive write access to the `$TEMPLATE` folder.","permissions":["write-all","scope-template"]},"allow-template-write-recursive":{"identifier":"allow-template-write-recursive","description":"This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["write-all","scope-template-recursive"]},"allow-video-meta":{"identifier":"allow-video-meta","description":"This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-index"]},"allow-video-meta-recursive":{"identifier":"allow-video-meta-recursive","description":"This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-recursive"]},"allow-video-read":{"identifier":"allow-video-read","description":"This allows non-recursive read access to the `$VIDEO` folder.","permissions":["read-all","scope-video"]},"allow-video-read-recursive":{"identifier":"allow-video-read-recursive","description":"This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["read-all","scope-video-recursive"]},"allow-video-write":{"identifier":"allow-video-write","description":"This allows non-recursive write access to the `$VIDEO` folder.","permissions":["write-all","scope-video"]},"allow-video-write-recursive":{"identifier":"allow-video-write-recursive","description":"This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["write-all","scope-video-recursive"]},"deny-default":{"identifier":"deny-default","description":"This denies access to dangerous Tauri relevant files and folders by default.","permissions":["deny-webview-data-linux","deny-webview-data-windows"]}},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"description":"A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},{"properties":{"path":{"description":"A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"}},"required":["path"],"type":"object"}],"description":"FS scope entry.","title":"FsScopeEntry"}},"http":{"default_permission":{"identifier":"default","description":"This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n","permissions":["allow-fetch","allow-fetch-cancel","allow-fetch-read-body","allow-fetch-send"]},"permissions":{"allow-fetch":{"identifier":"allow-fetch","description":"Enables the fetch command without any pre-configured scope.","commands":{"allow":["fetch"],"deny":[]}},"allow-fetch-cancel":{"identifier":"allow-fetch-cancel","description":"Enables the fetch_cancel command without any pre-configured scope.","commands":{"allow":["fetch_cancel"],"deny":[]}},"allow-fetch-read-body":{"identifier":"allow-fetch-read-body","description":"Enables the fetch_read_body command without any pre-configured scope.","commands":{"allow":["fetch_read_body"],"deny":[]}},"allow-fetch-send":{"identifier":"allow-fetch-send","description":"Enables the fetch_send command without any pre-configured scope.","commands":{"allow":["fetch_send"],"deny":[]}},"deny-fetch":{"identifier":"deny-fetch","description":"Denies the fetch command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch"]}},"deny-fetch-cancel":{"identifier":"deny-fetch-cancel","description":"Denies the fetch_cancel command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_cancel"]}},"deny-fetch-read-body":{"identifier":"deny-fetch-read-body","description":"Denies the fetch_read_body command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_read_body"]}},"deny-fetch-send":{"identifier":"deny-fetch-send","description":"Denies the fetch_send command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_send"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"description":"A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"","type":"string"},{"properties":{"url":{"description":"A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"","type":"string"}},"required":["url"],"type":"object"}],"description":"HTTP scope entry.","title":"HttpScopeEntry"}},"notification":{"default_permission":{"identifier":"default","description":"This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n","permissions":["allow-is-permission-granted","allow-request-permission","allow-notify","allow-register-action-types","allow-register-listener","allow-cancel","allow-get-pending","allow-remove-active","allow-get-active","allow-check-permissions","allow-show","allow-batch","allow-list-channels","allow-delete-channel","allow-create-channel","allow-permission-state"]},"permissions":{"allow-batch":{"identifier":"allow-batch","description":"Enables the batch command without any pre-configured scope.","commands":{"allow":["batch"],"deny":[]}},"allow-cancel":{"identifier":"allow-cancel","description":"Enables the cancel command without any pre-configured scope.","commands":{"allow":["cancel"],"deny":[]}},"allow-check-permissions":{"identifier":"allow-check-permissions","description":"Enables the check_permissions command without any pre-configured scope.","commands":{"allow":["check_permissions"],"deny":[]}},"allow-create-channel":{"identifier":"allow-create-channel","description":"Enables the create_channel command without any pre-configured scope.","commands":{"allow":["create_channel"],"deny":[]}},"allow-delete-channel":{"identifier":"allow-delete-channel","description":"Enables the delete_channel command without any pre-configured scope.","commands":{"allow":["delete_channel"],"deny":[]}},"allow-get-active":{"identifier":"allow-get-active","description":"Enables the get_active command without any pre-configured scope.","commands":{"allow":["get_active"],"deny":[]}},"allow-get-pending":{"identifier":"allow-get-pending","description":"Enables the get_pending command without any pre-configured scope.","commands":{"allow":["get_pending"],"deny":[]}},"allow-is-permission-granted":{"identifier":"allow-is-permission-granted","description":"Enables the is_permission_granted command without any pre-configured scope.","commands":{"allow":["is_permission_granted"],"deny":[]}},"allow-list-channels":{"identifier":"allow-list-channels","description":"Enables the list_channels command without any pre-configured scope.","commands":{"allow":["list_channels"],"deny":[]}},"allow-notify":{"identifier":"allow-notify","description":"Enables the notify command without any pre-configured scope.","commands":{"allow":["notify"],"deny":[]}},"allow-permission-state":{"identifier":"allow-permission-state","description":"Enables the permission_state command without any pre-configured scope.","commands":{"allow":["permission_state"],"deny":[]}},"allow-register-action-types":{"identifier":"allow-register-action-types","description":"Enables the register_action_types command without any pre-configured scope.","commands":{"allow":["register_action_types"],"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-active":{"identifier":"allow-remove-active","description":"Enables the remove_active command without any pre-configured scope.","commands":{"allow":["remove_active"],"deny":[]}},"allow-request-permission":{"identifier":"allow-request-permission","description":"Enables the request_permission command without any pre-configured scope.","commands":{"allow":["request_permission"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"deny-batch":{"identifier":"deny-batch","description":"Denies the batch command without any pre-configured scope.","commands":{"allow":[],"deny":["batch"]}},"deny-cancel":{"identifier":"deny-cancel","description":"Denies the cancel command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel"]}},"deny-check-permissions":{"identifier":"deny-check-permissions","description":"Denies the check_permissions command without any pre-configured scope.","commands":{"allow":[],"deny":["check_permissions"]}},"deny-create-channel":{"identifier":"deny-create-channel","description":"Denies the create_channel command without any pre-configured scope.","commands":{"allow":[],"deny":["create_channel"]}},"deny-delete-channel":{"identifier":"deny-delete-channel","description":"Denies the delete_channel command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_channel"]}},"deny-get-active":{"identifier":"deny-get-active","description":"Denies the get_active command without any pre-configured scope.","commands":{"allow":[],"deny":["get_active"]}},"deny-get-pending":{"identifier":"deny-get-pending","description":"Denies the get_pending command without any pre-configured scope.","commands":{"allow":[],"deny":["get_pending"]}},"deny-is-permission-granted":{"identifier":"deny-is-permission-granted","description":"Denies the is_permission_granted command without any pre-configured scope.","commands":{"allow":[],"deny":["is_permission_granted"]}},"deny-list-channels":{"identifier":"deny-list-channels","description":"Denies the list_channels command without any pre-configured scope.","commands":{"allow":[],"deny":["list_channels"]}},"deny-notify":{"identifier":"deny-notify","description":"Denies the notify command without any pre-configured scope.","commands":{"allow":[],"deny":["notify"]}},"deny-permission-state":{"identifier":"deny-permission-state","description":"Denies the permission_state command without any pre-configured scope.","commands":{"allow":[],"deny":["permission_state"]}},"deny-register-action-types":{"identifier":"deny-register-action-types","description":"Denies the register_action_types command without any pre-configured scope.","commands":{"allow":[],"deny":["register_action_types"]}},"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-active":{"identifier":"deny-remove-active","description":"Denies the remove_active command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_active"]}},"deny-request-permission":{"identifier":"deny-request-permission","description":"Denies the request_permission command without any pre-configured scope.","commands":{"allow":[],"deny":["request_permission"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}}},"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"}},"updater":{"default_permission":{"identifier":"default","description":"This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n","permissions":["allow-check","allow-download","allow-install","allow-download-and-install"]},"permissions":{"allow-check":{"identifier":"allow-check","description":"Enables the check command without any pre-configured scope.","commands":{"allow":["check"],"deny":[]}},"allow-download":{"identifier":"allow-download","description":"Enables the download command without any pre-configured scope.","commands":{"allow":["download"],"deny":[]}},"allow-download-and-install":{"identifier":"allow-download-and-install","description":"Enables the download_and_install command without any pre-configured scope.","commands":{"allow":["download_and_install"],"deny":[]}},"allow-install":{"identifier":"allow-install","description":"Enables the install command without any pre-configured scope.","commands":{"allow":["install"],"deny":[]}},"deny-check":{"identifier":"deny-check","description":"Denies the check command without any pre-configured scope.","commands":{"allow":[],"deny":["check"]}},"deny-download":{"identifier":"deny-download","description":"Denies the download command without any pre-configured scope.","commands":{"allow":[],"deny":["download"]}},"deny-download-and-install":{"identifier":"deny-download-and-install","description":"Denies the download_and_install command without any pre-configured scope.","commands":{"allow":[],"deny":["download_and_install"]}},"deny-install":{"identifier":"deny-install","description":"Denies the install command without any pre-configured scope.","commands":{"allow":[],"deny":["install"]}}},"permission_sets":{},"global_scope_schema":null},"window-state":{"default_permission":{"identifier":"default","description":"This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n","permissions":["allow-filename","allow-restore-state","allow-save-window-state"]},"permissions":{"allow-filename":{"identifier":"allow-filename","description":"Enables the filename command without any pre-configured scope.","commands":{"allow":["filename"],"deny":[]}},"allow-restore-state":{"identifier":"allow-restore-state","description":"Enables the restore_state command without any pre-configured scope.","commands":{"allow":["restore_state"],"deny":[]}},"allow-save-window-state":{"identifier":"allow-save-window-state","description":"Enables the save_window_state command without any pre-configured scope.","commands":{"allow":["save_window_state"],"deny":[]}},"deny-filename":{"identifier":"deny-filename","description":"Denies the filename command without any pre-configured scope.","commands":{"allow":[],"deny":["filename"]}},"deny-restore-state":{"identifier":"deny-restore-state","description":"Denies the restore_state command without any pre-configured scope.","commands":{"allow":[],"deny":["restore_state"]}},"deny-save-window-state":{"identifier":"deny-save-window-state","description":"Denies the save_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["save_window_state"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/tauri/src-tauri/gen/schemas/capabilities.json b/tauri/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 00000000..890cbcf1 --- /dev/null +++ b/tauri/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{"all-windows":{"identifier":"all-windows","description":"Capability for all application windows","local":true,"windows":["main","settings","welcome","server-console","api-testing"],"permissions":["core:default","core:window:default","core:window:allow-create","core:window:allow-close","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-start-dragging","core:window:allow-set-title","core:app:default","core:path:default","core:event:default","core:webview:default","shell:default","shell:allow-open","dialog:default","fs:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-rename","fs:allow-exists","fs:scope-appdata-recursive","fs:scope-resource-recursive","fs:scope-temp-recursive","fs:allow-home-read-recursive","fs:allow-home-write-recursive","process:default","process:allow-exit","process:allow-restart","http:default","http:allow-fetch","notification:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:allow-notify","window-state:allow-restore-state","window-state:allow-save-window-state","updater:default"]},"default":{"identifier":"default","description":"Default capability for the application","local":true,"windows":["main"],"permissions":["core:default","core:window:default","core:window:allow-create","core:window:allow-close","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-start-dragging","core:window:allow-set-title","core:app:default","core:path:default","core:event:default","core:webview:default","shell:default","shell:allow-open","dialog:default","fs:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-rename","fs:allow-exists","fs:scope-appdata-recursive","fs:scope-resource-recursive","fs:scope-temp-recursive","fs:allow-home-read-recursive","fs:allow-home-write-recursive","process:default","process:allow-exit","process:allow-restart","http:default","http:allow-fetch","notification:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:allow-notify","window-state:allow-restore-state","window-state:allow-save-window-state","updater:default"]},"settings":{"identifier":"settings","description":"Capability for the settings window","local":true,"windows":["settings"],"permissions":["core:default","core:window:default","core:window:allow-close","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-start-dragging","core:window:allow-set-title","core:app:default","core:path:default","core:event:default","core:webview:default","shell:default","shell:allow-open","dialog:default","fs:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-rename","fs:allow-exists","fs:scope-appdata-recursive","fs:scope-resource-recursive","fs:scope-temp-recursive","fs:allow-home-read-recursive","fs:allow-home-write-recursive","process:default","process:allow-exit","process:allow-restart","http:default","http:allow-fetch","notification:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:allow-notify","window-state:allow-restore-state","window-state:allow-save-window-state","updater:default"]}} \ No newline at end of file diff --git a/tauri/src-tauri/gen/schemas/desktop-schema.json b/tauri/src-tauri/gen/schemas/desktop-schema.json new file mode 100644 index 00000000..e93ad2c2 --- /dev/null +++ b/tauri/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,6590 @@ +{ + "$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 programatic 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 programatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", + "type": "string", + "const": "http:default", + "markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" + }, + { + "description": "Enables the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch", + "markdownDescription": "Enables the fetch command without any pre-configured scope." + }, + { + "description": "Enables the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-cancel", + "markdownDescription": "Enables the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Enables the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-read-body", + "markdownDescription": "Enables the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Enables the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-send", + "markdownDescription": "Enables the fetch_send command without any pre-configured scope." + }, + { + "description": "Denies the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch", + "markdownDescription": "Denies the fetch command without any pre-configured scope." + }, + { + "description": "Denies the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-cancel", + "markdownDescription": "Denies the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Denies the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-read-body", + "markdownDescription": "Denies the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Denies the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-send", + "markdownDescription": "Denies the fetch_send command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "HttpScopeEntry", + "description": "HTTP scope entry.", + "anyOf": [ + { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + }, + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "HttpScopeEntry", + "description": "HTTP scope entry.", + "anyOf": [ + { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + }, + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`", + "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`" + }, + { + "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 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 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 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 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 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 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_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_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_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_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_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_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-ask`\n- `allow-confirm`\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-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "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.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + }, + { + "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", + "type": "string", + "const": "http:default", + "markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" + }, + { + "description": "Enables the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch", + "markdownDescription": "Enables the fetch command without any pre-configured scope." + }, + { + "description": "Enables the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-cancel", + "markdownDescription": "Enables the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Enables the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-read-body", + "markdownDescription": "Enables the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Enables the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-send", + "markdownDescription": "Enables the fetch_send command without any pre-configured scope." + }, + { + "description": "Denies the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch", + "markdownDescription": "Denies the fetch command without any pre-configured scope." + }, + { + "description": "Denies the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-cancel", + "markdownDescription": "Denies the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Denies the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-read-body", + "markdownDescription": "Denies the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Denies the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-send", + "markdownDescription": "Denies the fetch_send command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`", + "type": "string", + "const": "notification:default", + "markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`" + }, + { + "description": "Enables the batch command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-batch", + "markdownDescription": "Enables the batch command without any pre-configured scope." + }, + { + "description": "Enables the cancel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-cancel", + "markdownDescription": "Enables the cancel command without any pre-configured scope." + }, + { + "description": "Enables the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-check-permissions", + "markdownDescription": "Enables the check_permissions command without any pre-configured scope." + }, + { + "description": "Enables the create_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-create-channel", + "markdownDescription": "Enables the create_channel command without any pre-configured scope." + }, + { + "description": "Enables the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-delete-channel", + "markdownDescription": "Enables the delete_channel command without any pre-configured scope." + }, + { + "description": "Enables the get_active command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-get-active", + "markdownDescription": "Enables the get_active command without any pre-configured scope." + }, + { + "description": "Enables the get_pending command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-get-pending", + "markdownDescription": "Enables the get_pending command without any pre-configured scope." + }, + { + "description": "Enables the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-is-permission-granted", + "markdownDescription": "Enables the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Enables the list_channels command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-list-channels", + "markdownDescription": "Enables the list_channels command without any pre-configured scope." + }, + { + "description": "Enables the notify command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-notify", + "markdownDescription": "Enables the notify command without any pre-configured scope." + }, + { + "description": "Enables the permission_state command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-permission-state", + "markdownDescription": "Enables the permission_state command without any pre-configured scope." + }, + { + "description": "Enables the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-register-action-types", + "markdownDescription": "Enables the register_action_types command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_active command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-remove-active", + "markdownDescription": "Enables the remove_active command without any pre-configured scope." + }, + { + "description": "Enables the request_permission command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-request-permission", + "markdownDescription": "Enables the request_permission command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Denies the batch command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-batch", + "markdownDescription": "Denies the batch command without any pre-configured scope." + }, + { + "description": "Denies the cancel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-cancel", + "markdownDescription": "Denies the cancel command without any pre-configured scope." + }, + { + "description": "Denies the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-check-permissions", + "markdownDescription": "Denies the check_permissions command without any pre-configured scope." + }, + { + "description": "Denies the create_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-create-channel", + "markdownDescription": "Denies the create_channel command without any pre-configured scope." + }, + { + "description": "Denies the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-delete-channel", + "markdownDescription": "Denies the delete_channel command without any pre-configured scope." + }, + { + "description": "Denies the get_active command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-get-active", + "markdownDescription": "Denies the get_active command without any pre-configured scope." + }, + { + "description": "Denies the get_pending command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-get-pending", + "markdownDescription": "Denies the get_pending command without any pre-configured scope." + }, + { + "description": "Denies the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-is-permission-granted", + "markdownDescription": "Denies the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Denies the list_channels command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-list-channels", + "markdownDescription": "Denies the list_channels command without any pre-configured scope." + }, + { + "description": "Denies the notify command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-notify", + "markdownDescription": "Denies the notify command without any pre-configured scope." + }, + { + "description": "Denies the permission_state command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-permission-state", + "markdownDescription": "Denies the permission_state command without any pre-configured scope." + }, + { + "description": "Denies the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-register-action-types", + "markdownDescription": "Denies the register_action_types command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_active command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-remove-active", + "markdownDescription": "Denies the remove_active command without any pre-configured scope." + }, + { + "description": "Denies the request_permission command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-request-permission", + "markdownDescription": "Denies the request_permission command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "This permission set 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." + }, + { + "description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`", + "type": "string", + "const": "updater:default", + "markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`" + }, + { + "description": "Enables the check command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-check", + "markdownDescription": "Enables the check command without any pre-configured scope." + }, + { + "description": "Enables the download command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-download", + "markdownDescription": "Enables the download command without any pre-configured scope." + }, + { + "description": "Enables the download_and_install command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-download-and-install", + "markdownDescription": "Enables the download_and_install command without any pre-configured scope." + }, + { + "description": "Enables the install command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-install", + "markdownDescription": "Enables the install command without any pre-configured scope." + }, + { + "description": "Denies the check command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-check", + "markdownDescription": "Denies the check command without any pre-configured scope." + }, + { + "description": "Denies the download command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-download", + "markdownDescription": "Denies the download command without any pre-configured scope." + }, + { + "description": "Denies the download_and_install command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-download-and-install", + "markdownDescription": "Denies the download_and_install command without any pre-configured scope." + }, + { + "description": "Denies the install command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-install", + "markdownDescription": "Denies the install command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`", + "type": "string", + "const": "window-state:default", + "markdownDescription": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`" + }, + { + "description": "Enables the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-filename", + "markdownDescription": "Enables the filename command without any pre-configured scope." + }, + { + "description": "Enables the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-restore-state", + "markdownDescription": "Enables the restore_state command without any pre-configured scope." + }, + { + "description": "Enables the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-save-window-state", + "markdownDescription": "Enables the save_window_state command without any pre-configured scope." + }, + { + "description": "Denies the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-filename", + "markdownDescription": "Denies the filename command without any pre-configured scope." + }, + { + "description": "Denies the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-restore-state", + "markdownDescription": "Denies the restore_state command without any pre-configured scope." + }, + { + "description": "Denies the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-save-window-state", + "markdownDescription": "Denies the save_window_state command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "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/tauri/src-tauri/gen/schemas/macOS-schema.json b/tauri/src-tauri/gen/schemas/macOS-schema.json new file mode 100644 index 00000000..e93ad2c2 --- /dev/null +++ b/tauri/src-tauri/gen/schemas/macOS-schema.json @@ -0,0 +1,6590 @@ +{ + "$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 programatic 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 programatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", + "type": "string", + "const": "http:default", + "markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" + }, + { + "description": "Enables the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch", + "markdownDescription": "Enables the fetch command without any pre-configured scope." + }, + { + "description": "Enables the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-cancel", + "markdownDescription": "Enables the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Enables the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-read-body", + "markdownDescription": "Enables the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Enables the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-send", + "markdownDescription": "Enables the fetch_send command without any pre-configured scope." + }, + { + "description": "Denies the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch", + "markdownDescription": "Denies the fetch command without any pre-configured scope." + }, + { + "description": "Denies the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-cancel", + "markdownDescription": "Denies the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Denies the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-read-body", + "markdownDescription": "Denies the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Denies the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-send", + "markdownDescription": "Denies the fetch_send command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "HttpScopeEntry", + "description": "HTTP scope entry.", + "anyOf": [ + { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + }, + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "HttpScopeEntry", + "description": "HTTP scope entry.", + "anyOf": [ + { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + }, + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`", + "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`" + }, + { + "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 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 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 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 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 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 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_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_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_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_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_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_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-ask`\n- `allow-confirm`\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-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "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.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + }, + { + "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", + "type": "string", + "const": "http:default", + "markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" + }, + { + "description": "Enables the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch", + "markdownDescription": "Enables the fetch command without any pre-configured scope." + }, + { + "description": "Enables the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-cancel", + "markdownDescription": "Enables the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Enables the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-read-body", + "markdownDescription": "Enables the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Enables the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:allow-fetch-send", + "markdownDescription": "Enables the fetch_send command without any pre-configured scope." + }, + { + "description": "Denies the fetch command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch", + "markdownDescription": "Denies the fetch command without any pre-configured scope." + }, + { + "description": "Denies the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-cancel", + "markdownDescription": "Denies the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Denies the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-read-body", + "markdownDescription": "Denies the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Denies the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "http:deny-fetch-send", + "markdownDescription": "Denies the fetch_send command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`", + "type": "string", + "const": "notification:default", + "markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`" + }, + { + "description": "Enables the batch command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-batch", + "markdownDescription": "Enables the batch command without any pre-configured scope." + }, + { + "description": "Enables the cancel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-cancel", + "markdownDescription": "Enables the cancel command without any pre-configured scope." + }, + { + "description": "Enables the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-check-permissions", + "markdownDescription": "Enables the check_permissions command without any pre-configured scope." + }, + { + "description": "Enables the create_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-create-channel", + "markdownDescription": "Enables the create_channel command without any pre-configured scope." + }, + { + "description": "Enables the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-delete-channel", + "markdownDescription": "Enables the delete_channel command without any pre-configured scope." + }, + { + "description": "Enables the get_active command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-get-active", + "markdownDescription": "Enables the get_active command without any pre-configured scope." + }, + { + "description": "Enables the get_pending command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-get-pending", + "markdownDescription": "Enables the get_pending command without any pre-configured scope." + }, + { + "description": "Enables the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-is-permission-granted", + "markdownDescription": "Enables the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Enables the list_channels command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-list-channels", + "markdownDescription": "Enables the list_channels command without any pre-configured scope." + }, + { + "description": "Enables the notify command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-notify", + "markdownDescription": "Enables the notify command without any pre-configured scope." + }, + { + "description": "Enables the permission_state command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-permission-state", + "markdownDescription": "Enables the permission_state command without any pre-configured scope." + }, + { + "description": "Enables the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-register-action-types", + "markdownDescription": "Enables the register_action_types command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_active command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-remove-active", + "markdownDescription": "Enables the remove_active command without any pre-configured scope." + }, + { + "description": "Enables the request_permission command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-request-permission", + "markdownDescription": "Enables the request_permission command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "notification:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Denies the batch command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-batch", + "markdownDescription": "Denies the batch command without any pre-configured scope." + }, + { + "description": "Denies the cancel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-cancel", + "markdownDescription": "Denies the cancel command without any pre-configured scope." + }, + { + "description": "Denies the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-check-permissions", + "markdownDescription": "Denies the check_permissions command without any pre-configured scope." + }, + { + "description": "Denies the create_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-create-channel", + "markdownDescription": "Denies the create_channel command without any pre-configured scope." + }, + { + "description": "Denies the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-delete-channel", + "markdownDescription": "Denies the delete_channel command without any pre-configured scope." + }, + { + "description": "Denies the get_active command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-get-active", + "markdownDescription": "Denies the get_active command without any pre-configured scope." + }, + { + "description": "Denies the get_pending command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-get-pending", + "markdownDescription": "Denies the get_pending command without any pre-configured scope." + }, + { + "description": "Denies the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-is-permission-granted", + "markdownDescription": "Denies the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Denies the list_channels command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-list-channels", + "markdownDescription": "Denies the list_channels command without any pre-configured scope." + }, + { + "description": "Denies the notify command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-notify", + "markdownDescription": "Denies the notify command without any pre-configured scope." + }, + { + "description": "Denies the permission_state command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-permission-state", + "markdownDescription": "Denies the permission_state command without any pre-configured scope." + }, + { + "description": "Denies the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-register-action-types", + "markdownDescription": "Denies the register_action_types command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_active command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-remove-active", + "markdownDescription": "Denies the remove_active command without any pre-configured scope." + }, + { + "description": "Denies the request_permission command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-request-permission", + "markdownDescription": "Denies the request_permission command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "notification:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "This permission set 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." + }, + { + "description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`", + "type": "string", + "const": "updater:default", + "markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`" + }, + { + "description": "Enables the check command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-check", + "markdownDescription": "Enables the check command without any pre-configured scope." + }, + { + "description": "Enables the download command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-download", + "markdownDescription": "Enables the download command without any pre-configured scope." + }, + { + "description": "Enables the download_and_install command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-download-and-install", + "markdownDescription": "Enables the download_and_install command without any pre-configured scope." + }, + { + "description": "Enables the install command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-install", + "markdownDescription": "Enables the install command without any pre-configured scope." + }, + { + "description": "Denies the check command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-check", + "markdownDescription": "Denies the check command without any pre-configured scope." + }, + { + "description": "Denies the download command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-download", + "markdownDescription": "Denies the download command without any pre-configured scope." + }, + { + "description": "Denies the download_and_install command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-download-and-install", + "markdownDescription": "Denies the download_and_install command without any pre-configured scope." + }, + { + "description": "Denies the install command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-install", + "markdownDescription": "Denies the install command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`", + "type": "string", + "const": "window-state:default", + "markdownDescription": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`" + }, + { + "description": "Enables the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-filename", + "markdownDescription": "Enables the filename command without any pre-configured scope." + }, + { + "description": "Enables the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-restore-state", + "markdownDescription": "Enables the restore_state command without any pre-configured scope." + }, + { + "description": "Enables the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-save-window-state", + "markdownDescription": "Enables the save_window_state command without any pre-configured scope." + }, + { + "description": "Denies the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-filename", + "markdownDescription": "Denies the filename command without any pre-configured scope." + }, + { + "description": "Denies the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-restore-state", + "markdownDescription": "Denies the restore_state command without any pre-configured scope." + }, + { + "description": "Denies the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-save-window-state", + "markdownDescription": "Denies the save_window_state command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "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/tauri/src-tauri/icons/128x128.png b/tauri/src-tauri/icons/128x128.png new file mode 100644 index 00000000..c7c23200 Binary files /dev/null and b/tauri/src-tauri/icons/128x128.png differ diff --git a/tauri/src-tauri/icons/128x128@2x.png b/tauri/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..3b597184 Binary files /dev/null and b/tauri/src-tauri/icons/128x128@2x.png differ diff --git a/tauri/src-tauri/icons/32x32.png b/tauri/src-tauri/icons/32x32.png new file mode 100644 index 00000000..7a2cc1ae Binary files /dev/null and b/tauri/src-tauri/icons/32x32.png differ diff --git a/tauri/src-tauri/icons/icon.icns b/tauri/src-tauri/icons/icon.icns new file mode 100644 index 00000000..b715b5a2 Binary files /dev/null and b/tauri/src-tauri/icons/icon.icns differ diff --git a/tauri/src-tauri/icons/icon.ico b/tauri/src-tauri/icons/icon.ico new file mode 100644 index 00000000..e69de29b diff --git a/tauri/src-tauri/icons/icon.iconset/icon_128x128.png b/tauri/src-tauri/icons/icon.iconset/icon_128x128.png new file mode 100644 index 00000000..0412439c Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_128x128.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_128x128@2x.png b/tauri/src-tauri/icons/icon.iconset/icon_128x128@2x.png new file mode 100644 index 00000000..fa0b9768 Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_128x128@2x.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_16x16.png b/tauri/src-tauri/icons/icon.iconset/icon_16x16.png new file mode 100644 index 00000000..9e9973bd Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_16x16.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_16x16@2x.png b/tauri/src-tauri/icons/icon.iconset/icon_16x16@2x.png new file mode 100644 index 00000000..0153828b Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_16x16@2x.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_256x256.png b/tauri/src-tauri/icons/icon.iconset/icon_256x256.png new file mode 100644 index 00000000..fa0b9768 Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_256x256.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_256x256@2x.png b/tauri/src-tauri/icons/icon.iconset/icon_256x256@2x.png new file mode 100644 index 00000000..ba4bce20 Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_256x256@2x.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_32x32.png b/tauri/src-tauri/icons/icon.iconset/icon_32x32.png new file mode 100644 index 00000000..0153828b Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_32x32.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_32x32@2x.png b/tauri/src-tauri/icons/icon.iconset/icon_32x32@2x.png new file mode 100644 index 00000000..2983a208 Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_32x32@2x.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_512x512.png b/tauri/src-tauri/icons/icon.iconset/icon_512x512.png new file mode 100644 index 00000000..ba4bce20 Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_512x512.png differ diff --git a/tauri/src-tauri/icons/icon.iconset/icon_512x512@2x.png b/tauri/src-tauri/icons/icon.iconset/icon_512x512@2x.png new file mode 100644 index 00000000..1a5ee251 Binary files /dev/null and b/tauri/src-tauri/icons/icon.iconset/icon_512x512@2x.png differ diff --git a/tauri/src-tauri/icons/icon.png b/tauri/src-tauri/icons/icon.png new file mode 100644 index 00000000..1a5ee251 Binary files /dev/null and b/tauri/src-tauri/icons/icon.png differ diff --git a/tauri/src-tauri/icons/menu-bar-icon.png b/tauri/src-tauri/icons/menu-bar-icon.png new file mode 100644 index 00000000..9e9973bd Binary files /dev/null and b/tauri/src-tauri/icons/menu-bar-icon.png differ diff --git a/tauri/src-tauri/icons/menu-bar-icon@2x.png b/tauri/src-tauri/icons/menu-bar-icon@2x.png new file mode 100644 index 00000000..0153828b Binary files /dev/null and b/tauri/src-tauri/icons/menu-bar-icon@2x.png differ diff --git a/tauri/src-tauri/icons/tray-icon.png b/tauri/src-tauri/icons/tray-icon.png new file mode 100644 index 00000000..609fa9c8 Binary files /dev/null and b/tauri/src-tauri/icons/tray-icon.png differ diff --git a/tauri/src-tauri/icons/tray-icon@2x.png b/tauri/src-tauri/icons/tray-icon@2x.png new file mode 100644 index 00000000..609fa9c8 Binary files /dev/null and b/tauri/src-tauri/icons/tray-icon@2x.png differ diff --git a/tauri/src-tauri/rustfmt.toml b/tauri/src-tauri/rustfmt.toml new file mode 100644 index 00000000..b578ae89 --- /dev/null +++ b/tauri/src-tauri/rustfmt.toml @@ -0,0 +1,22 @@ +# Rust formatting configuration +edition = "2021" +max_width = 100 +tab_spaces = 4 +use_small_heuristics = "Default" +reorder_imports = true +reorder_modules = true +remove_nested_parens = true +match_arm_blocks = true +use_field_init_shorthand = true +use_try_shorthand = true +newline_style = "Auto" +format_code_in_doc_comments = true +wrap_comments = true +comment_width = 80 +normalize_comments = true +normalize_doc_attributes = true +format_strings = true +format_macro_matchers = true +format_macro_bodies = true +imports_granularity = "Crate" +group_imports = "StdExternalCrate" \ No newline at end of file diff --git a/tauri/src-tauri/src/api_client.rs b/tauri/src-tauri/src/api_client.rs new file mode 100644 index 00000000..b70ca1ea --- /dev/null +++ b/tauri/src-tauri/src/api_client.rs @@ -0,0 +1,182 @@ +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct ApiClient { + client: Client, + base_url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateSessionRequest { + pub name: Option, + pub rows: Option, + pub cols: Option, + pub cwd: Option, + pub env: Option>, + pub shell: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SessionResponse { + pub id: String, + pub name: String, + pub pid: u32, + pub rows: u16, + pub cols: u16, + pub created_at: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct InputRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub key: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ResizeRequest { + pub cols: u16, + pub rows: u16, +} + +impl ApiClient { + pub fn new(port: u16) -> Self { + Self { + client: Client::new(), + base_url: format!("http://127.0.0.1:{}", port), + } + } + + pub async fn create_session(&self, req: CreateSessionRequest) -> Result { + let url = format!("{}/api/sessions", self.base_url); + + let response = self.client + .post(&url) + .json(&req) + .send() + .await + .map_err(|e| format!("Failed to create session: {}", e))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(format!("Server returned error {}: {}", status, error_text)); + } + + response + .json::() + .await + .map_err(|e| format!("Failed to parse response: {}", e)) + } + + pub async fn list_sessions(&self) -> Result, String> { + let url = format!("{}/api/sessions", self.base_url); + + let response = self.client + .get(&url) + .send() + .await + .map_err(|e| format!("Failed to list sessions: {}", e))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(format!("Server returned error {}: {}", status, error_text)); + } + + response + .json::>() + .await + .map_err(|e| format!("Failed to parse response: {}", e)) + } + + pub async fn close_session(&self, id: &str) -> Result<(), String> { + let url = format!("{}/api/sessions/{}", self.base_url, id); + + let response = self.client + .delete(&url) + .send() + .await + .map_err(|e| format!("Failed to close session: {}", e))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(format!("Server returned error {}: {}", status, error_text)); + } + + Ok(()) + } + + pub async fn send_input(&self, id: &str, input: &[u8]) -> Result<(), String> { + let url = format!("{}/api/sessions/{}/input", self.base_url, id); + + // Convert bytes to string + let text = String::from_utf8_lossy(input).into_owned(); + let req = InputRequest { + text: Some(text), + key: None, + }; + + let response = self.client + .post(&url) + .json(&req) + .send() + .await + .map_err(|e| format!("Failed to send input: {}", e))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(format!("Server returned error {}: {}", status, error_text)); + } + + Ok(()) + } + + pub async fn resize_session(&self, id: &str, rows: u16, cols: u16) -> Result<(), String> { + let url = format!("{}/api/sessions/{}/resize", self.base_url, id); + + let req = ResizeRequest { cols, rows }; + + let response = self.client + .post(&url) + .json(&req) + .send() + .await + .map_err(|e| format!("Failed to resize session: {}", e))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(format!("Server returned error {}: {}", status, error_text)); + } + + Ok(()) + } + + pub async fn get_session_output(&self, id: &str) -> Result, String> { + let url = format!("{}/api/sessions/{}/buffer", self.base_url, id); + + let response = self.client + .get(&url) + .send() + .await + .map_err(|e| format!("Failed to get session output: {}", e))?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(format!("Server returned error {}: {}", status, error_text)); + } + + response + .bytes() + .await + .map(|b| b.to_vec()) + .map_err(|e| format!("Failed to read response: {}", e)) + } +} \ No newline at end of file diff --git a/tauri/src-tauri/src/api_testing.rs b/tauri/src-tauri/src/api_testing.rs new file mode 100644 index 00000000..a349f442 --- /dev/null +++ b/tauri/src-tauri/src/api_testing.rs @@ -0,0 +1,689 @@ +use chrono::{DateTime, Utc}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::RwLock; + +/// API test method +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum HttpMethod { + GET, + POST, + PUT, + PATCH, + DELETE, + HEAD, + OPTIONS, +} + +impl HttpMethod { + #[allow(dead_code)] + pub fn as_str(&self) -> &str { + match self { + HttpMethod::GET => "GET", + HttpMethod::POST => "POST", + HttpMethod::PUT => "PUT", + HttpMethod::PATCH => "PATCH", + HttpMethod::DELETE => "DELETE", + HttpMethod::HEAD => "HEAD", + HttpMethod::OPTIONS => "OPTIONS", + } + } +} + +/// API test assertion type +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum AssertionType { + StatusCode(u16), + StatusRange { + min: u16, + max: u16, + }, + ResponseTime { + max_ms: u64, + }, + HeaderExists(String), + HeaderEquals { + key: String, + value: String, + }, + JsonPath { + path: String, + expected: serde_json::Value, + }, + BodyContains(String), + BodyMatches(String), // Regex + ContentType(String), +} + +/// API test case +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITest { + pub id: String, + pub name: String, + pub description: Option, + pub group: Option, + pub endpoint_url: String, + pub method: HttpMethod, + pub headers: HashMap, + pub query_params: HashMap, + pub body: Option, + pub auth: Option, + pub assertions: Vec, + pub timeout_ms: u64, + pub retry_count: u32, + pub delay_ms: Option, + pub save_response: bool, +} + +/// API test body +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum APITestBody { + Json(serde_json::Value), + Form(HashMap), + Text(String), + Binary(Vec), +} + +/// API test authentication +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum APITestAuth { + Basic { + username: String, + password: String, + }, + Bearer(String), + ApiKey { + key: String, + value: String, + in_header: bool, + }, + Custom(HashMap), +} + +/// API test result +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestResult { + pub test_id: String, + pub test_name: String, + pub success: bool, + pub timestamp: DateTime, + pub duration_ms: u64, + pub status_code: Option, + pub response_headers: HashMap, + pub response_body: Option, + pub assertion_results: Vec, + pub error: Option, + pub retries_used: u32, +} + +/// Assertion result +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AssertionResult { + pub assertion: AssertionType, + pub passed: bool, + pub actual_value: Option, + pub error_message: Option, +} + +/// API test suite +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestSuite { + pub id: String, + pub name: String, + pub description: Option, + pub base_url: Option, + pub default_headers: HashMap, + pub default_auth: Option, + pub tests: Vec, + pub setup_tests: Vec, + pub teardown_tests: Vec, + pub variables: HashMap, +} + +/// API test collection +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestCollection { + pub id: String, + pub name: String, + pub suites: Vec, + pub global_variables: HashMap, +} + +/// API test runner configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestRunnerConfig { + pub parallel_execution: bool, + pub max_parallel_tests: usize, + pub stop_on_failure: bool, + pub capture_responses: bool, + pub follow_redirects: bool, + pub verify_ssl: bool, + pub proxy: Option, + pub environment_variables: HashMap, +} + +impl Default for APITestRunnerConfig { + fn default() -> Self { + Self { + parallel_execution: false, + max_parallel_tests: 5, + stop_on_failure: false, + capture_responses: true, + follow_redirects: true, + verify_ssl: true, + proxy: None, + environment_variables: HashMap::new(), + } + } +} + +/// API test history entry +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestHistoryEntry { + pub run_id: String, + pub timestamp: DateTime, + pub suite_name: String, + pub total_tests: usize, + pub passed_tests: usize, + pub failed_tests: usize, + pub total_duration_ms: u64, + pub results: Vec, +} + +/// API testing manager +pub struct APITestingManager { + client: Arc, + config: Arc>, + test_suites: Arc>>, + test_history: Arc>>, + running_tests: Arc>>, + shared_variables: Arc>>, + notification_manager: Option>, +} + +impl Default for APITestingManager { + fn default() -> Self { + Self::new() + } +} + +impl APITestingManager { + /// Create a new API testing manager + pub fn new() -> Self { + let client = Client::builder() + .timeout(Duration::from_secs(30)) + .build() + .unwrap(); + + Self { + client: Arc::new(client), + config: Arc::new(RwLock::new(APITestRunnerConfig::default())), + test_suites: Arc::new(RwLock::new(HashMap::new())), + test_history: Arc::new(RwLock::new(Vec::new())), + running_tests: Arc::new(RwLock::new(HashMap::new())), + shared_variables: Arc::new(RwLock::new(HashMap::new())), + notification_manager: None, + } + } + + /// Set the notification manager + pub fn set_notification_manager( + &mut self, + notification_manager: Arc, + ) { + self.notification_manager = Some(notification_manager); + } + + /// Get configuration + pub async fn get_config(&self) -> APITestRunnerConfig { + self.config.read().await.clone() + } + + /// Update configuration + pub async fn update_config(&self, config: APITestRunnerConfig) { + *self.config.write().await = config; + } + + /// Add test suite + pub async fn add_test_suite(&self, suite: APITestSuite) { + self.test_suites + .write() + .await + .insert(suite.id.clone(), suite); + } + + /// Get test suite + pub async fn get_test_suite(&self, suite_id: &str) -> Option { + self.test_suites.read().await.get(suite_id).cloned() + } + + /// List test suites + pub async fn list_test_suites(&self) -> Vec { + self.test_suites.read().await.values().cloned().collect() + } + + /// Run single test + pub async fn run_test( + &self, + test: &APITest, + variables: &HashMap, + ) -> APITestResult { + let start_time = std::time::Instant::now(); + let mut result = APITestResult { + test_id: test.id.clone(), + test_name: test.name.clone(), + success: false, + timestamp: Utc::now(), + duration_ms: 0, + status_code: None, + response_headers: HashMap::new(), + response_body: None, + assertion_results: Vec::new(), + error: None, + retries_used: 0, + }; + + // Replace variables in URL + let url = self.replace_variables(&test.endpoint_url, variables); + + // Run test with retries + let mut last_error = None; + for retry in 0..=test.retry_count { + if retry > 0 { + // Delay between retries + if let Some(delay) = test.delay_ms { + tokio::time::sleep(Duration::from_millis(delay)).await; + } + } + + match self.execute_request(test, &url, variables).await { + Ok((status, headers, body)) => { + result.status_code = Some(status); + result.response_headers = headers; + if test.save_response { + result.response_body = Some(body.clone()); + } + result.retries_used = retry; + + // Run assertions + result.assertion_results = self + .run_assertions(&test.assertions, status, &result.response_headers, &body) + .await; + result.success = result.assertion_results.iter().all(|a| a.passed); + + break; + } + Err(e) => { + last_error = Some(e); + } + } + } + + if let Some(error) = last_error { + result.error = Some(error); + } + + result.duration_ms = start_time.elapsed().as_millis() as u64; + result + } + + /// Run test suite + pub async fn run_test_suite(&self, suite_id: &str) -> Option { + let suite = self.get_test_suite(suite_id).await?; + let run_id = uuid::Uuid::new_v4().to_string(); + let start_time = std::time::Instant::now(); + + // Merge variables + let mut variables = self.shared_variables.read().await.clone(); + variables.extend(suite.variables.clone()); + + let mut results = Vec::new(); + + // Run setup tests + for test in &suite.setup_tests { + let result = self.run_test(test, &variables).await; + if !result.success && self.config.read().await.stop_on_failure { + break; + } + results.push(result); + } + + // Run main tests + let config = self.config.read().await; + if config.parallel_execution { + // Run tests in parallel + let mut tasks = Vec::new(); + for test in &suite.tests { + let test = test.clone(); + let vars = variables.clone(); + let manager = self.clone_for_parallel(); + + tasks.push(tokio::spawn( + async move { manager.run_test(&test, &vars).await }, + )); + } + + for task in tasks { + if let Ok(result) = task.await { + results.push(result); + } + } + } else { + // Run tests sequentially + for test in &suite.tests { + let result = self.run_test(test, &variables).await; + if !result.success && config.stop_on_failure { + break; + } + results.push(result); + } + } + + // Run teardown tests + for test in &suite.teardown_tests { + let result = self.run_test(test, &variables).await; + results.push(result); + } + + let total_duration = start_time.elapsed().as_millis() as u64; + let passed = results.iter().filter(|r| r.success).count(); + let failed = results.len() - passed; + + let history_entry = APITestHistoryEntry { + run_id, + timestamp: Utc::now(), + suite_name: suite.name, + total_tests: results.len(), + passed_tests: passed, + failed_tests: failed, + total_duration_ms: total_duration, + results, + }; + + // Store in history + self.test_history.write().await.push(history_entry.clone()); + + // Send notification + if let Some(notification_manager) = &self.notification_manager { + let message = format!("Test suite completed: {} passed, {} failed", passed, failed); + let _ = notification_manager + .notify_success("API Tests", &message) + .await; + } + + Some(history_entry) + } + + /// Get test history + pub async fn get_test_history(&self, limit: Option) -> Vec { + let history = self.test_history.read().await; + match limit { + Some(n) => history.iter().rev().take(n).cloned().collect(), + None => history.clone(), + } + } + + /// Clear test history + pub async fn clear_test_history(&self) { + self.test_history.write().await.clear(); + } + + /// Import Postman collection + pub async fn import_postman_collection(&self, _json_data: &str) -> Result { + // TODO: Implement Postman collection import + Err("Postman import not yet implemented".to_string()) + } + + /// Export test suite + pub async fn export_test_suite(&self, suite_id: &str) -> Result { + let suite = self + .get_test_suite(suite_id) + .await + .ok_or_else(|| "Test suite not found".to_string())?; + + serde_json::to_string_pretty(&suite) + .map_err(|e| format!("Failed to serialize test suite: {}", e)) + } + + // Helper methods + async fn execute_request( + &self, + test: &APITest, + url: &str, + variables: &HashMap, + ) -> Result<(u16, HashMap, String), String> { + let config = self.config.read().await; + let client = Client::builder() + .timeout(Duration::from_millis(test.timeout_ms)) + .redirect(if config.follow_redirects { + reqwest::redirect::Policy::default() + } else { + reqwest::redirect::Policy::none() + }) + .danger_accept_invalid_certs(!config.verify_ssl) + .build() + .map_err(|e| e.to_string())?; + + let mut request = match test.method { + HttpMethod::GET => client.get(url), + HttpMethod::POST => client.post(url), + HttpMethod::PUT => client.put(url), + HttpMethod::PATCH => client.patch(url), + HttpMethod::DELETE => client.delete(url), + HttpMethod::HEAD => client.head(url), + HttpMethod::OPTIONS => client.request(reqwest::Method::OPTIONS, url), + }; + + // Add headers + for (key, value) in &test.headers { + let value = self.replace_variables(value, variables); + request = request.header(key, value); + } + + // Add query params + for (key, value) in &test.query_params { + let value = self.replace_variables(value, variables); + request = request.query(&[(key, value)]); + } + + // Add auth + if let Some(auth) = &test.auth { + request = self.apply_auth(request, auth, variables); + } + + // Add body + if let Some(body) = &test.body { + request = match body { + APITestBody::Json(json) => request.json(json), + APITestBody::Form(form) => request.form(form), + APITestBody::Text(text) => request.body(text.clone()), + APITestBody::Binary(bytes) => request.body(bytes.clone()), + }; + } + + // Execute request + let response = request.send().await.map_err(|e| e.to_string())?; + let status = response.status().as_u16(); + + let mut headers = HashMap::new(); + for (key, value) in response.headers() { + if let Ok(value_str) = value.to_str() { + headers.insert(key.to_string(), value_str.to_string()); + } + } + + let body = response.text().await.unwrap_or_default(); + + Ok((status, headers, body)) + } + + async fn run_assertions( + &self, + assertions: &[AssertionType], + status: u16, + headers: &HashMap, + body: &str, + ) -> Vec { + let mut results = Vec::new(); + + for assertion in assertions { + let result = match assertion { + AssertionType::StatusCode(expected) => AssertionResult { + assertion: assertion.clone(), + passed: status == *expected, + actual_value: Some(status.to_string()), + error_message: if status != *expected { + Some(format!("Expected status {}, got {}", expected, status)) + } else { + None + }, + }, + AssertionType::StatusRange { min, max } => AssertionResult { + assertion: assertion.clone(), + passed: status >= *min && status <= *max, + actual_value: Some(status.to_string()), + error_message: if status < *min || status > *max { + Some(format!( + "Expected status between {} and {}, got {}", + min, max, status + )) + } else { + None + }, + }, + AssertionType::HeaderExists(key) => AssertionResult { + assertion: assertion.clone(), + passed: headers.contains_key(key), + actual_value: None, + error_message: if !headers.contains_key(key) { + Some(format!("Header '{}' not found", key)) + } else { + None + }, + }, + AssertionType::HeaderEquals { key, value } => { + let actual = headers.get(key); + AssertionResult { + assertion: assertion.clone(), + passed: actual == Some(value), + actual_value: actual.cloned(), + error_message: if actual != Some(value) { + Some(format!( + "Header '{}' expected '{}', got '{:?}'", + key, value, actual + )) + } else { + None + }, + } + } + AssertionType::BodyContains(text) => AssertionResult { + assertion: assertion.clone(), + passed: body.contains(text), + actual_value: None, + error_message: if !body.contains(text) { + Some(format!("Body does not contain '{}'", text)) + } else { + None + }, + }, + AssertionType::JsonPath { + path: _, + expected: _, + } => { + // TODO: Implement JSON path assertion + AssertionResult { + assertion: assertion.clone(), + passed: false, + actual_value: None, + error_message: Some("JSON path assertions not yet implemented".to_string()), + } + } + _ => AssertionResult { + assertion: assertion.clone(), + passed: false, + actual_value: None, + error_message: Some("Assertion type not implemented".to_string()), + }, + }; + results.push(result); + } + + results + } + + fn replace_variables(&self, text: &str, variables: &HashMap) -> String { + let mut result = text.to_string(); + for (key, value) in variables { + result = result.replace(&format!("{{{{{}}}}}", key), value); + } + result + } + + fn apply_auth( + &self, + request: reqwest::RequestBuilder, + auth: &APITestAuth, + variables: &HashMap, + ) -> reqwest::RequestBuilder { + match auth { + APITestAuth::Basic { username, password } => { + let username = self.replace_variables(username, variables); + let password = self.replace_variables(password, variables); + request.basic_auth(username, Some(password)) + } + APITestAuth::Bearer(token) => { + let token = self.replace_variables(token, variables); + request.bearer_auth(token) + } + APITestAuth::ApiKey { + key, + value, + in_header, + } => { + let key = self.replace_variables(key, variables); + let value = self.replace_variables(value, variables); + if *in_header { + request.header(key, value) + } else { + request.query(&[(key, value)]) + } + } + APITestAuth::Custom(headers) => { + let mut req = request; + for (key, value) in headers { + let value = self.replace_variables(value, variables); + req = req.header(key, value); + } + req + } + } + } + + fn clone_for_parallel(&self) -> Self { + Self { + client: self.client.clone(), + config: self.config.clone(), + test_suites: self.test_suites.clone(), + test_history: self.test_history.clone(), + running_tests: self.running_tests.clone(), + shared_variables: self.shared_variables.clone(), + notification_manager: self.notification_manager.clone(), + } + } +} + +/// API test statistics +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestStatistics { + pub total_suites: usize, + pub total_tests: usize, + pub total_runs: usize, + pub success_rate: f64, + pub average_duration_ms: f64, + pub most_failed_tests: Vec<(String, usize)>, + pub slowest_tests: Vec<(String, u64)>, +} diff --git a/tauri/src-tauri/src/app_mover.rs b/tauri/src-tauri/src/app_mover.rs new file mode 100644 index 00000000..c4a00366 --- /dev/null +++ b/tauri/src-tauri/src/app_mover.rs @@ -0,0 +1,175 @@ +use std::path::PathBuf; +use tauri::AppHandle; + +/// Check if the app should be moved to Applications folder +/// This is a macOS-specific feature +#[cfg(target_os = "macos")] +pub async fn check_and_prompt_move(app_handle: AppHandle) -> Result<(), String> { + // Get current app bundle path + let bundle_path = get_app_bundle_path()?; + + // Check if already in Applications folder + if is_in_applications_folder(&bundle_path) { + return Ok(()); + } + + // Check if we've already asked this question + let settings = crate::settings::Settings::load().unwrap_or_default(); + if let Some(asked) = settings.general.prompt_move_to_applications { + if !asked { + // User has already been asked, don't ask again + return Ok(()); + } + } + + // Show dialog to ask user if they want to move the app + use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; + + let response = app_handle.dialog() + .message("VibeTunnel works best when run from the Applications folder. Would you like to move it there now?\n\nClick OK to move it now, or Cancel to skip.") + .title("Move to Applications?") + .kind(MessageDialogKind::Info) + .blocking_show(); + + if response { + // User wants to move the app + move_to_applications_folder(bundle_path)?; + + // Show success message + app_handle + .dialog() + .message("VibeTunnel has been moved to your Applications folder and will restart.") + .title("Move Complete") + .kind(MessageDialogKind::Info) + .blocking_show(); + + // Restart the app from the new location + restart_from_applications()?; + } + + // Update settings to not ask again + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + settings.general.prompt_move_to_applications = Some(false); + settings.save().ok(); + + Ok(()) +} + +#[cfg(not(target_os = "macos"))] +pub async fn check_and_prompt_move(_app_handle: AppHandle) -> Result<(), String> { + // Not applicable on other platforms + Ok(()) +} + +#[cfg(target_os = "macos")] +fn get_app_bundle_path() -> Result { + use std::env; + + // Get the executable path + let exe_path = + env::current_exe().map_err(|e| format!("Failed to get executable path: {}", e))?; + + // Navigate up to the .app bundle + // Typical structure: /path/to/VibeTunnel.app/Contents/MacOS/VibeTunnel + let mut bundle_path = exe_path; + + // Go up three levels to reach the .app bundle + for _ in 0..3 { + bundle_path = bundle_path + .parent() + .ok_or("Failed to find app bundle")? + .to_path_buf(); + } + + // Verify this is an .app bundle + if !bundle_path.to_string_lossy().ends_with(".app") { + return Err("Not running from an app bundle".to_string()); + } + + Ok(bundle_path) +} + +#[cfg(target_os = "macos")] +fn is_in_applications_folder(bundle_path: &PathBuf) -> bool { + let path_str = bundle_path.to_string_lossy(); + path_str.contains("/Applications/") || path_str.contains("/System/Applications/") +} + +#[cfg(target_os = "macos")] +fn move_to_applications_folder(bundle_path: PathBuf) -> Result<(), String> { + use std::fs; + use std::process::Command; + + let app_name = bundle_path + .file_name() + .ok_or("Failed to get app name")? + .to_string_lossy(); + + let dest_path = PathBuf::from("/Applications").join(app_name.as_ref()); + + // Check if destination already exists + if dest_path.exists() { + // For now, just remove the existing app + // TODO: Implement dialog using tauri-plugin-dialog + + // Remove existing app + fs::remove_dir_all(&dest_path) + .map_err(|e| format!("Failed to remove existing app: {}", e))?; + } + + // Use AppleScript to move the app with proper permissions + let script = format!( + r#"tell application "Finder" + move (POSIX file "{}") to (POSIX file "/Applications/") with replacing + end tell"#, + bundle_path.to_string_lossy() + ); + + let output = Command::new("osascript") + .arg("-e") + .arg(script) + .output() + .map_err(|e| format!("Failed to execute move command: {}", e))?; + + if !output.status.success() { + let error = String::from_utf8_lossy(&output.stderr); + return Err(format!("Failed to move app: {}", error)); + } + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn restart_from_applications() -> Result<(), String> { + use std::process::Command; + + // Launch the app from the Applications folder + let _output = Command::new("open") + .arg("-n") + .arg("/Applications/VibeTunnel.app") + .spawn() + .map_err(|e| format!("Failed to restart app: {}", e))?; + + // Exit the current instance + std::process::exit(0); +} + +#[tauri::command] +pub async fn prompt_move_to_applications(app_handle: AppHandle) -> Result<(), String> { + check_and_prompt_move(app_handle).await +} + +#[tauri::command] +pub async fn is_in_applications_folder_command() -> Result { + #[cfg(target_os = "macos")] + { + let bundle_path = get_app_bundle_path()?; + Ok(is_in_applications_folder(&bundle_path)) + } + + #[cfg(not(target_os = "macos"))] + { + // Always return true on non-macOS platforms + Ok(true) + } +} diff --git a/tauri/src-tauri/src/auth_cache.rs b/tauri/src-tauri/src/auth_cache.rs new file mode 100644 index 00000000..dc31e069 --- /dev/null +++ b/tauri/src-tauri/src/auth_cache.rs @@ -0,0 +1,505 @@ +use chrono::{DateTime, Duration, Utc}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +/// Authentication token type +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum TokenType { + Bearer, + Basic, + ApiKey, + OAuth2, + JWT, + Custom, +} + +/// Authentication scope +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct AuthScope { + pub service: String, + pub resource: Option, + pub permissions: Vec, +} + +/// Cached authentication token +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CachedToken { + pub token_type: TokenType, + pub token_value: String, + pub scope: AuthScope, + pub created_at: DateTime, + pub expires_at: Option>, + pub refresh_token: Option, + pub metadata: HashMap, +} + +impl CachedToken { + /// Check if token is expired + pub fn is_expired(&self) -> bool { + if let Some(expires_at) = self.expires_at { + Utc::now() >= expires_at + } else { + false + } + } + + /// Check if token needs refresh (expires within threshold) + pub fn needs_refresh(&self, threshold_seconds: i64) -> bool { + if let Some(expires_at) = self.expires_at { + let refresh_time = expires_at - Duration::seconds(threshold_seconds); + Utc::now() >= refresh_time + } else { + false + } + } + + /// Get remaining lifetime in seconds + #[allow(dead_code)] + pub fn remaining_lifetime_seconds(&self) -> Option { + self.expires_at.map(|expires_at| { + let duration = expires_at - Utc::now(); + duration.num_seconds().max(0) + }) + } +} + +/// Authentication credential +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuthCredential { + pub credential_type: String, + pub username: Option, + pub password_hash: Option, // Store hashed password + pub api_key: Option, + pub client_id: Option, + pub client_secret: Option, + pub metadata: HashMap, +} + +/// Authentication cache entry +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuthCacheEntry { + pub key: String, + pub tokens: Vec, + pub credential: Option, + pub last_accessed: DateTime, + pub access_count: u64, +} + +/// Authentication cache configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuthCacheConfig { + pub enabled: bool, + pub max_entries: usize, + pub default_ttl_seconds: u64, + pub refresh_threshold_seconds: i64, + pub persist_to_disk: bool, + pub encryption_enabled: bool, + pub cleanup_interval_seconds: u64, +} + +impl Default for AuthCacheConfig { + fn default() -> Self { + Self { + enabled: true, + max_entries: 1000, + default_ttl_seconds: 3600, // 1 hour + refresh_threshold_seconds: 300, // 5 minutes + persist_to_disk: false, + encryption_enabled: true, + cleanup_interval_seconds: 600, // 10 minutes + } + } +} + +/// Authentication cache statistics +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuthCacheStats { + pub total_entries: usize, + pub total_tokens: usize, + pub expired_tokens: usize, + pub cache_hits: u64, + pub cache_misses: u64, + pub refresh_count: u64, + pub eviction_count: u64, +} + +/// Token refresh callback +pub type TokenRefreshCallback = Arc< + dyn Fn(CachedToken) -> futures::future::BoxFuture<'static, Result> + + Send + + Sync, +>; + +/// Authentication cache manager +pub struct AuthCacheManager { + config: Arc>, + cache: Arc>>, + stats: Arc>, + refresh_callbacks: Arc>>, + cleanup_handle: Arc>>>, + notification_manager: Option>, +} + +impl Default for AuthCacheManager { + fn default() -> Self { + Self::new() + } +} + +impl AuthCacheManager { + /// Create a new authentication cache manager + pub fn new() -> Self { + let manager = Self { + config: Arc::new(RwLock::new(AuthCacheConfig::default())), + cache: Arc::new(RwLock::new(HashMap::new())), + stats: Arc::new(RwLock::new(AuthCacheStats { + total_entries: 0, + total_tokens: 0, + expired_tokens: 0, + cache_hits: 0, + cache_misses: 0, + refresh_count: 0, + eviction_count: 0, + })), + refresh_callbacks: Arc::new(RwLock::new(HashMap::new())), + cleanup_handle: Arc::new(RwLock::new(None)), + notification_manager: None, + }; + + manager + } + + /// Set the notification manager + pub fn set_notification_manager( + &mut self, + notification_manager: Arc, + ) { + self.notification_manager = Some(notification_manager); + } + + /// Get configuration + pub async fn get_config(&self) -> AuthCacheConfig { + self.config.read().await.clone() + } + + /// Update configuration + pub async fn update_config(&self, config: AuthCacheConfig) { + *self.config.write().await = config; + } + + /// Store token in cache + pub async fn store_token(&self, key: &str, token: CachedToken) -> Result<(), String> { + let config = self.config.read().await; + if !config.enabled { + return Ok(()); + } + + let mut cache = self.cache.write().await; + let mut stats = self.stats.write().await; + + // Get or create cache entry + let entry = cache.entry(key.to_string()).or_insert_with(|| { + stats.total_entries += 1; + AuthCacheEntry { + key: key.to_string(), + tokens: Vec::new(), + credential: None, + last_accessed: Utc::now(), + access_count: 0, + } + }); + + // Remove expired tokens + let expired_count = entry.tokens.iter().filter(|t| t.is_expired()).count(); + stats.expired_tokens += expired_count; + entry.tokens.retain(|t| !t.is_expired()); + + // Add new token + entry.tokens.push(token); + stats.total_tokens += 1; + entry.last_accessed = Utc::now(); + + // Check cache size limit + if cache.len() > config.max_entries { + self.evict_oldest_entry(&mut cache, &mut stats); + } + + Ok(()) + } + + /// Get token from cache + pub async fn get_token(&self, key: &str, scope: &AuthScope) -> Option { + let config = self.config.read().await; + if !config.enabled { + return None; + } + + let mut cache = self.cache.write().await; + let mut stats = self.stats.write().await; + + if let Some(entry) = cache.get_mut(key) { + entry.last_accessed = Utc::now(); + entry.access_count += 1; + + // Find matching token + for token in &entry.tokens { + if !token.is_expired() && self.token_matches_scope(token, scope) { + stats.cache_hits += 1; + + // Check if needs refresh + if token.needs_refresh(config.refresh_threshold_seconds) { + // Trigger refresh in background + if let Some(refresh_callback) = self.refresh_callbacks.read().await.get(key) + { + let token_clone = token.clone(); + let callback = refresh_callback.clone(); + let key_clone = key.to_string(); + let manager = self.clone_for_refresh(); + + tokio::spawn(async move { + if let Ok(refreshed_token) = callback(token_clone).await { + let _ = manager.store_token(&key_clone, refreshed_token).await; + manager.stats.write().await.refresh_count += 1; + } + }); + } + } + + return Some(token.clone()); + } + } + } + + stats.cache_misses += 1; + None + } + + /// Store credential in cache + pub async fn store_credential( + &self, + key: &str, + credential: AuthCredential, + ) -> Result<(), String> { + let config = self.config.read().await; + if !config.enabled { + return Ok(()); + } + + let mut cache = self.cache.write().await; + let mut stats = self.stats.write().await; + + let entry = cache.entry(key.to_string()).or_insert_with(|| { + stats.total_entries += 1; + AuthCacheEntry { + key: key.to_string(), + tokens: Vec::new(), + credential: None, + last_accessed: Utc::now(), + access_count: 0, + } + }); + + entry.credential = Some(credential); + entry.last_accessed = Utc::now(); + + Ok(()) + } + + /// Get credential from cache + pub async fn get_credential(&self, key: &str) -> Option { + let config = self.config.read().await; + if !config.enabled { + return None; + } + + let mut cache = self.cache.write().await; + + if let Some(entry) = cache.get_mut(key) { + entry.last_accessed = Utc::now(); + entry.access_count += 1; + return entry.credential.clone(); + } + + None + } + + /// Register token refresh callback + #[allow(dead_code)] + pub async fn register_refresh_callback(&self, key: &str, callback: TokenRefreshCallback) { + self.refresh_callbacks + .write() + .await + .insert(key.to_string(), callback); + } + + /// Clear specific cache entry + pub async fn clear_entry(&self, key: &str) { + let mut cache = self.cache.write().await; + if cache.remove(key).is_some() { + self.stats.write().await.total_entries = cache.len(); + } + } + + /// Clear all cache entries + pub async fn clear_all(&self) { + let mut cache = self.cache.write().await; + cache.clear(); + + let mut stats = self.stats.write().await; + stats.total_entries = 0; + stats.total_tokens = 0; + stats.expired_tokens = 0; + } + + /// Get cache statistics + pub async fn get_stats(&self) -> AuthCacheStats { + self.stats.read().await.clone() + } + + /// List all cache entries + pub async fn list_entries(&self) -> Vec<(String, DateTime, u64)> { + self.cache + .read() + .await + .values() + .map(|entry| (entry.key.clone(), entry.last_accessed, entry.access_count)) + .collect() + } + + /// Export cache to JSON (for persistence) + pub async fn export_cache(&self) -> Result { + let cache = self.cache.read().await; + let entries: Vec<_> = cache.values().cloned().collect(); + + serde_json::to_string_pretty(&entries) + .map_err(|e| format!("Failed to serialize cache: {}", e)) + } + + /// Import cache from JSON + pub async fn import_cache(&self, json_data: &str) -> Result<(), String> { + let entries: Vec = serde_json::from_str(json_data) + .map_err(|e| format!("Failed to deserialize cache: {}", e))?; + + let mut cache = self.cache.write().await; + let mut stats = self.stats.write().await; + + for entry in entries { + cache.insert(entry.key.clone(), entry); + } + + stats.total_entries = cache.len(); + stats.total_tokens = cache.values().map(|e| e.tokens.len()).sum(); + + Ok(()) + } + + /// Hash password for secure storage + pub fn hash_password(password: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(password.as_bytes()); + format!("{:x}", hasher.finalize()) + } + + // Helper methods + fn token_matches_scope(&self, token: &CachedToken, scope: &AuthScope) -> bool { + token.scope.service == scope.service + && token.scope.resource == scope.resource + && scope + .permissions + .iter() + .all(|p| token.scope.permissions.contains(p)) + } + + fn evict_oldest_entry( + &self, + cache: &mut HashMap, + stats: &mut AuthCacheStats, + ) { + if let Some((key, _)) = cache.iter().min_by_key(|(_, entry)| entry.last_accessed) { + let key = key.clone(); + cache.remove(&key); + stats.eviction_count += 1; + stats.total_entries = cache.len(); + } + } + + pub async fn start_cleanup_task(&self) { + let config = self.config.read().await; + let cleanup_interval = Duration::seconds(config.cleanup_interval_seconds as i64); + drop(config); + + loop { + tokio::time::sleep(cleanup_interval.to_std().unwrap()).await; + + let config = self.config.read().await; + if !config.enabled { + continue; + } + drop(config); + + // Clean up expired tokens + let mut cache = self.cache.write().await; + let mut stats = self.stats.write().await; + let mut total_expired = 0; + + for entry in cache.values_mut() { + let expired_count = entry.tokens.iter().filter(|t| t.is_expired()).count(); + total_expired += expired_count; + entry.tokens.retain(|t| !t.is_expired()); + } + + stats.expired_tokens += total_expired; + stats.total_tokens = cache.values().map(|e| e.tokens.len()).sum(); + + // Remove empty entries + cache.retain(|_, entry| !entry.tokens.is_empty() || entry.credential.is_some()); + stats.total_entries = cache.len(); + } + } + + #[allow(dead_code)] + fn clone_for_cleanup(&self) -> Self { + Self { + config: self.config.clone(), + cache: self.cache.clone(), + stats: self.stats.clone(), + refresh_callbacks: self.refresh_callbacks.clone(), + cleanup_handle: self.cleanup_handle.clone(), + notification_manager: self.notification_manager.clone(), + } + } + + fn clone_for_refresh(&self) -> Self { + Self { + config: self.config.clone(), + cache: self.cache.clone(), + stats: self.stats.clone(), + refresh_callbacks: self.refresh_callbacks.clone(), + cleanup_handle: self.cleanup_handle.clone(), + notification_manager: self.notification_manager.clone(), + } + } +} + +/// Create a cache key from components +pub fn create_cache_key(service: &str, username: Option<&str>, resource: Option<&str>) -> String { + let mut components = vec![service]; + if let Some(user) = username { + components.push(user); + } + if let Some(res) = resource { + components.push(res); + } + components.join(":") +} + +/// Authentication cache error +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuthCacheError { + pub code: String, + pub message: String, + pub details: Option>, +} diff --git a/tauri/src-tauri/src/auto_launch.rs b/tauri/src-tauri/src/auto_launch.rs new file mode 100644 index 00000000..511f0dfc --- /dev/null +++ b/tauri/src-tauri/src/auto_launch.rs @@ -0,0 +1,78 @@ +use crate::state::AppState; +use auto_launch::AutoLaunchBuilder; +use tauri::State; + +fn get_app_path() -> String { + let exe_path = std::env::current_exe().unwrap(); + + // On macOS, we need to use the .app bundle path, not the executable inside it + #[cfg(target_os = "macos")] + { + // The executable is at: /path/to/VibeTunnel.app/Contents/MacOS/VibeTunnel + // We need: /path/to/VibeTunnel.app + if let Some(macos_dir) = exe_path.parent() { + if let Some(contents_dir) = macos_dir.parent() { + if let Some(app_bundle) = contents_dir.parent() { + if app_bundle.to_string_lossy().ends_with(".app") { + return app_bundle.to_string_lossy().to_string(); + } + } + } + } + } + + // For other platforms or if we couldn't find the .app bundle, use the executable path + exe_path.to_string_lossy().to_string() +} + +pub fn enable_auto_launch() -> Result<(), String> { + let auto = AutoLaunchBuilder::new() + .set_app_name("VibeTunnel") + .set_app_path(&get_app_path()) + .set_args(&["--auto-launch"]) + .build() + .map_err(|e| format!("Failed to build auto-launch: {}", e))?; + + auto.enable() + .map_err(|e| format!("Failed to enable auto-launch: {}", e))?; + + Ok(()) +} + +pub fn disable_auto_launch() -> Result<(), String> { + let auto = AutoLaunchBuilder::new() + .set_app_name("VibeTunnel") + .set_app_path(&get_app_path()) + .build() + .map_err(|e| format!("Failed to build auto-launch: {}", e))?; + + auto.disable() + .map_err(|e| format!("Failed to disable auto-launch: {}", e))?; + + Ok(()) +} + +pub fn is_auto_launch_enabled() -> Result { + let auto = AutoLaunchBuilder::new() + .set_app_name("VibeTunnel") + .set_app_path(&get_app_path()) + .build() + .map_err(|e| format!("Failed to build auto-launch: {}", e))?; + + auto.is_enabled() + .map_err(|e| format!("Failed to check auto-launch status: {}", e)) +} + +#[tauri::command] +pub async fn set_auto_launch(enabled: bool, _state: State<'_, AppState>) -> Result<(), String> { + if enabled { + enable_auto_launch() + } else { + disable_auto_launch() + } +} + +#[tauri::command] +pub async fn get_auto_launch(_state: State<'_, AppState>) -> Result { + is_auto_launch_enabled() +} diff --git a/tauri/src-tauri/src/backend_manager.rs b/tauri/src-tauri/src/backend_manager.rs new file mode 100644 index 00000000..215a4c1c --- /dev/null +++ b/tauri/src-tauri/src/backend_manager.rs @@ -0,0 +1,476 @@ +use std::path::PathBuf; +use std::process::Stdio; +use std::sync::Arc; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::{Child, Command}; +use tokio::sync::RwLock; +use tracing::{error, info, warn}; + +/// Server state enumeration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ServerState { + Idle, + Starting, + Running, + Stopping, + Crashed, +} + +/// Node.js server implementation that spawns vibetunnel as a subprocess +pub struct NodeJsServer { + process: Arc>>, + state: Arc>, + port: String, + bind_address: String, + on_crash: Arc>>>, +} + +impl NodeJsServer { + /// Create a new Node.js server instance + pub fn new(port: String, bind_address: String) -> Self { + Self { + process: Arc::new(RwLock::new(None)), + state: Arc::new(RwLock::new(ServerState::Idle)), + port, + bind_address, + on_crash: Arc::new(RwLock::new(None)), + } + } + + /// Set crash callback + pub async fn set_on_crash(&self, callback: F) + where + F: Fn(i32) + Send + Sync + 'static, + { + *self.on_crash.write().await = Some(Box::new(callback)); + } + + /// Start the Node.js server + pub async fn start(&self) -> Result<(), String> { + // Check current state + let current_state = *self.state.read().await; + match current_state { + ServerState::Running | ServerState::Starting => { + warn!("Server already running or starting"); + return Ok(()); + } + ServerState::Stopping => { + return Err("Cannot start server while stopping".to_string()); + } + _ => {} + } + + // Update state + *self.state.write().await = ServerState::Starting; + + info!("Starting Node.js vibetunnel server on port {}", self.port); + + // Get the vibetunnel executable path + let exe_path = self.get_vibetunnel_path()?; + info!("Using vibetunnel executable at: {:?}", exe_path); + + // Build command arguments + let mut args = vec!["--port".to_string(), self.port.clone()]; + + // Add authentication if configured + if let Some((username, password)) = self.get_auth_credentials().await { + args.push("--username".to_string()); + args.push(username); + args.push("--password".to_string()); + args.push(password); + } + + // Create the command + let mut cmd = Command::new(&exe_path); + cmd.args(&args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .kill_on_drop(true); // Ensure process is killed when dropped + + // Set working directory to where static files are located + if let Ok(current_exe) = std::env::current_exe() { + if let Some(exe_dir) = current_exe.parent() { + // Look for web/public directory relative to executable + let web_dir = exe_dir.join("web"); + if web_dir.exists() { + cmd.current_dir(&web_dir); + info!("Set working directory to: {:?}", web_dir); + } + } + } + + // Spawn the process + match cmd.spawn() { + Ok(mut child) => { + // Set up output monitoring + if let Some(stdout) = child.stdout.take() { + let state = self.state.clone(); + tokio::spawn(async move { + let reader = BufReader::new(stdout); + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + Self::log_output(&line, false); + // Check for successful startup + if line.contains("Server running on") { + info!("Server started successfully"); + *state.write().await = ServerState::Running; + } + } + }); + } + + if let Some(stderr) = child.stderr.take() { + tokio::spawn(async move { + let reader = BufReader::new(stderr); + let mut lines = reader.lines(); + while let Ok(Some(line)) = lines.next_line().await { + Self::log_output(&line, true); + } + }); + } + + // Store the process + *self.process.write().await = Some(child); + + // Give the process a moment to start + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + // Check if process is still running + let mut process_guard = self.process.write().await; + if let Some(ref mut child) = *process_guard { + match child.try_wait() { + Ok(Some(status)) => { + // Process exited immediately + let exit_code = status.code().unwrap_or(-1); + error!("Process exited immediately with code: {}", exit_code); + *process_guard = None; + drop(process_guard); + *self.state.write().await = ServerState::Idle; + + if exit_code == 9 { + return Err(format!("Port {} is already in use", self.port)); + } else { + return Err("Server failed to start".to_string()); + } + } + Ok(None) => { + // Process is still running + drop(process_guard); + + // Start monitoring for unexpected termination + self.monitor_process().await; + + // Wait a bit more for server to be ready + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + // Update state if not already updated by stdout monitor + let mut state = self.state.write().await; + if *state == ServerState::Starting { + *state = ServerState::Running; + } + + info!("Node.js server started successfully"); + Ok(()) + } + Err(e) => { + error!("Failed to check process status: {}", e); + *process_guard = None; + drop(process_guard); + *self.state.write().await = ServerState::Idle; + Err("Failed to check process status".to_string()) + } + } + } else { + *self.state.write().await = ServerState::Idle; + Err("Process handle lost".to_string()) + } + } + Err(e) => { + error!("Failed to spawn vibetunnel process: {}", e); + *self.state.write().await = ServerState::Idle; + Err(format!("Failed to spawn process: {}", e)) + } + } + } + + /// Stop the Node.js server + pub async fn stop(&self) -> Result<(), String> { + let current_state = *self.state.read().await; + match current_state { + ServerState::Running | ServerState::Crashed => { + // Continue with stop + } + _ => { + warn!("Server not running (state: {:?})", current_state); + return Ok(()); + } + } + + *self.state.write().await = ServerState::Stopping; + info!("Stopping Node.js server"); + + let mut process_guard = self.process.write().await; + if let Some(mut child) = process_guard.take() { + // Try graceful shutdown first + #[cfg(unix)] + { + use nix::sys::signal::{self, Signal}; + use nix::unistd::Pid; + + if let Some(pid) = child.id() { + let _ = signal::kill(Pid::from_raw(pid as i32), Signal::SIGTERM); + } + } + + #[cfg(windows)] + { + let _ = child.kill(); + } + + // Wait for process to exit with timeout + match tokio::time::timeout( + tokio::time::Duration::from_secs(5), + child.wait() + ).await { + Ok(Ok(status)) => { + info!("Server stopped with status: {:?}", status); + } + Ok(Err(e)) => { + error!("Error waiting for process: {}", e); + } + Err(_) => { + warn!("Timeout waiting for process to exit, force killing"); + let _ = child.kill(); + let _ = child.wait().await; + } + } + } + + *self.state.write().await = ServerState::Idle; + info!("Node.js server stopped"); + Ok(()) + } + + /// Restart the server + pub async fn restart(&self) -> Result<(), String> { + info!("Restarting Node.js server"); + self.stop().await?; + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + self.start().await + } + + /// Check if server is running + pub async fn is_running(&self) -> bool { + matches!(*self.state.read().await, ServerState::Running) + } + + /// Get server state + pub async fn get_state(&self) -> ServerState { + *self.state.read().await + } + + /// Get the path to the vibetunnel executable + fn get_vibetunnel_path(&self) -> Result { + // Add .exe extension on Windows + let exe_name = if cfg!(windows) { + "vibetunnel.exe" + } else { + "vibetunnel" + }; + + // Try multiple locations for the vibetunnel executable + let possible_paths = vec![ + // Next to the Tauri executable + std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|p| p.join(exe_name))), + // In resources directory (common for packaged apps) + std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|p| p.join("resources").join(exe_name))), + // Development path + PathBuf::from("../web/native").join(exe_name).canonicalize().ok(), + // Another development path + PathBuf::from("../../web/native").join(exe_name).canonicalize().ok(), + ]; + + for path_opt in possible_paths { + if let Some(path) = path_opt { + if path.exists() { + // Make sure it's executable on Unix + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Ok(metadata) = std::fs::metadata(&path) { + let mut perms = metadata.permissions(); + perms.set_mode(0o755); + let _ = std::fs::set_permissions(&path, perms); + } + } + return Ok(path); + } + } + } + + Err("vibetunnel executable not found".to_string()) + } + + /// Get authentication credentials if configured + async fn get_auth_credentials(&self) -> Option<(String, String)> { + // Load settings to check if password is enabled + let settings = crate::settings::Settings::load().ok()?; + + if settings.dashboard.enable_password && !settings.dashboard.password.is_empty() { + Some(("admin".to_string(), settings.dashboard.password)) + } else { + None + } + } + + /// Log server output + fn log_output(line: &str, is_error: bool) { + let line_lower = line.to_lowercase(); + + if is_error || line_lower.contains("error") || line_lower.contains("failed") { + error!("Server: {}", line); + } else if line_lower.contains("warn") { + warn!("Server: {}", line); + } else { + info!("Server: {}", line); + } + } + + /// Monitor process for unexpected termination + async fn monitor_process(&self) { + let process = self.process.clone(); + let state = self.state.clone(); + let on_crash = self.on_crash.clone(); + + tokio::spawn(async move { + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + let mut process_guard = process.write().await; + if let Some(ref mut child) = *process_guard { + match child.try_wait() { + Ok(Some(status)) => { + // Process exited + let exit_code = status.code().unwrap_or(-1); + let was_running = *state.read().await == ServerState::Running; + + if was_running { + error!("Server terminated unexpectedly with exit code: {}", exit_code); + *state.write().await = ServerState::Crashed; + + // Call crash handler if set + if let Some(ref callback) = *on_crash.read().await { + callback(exit_code); + } + } + + *process_guard = None; + break; + } + Ok(None) => { + // Process still running + } + Err(e) => { + error!("Error checking process status: {}", e); + break; + } + } + } else { + // No process to monitor + break; + } + } + }); + } +} + +/// Backend manager that handles the Node.js server +pub struct BackendManager { + server: Arc, + crash_recovery_enabled: Arc>, + consecutive_crashes: Arc>, + is_handling_crash: Arc>, +} + +impl BackendManager { + /// Create a new backend manager + pub fn new(port: u16) -> Self { + let server = Arc::new(NodeJsServer::new( + port.to_string(), + "127.0.0.1".to_string(), + )); + + let manager = Self { + server: server.clone(), + crash_recovery_enabled: Arc::new(RwLock::new(true)), + consecutive_crashes: Arc::new(RwLock::new(0)), + is_handling_crash: Arc::new(RwLock::new(false)), + }; + + // Set up crash handler + let server_for_crash = server.clone(); + let consecutive_crashes = manager.consecutive_crashes.clone(); + let is_handling_crash = manager.is_handling_crash.clone(); + let crash_recovery_enabled = manager.crash_recovery_enabled.clone(); + + tokio::spawn(async move { + server_for_crash.set_on_crash(move |exit_code| { + let consecutive_crashes = consecutive_crashes.clone(); + let is_handling_crash = is_handling_crash.clone(); + let crash_recovery_enabled = crash_recovery_enabled.clone(); + let server = server_for_crash.clone(); + + tokio::spawn(async move { + handle_server_crash( + exit_code, + server, + consecutive_crashes, + is_handling_crash, + crash_recovery_enabled, + ).await; + }); + }).await; + }); + + manager + } + + /// Start the backend server + pub async fn start(&self) -> Result<(), String> { + // Reset consecutive crashes on successful start + let result = self.server.start().await; + if result.is_ok() { + *self.consecutive_crashes.write().await = 0; + } + result + } + + /// Enable or disable crash recovery + pub async fn set_crash_recovery_enabled(&self, enabled: bool) { + *self.crash_recovery_enabled.write().await = enabled; + } + + /// Stop the backend server + pub async fn stop(&self) -> Result<(), String> { + self.server.stop().await + } + + /// Restart the backend server + pub async fn restart(&self) -> Result<(), String> { + self.server.restart().await + } + + /// Check if server is running + pub async fn is_running(&self) -> bool { + self.server.is_running().await + } + + /// Get server instance + pub fn get_server(&self) -> Arc { + self.server.clone() + } +} \ No newline at end of file diff --git a/tauri/src-tauri/src/cli_installer.rs b/tauri/src-tauri/src/cli_installer.rs new file mode 100644 index 00000000..4ada1f26 --- /dev/null +++ b/tauri/src-tauri/src/cli_installer.rs @@ -0,0 +1,321 @@ +use serde::Serialize; +use std::fs; +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; + +const CLI_SCRIPT: &str = r#"#!/bin/bash +# VibeTunnel CLI wrapper + +# Get the VibeTunnel app path +if [ -d "/Applications/VibeTunnel.app" ]; then + APP_PATH="/Applications/VibeTunnel.app" +elif [ -d "$HOME/Applications/VibeTunnel.app" ]; then + APP_PATH="$HOME/Applications/VibeTunnel.app" +else + echo "Error: VibeTunnel.app not found in /Applications or ~/Applications" + exit 1 +fi + +# Launch VibeTunnel with CLI arguments +"$APP_PATH/Contents/MacOS/VibeTunnel" --cli "$@" +"#; + +#[cfg(target_os = "windows")] +const WINDOWS_CLI_SCRIPT: &str = r#"@echo off +:: VibeTunnel CLI wrapper for Windows + +:: Get the VibeTunnel installation path +set "APP_PATH=%LOCALAPPDATA%\VibeTunnel\VibeTunnel.exe" + +if not exist "%APP_PATH%" ( + echo Error: VibeTunnel.exe not found in %LOCALAPPDATA%\VibeTunnel\ + exit /b 1 +) + +:: Launch VibeTunnel with CLI arguments +"%APP_PATH%" --cli %* +"#; + +#[cfg(target_os = "linux")] +const LINUX_CLI_SCRIPT: &str = r#"#!/bin/bash +# VibeTunnel CLI wrapper for Linux + +# Try common installation paths +if [ -x "/usr/local/bin/vibetunnel" ]; then + APP_PATH="/usr/local/bin/vibetunnel" +elif [ -x "/opt/vibetunnel/vibetunnel" ]; then + APP_PATH="/opt/vibetunnel/vibetunnel" +elif [ -x "$HOME/.local/bin/vibetunnel" ]; then + APP_PATH="$HOME/.local/bin/vibetunnel" +else + echo "Error: VibeTunnel executable not found" + exit 1 +fi + +# Launch VibeTunnel with CLI arguments +"$APP_PATH" --cli "$@" +"#; + +#[derive(Debug, Serialize)] +pub struct CliInstallResult { + pub installed: bool, + pub path: String, + pub message: String, +} + +pub fn install_cli_tool() -> Result { + #[cfg(target_os = "macos")] + { + install_cli_macos() + } + + #[cfg(target_os = "windows")] + { + install_cli_windows() + } + + #[cfg(target_os = "linux")] + { + install_cli_linux() + } +} + +#[cfg(target_os = "macos")] +fn install_cli_macos() -> Result { + let cli_path = PathBuf::from("/usr/local/bin/vt"); + + // Check if /usr/local/bin exists, create if not + let bin_dir = cli_path.parent().unwrap(); + if !bin_dir.exists() { + fs::create_dir_all(bin_dir).map_err(|e| { + format!( + "Failed to create /usr/local/bin: {}. Try running with sudo.", + e + ) + })?; + } + + // Write the CLI script + fs::write(&cli_path, CLI_SCRIPT) + .map_err(|e| format!("Failed to write CLI script: {}. Try running with sudo.", e))?; + + // Make it executable + #[cfg(unix)] + { + let mut perms = fs::metadata(&cli_path) + .map_err(|e| format!("Failed to get file metadata: {}", e))? + .permissions(); + perms.set_mode(0o755); + fs::set_permissions(&cli_path, perms) + .map_err(|e| format!("Failed to set permissions: {}", e))?; + } + + Ok(CliInstallResult { + installed: true, + path: cli_path.to_string_lossy().to_string(), + message: "CLI tool installed successfully at /usr/local/bin/vt".to_string(), + }) +} + +#[cfg(target_os = "windows")] +fn install_cli_windows() -> Result { + let user_path = std::env::var("USERPROFILE").map_err(|_| "Failed to get user profile path")?; + + let cli_dir = PathBuf::from(&user_path).join(".vibetunnel"); + let cli_path = cli_dir.join("vt.cmd"); + + // Create directory if it doesn't exist + if !cli_dir.exists() { + fs::create_dir_all(&cli_dir) + .map_err(|e| format!("Failed to create CLI directory: {}", e))?; + } + + // Write the CLI script + fs::write(&cli_path, WINDOWS_CLI_SCRIPT) + .map_err(|e| format!("Failed to write CLI script: {}", e))?; + + // Add to PATH if not already there + add_to_windows_path(&cli_dir)?; + + Ok(CliInstallResult { + installed: true, + path: cli_path.to_string_lossy().to_string(), + message: format!( + "CLI tool installed successfully at {}. Restart your terminal to use 'vt' command.", + cli_path.display() + ), + }) +} + +#[cfg(target_os = "linux")] +fn install_cli_linux() -> Result { + let home_dir = std::env::var("HOME").map_err(|_| "Failed to get home directory")?; + + let local_bin = PathBuf::from(&home_dir).join(".local").join("bin"); + let cli_path = local_bin.join("vt"); + + // Create ~/.local/bin if it doesn't exist + if !local_bin.exists() { + fs::create_dir_all(&local_bin) + .map_err(|e| format!("Failed to create ~/.local/bin: {}", e))?; + } + + // Write the CLI script + fs::write(&cli_path, LINUX_CLI_SCRIPT) + .map_err(|e| format!("Failed to write CLI script: {}", e))?; + + // Make it executable + #[cfg(unix)] + { + let mut perms = fs::metadata(&cli_path) + .map_err(|e| format!("Failed to get file metadata: {}", e))? + .permissions(); + perms.set_mode(0o755); + fs::set_permissions(&cli_path, perms) + .map_err(|e| format!("Failed to set permissions: {}", e))?; + } + + Ok(CliInstallResult { + installed: true, + path: cli_path.to_string_lossy().to_string(), + message: format!( + "CLI tool installed successfully at {}. Make sure ~/.local/bin is in your PATH.", + cli_path.display() + ), + }) +} + +#[cfg(target_os = "windows")] +fn add_to_windows_path(dir: &Path) -> Result<(), String> { + #[cfg(windows)] + { + use winreg::enums::*; + use winreg::RegKey; + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let env = hkcu + .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE) + .map_err(|e| format!("Failed to open registry key: {}", e))?; + + let path: String = env.get_value("Path").unwrap_or_default(); + let dir_str = dir.to_string_lossy(); + + if !path.contains(&*dir_str) { + let new_path = if path.is_empty() { + dir_str.to_string() + } else { + format!("{};{}", path, dir_str) + }; + + env.set_value("Path", &new_path) + .map_err(|e| format!("Failed to update PATH: {}", e))?; + } + + Ok(()) + } + + #[cfg(not(windows))] + { + Ok(()) + } +} + +pub fn uninstall_cli_tool() -> Result { + #[cfg(target_os = "macos")] + { + let cli_path = PathBuf::from("/usr/local/bin/vt"); + if cli_path.exists() { + fs::remove_file(&cli_path) + .map_err(|e| format!("Failed to remove CLI tool: {}. Try running with sudo.", e))?; + } + + Ok(CliInstallResult { + installed: false, + path: cli_path.to_string_lossy().to_string(), + message: "CLI tool uninstalled successfully".to_string(), + }) + } + + #[cfg(target_os = "windows")] + { + let user_path = + std::env::var("USERPROFILE").map_err(|_| "Failed to get user profile path")?; + let cli_path = PathBuf::from(&user_path).join(".vibetunnel").join("vt.cmd"); + + if cli_path.exists() { + fs::remove_file(&cli_path).map_err(|e| format!("Failed to remove CLI tool: {}", e))?; + } + + Ok(CliInstallResult { + installed: false, + path: cli_path.to_string_lossy().to_string(), + message: "CLI tool uninstalled successfully".to_string(), + }) + } + + #[cfg(target_os = "linux")] + { + let home_dir = std::env::var("HOME").map_err(|_| "Failed to get home directory")?; + let cli_path = PathBuf::from(&home_dir) + .join(".local") + .join("bin") + .join("vt"); + + if cli_path.exists() { + fs::remove_file(&cli_path).map_err(|e| format!("Failed to remove CLI tool: {}", e))?; + } + + Ok(CliInstallResult { + installed: false, + path: cli_path.to_string_lossy().to_string(), + message: "CLI tool uninstalled successfully".to_string(), + }) + } +} + +pub fn is_cli_installed() -> bool { + #[cfg(target_os = "macos")] + { + PathBuf::from("/usr/local/bin/vt").exists() + } + + #[cfg(target_os = "windows")] + { + if let Ok(user_path) = std::env::var("USERPROFILE") { + PathBuf::from(&user_path) + .join(".vibetunnel") + .join("vt.cmd") + .exists() + } else { + false + } + } + + #[cfg(target_os = "linux")] + { + if let Ok(home_dir) = std::env::var("HOME") { + PathBuf::from(&home_dir) + .join(".local") + .join("bin") + .join("vt") + .exists() + } else { + false + } + } +} + +#[tauri::command] +pub fn install_cli() -> Result { + install_cli_tool() +} + +#[tauri::command] +pub fn uninstall_cli() -> Result { + uninstall_cli_tool() +} + +#[tauri::command] +pub fn check_cli_installed() -> Result { + Ok(is_cli_installed()) +} diff --git a/tauri/src-tauri/src/commands.rs b/tauri/src-tauri/src/commands.rs new file mode 100644 index 00000000..20164bb5 --- /dev/null +++ b/tauri/src-tauri/src/commands.rs @@ -0,0 +1,2200 @@ +use crate::state::AppState; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tauri::{Manager, State}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Terminal { + pub id: String, + pub name: String, + pub pid: u32, + pub rows: u16, + pub cols: u16, + pub created_at: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ServerStatus { + pub running: bool, + pub port: u16, + pub url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateTerminalOptions { + pub name: Option, + pub rows: Option, + pub cols: Option, + pub cwd: Option, + pub env: Option>, + pub shell: Option, +} + +#[tauri::command] +pub async fn create_terminal( + options: CreateTerminalOptions, + state: State<'_, AppState>, + app: tauri::AppHandle, +) -> Result { + // Check if server is running + if !state.backend_manager.is_running().await { + return Err("Server is not running. Please start the server first.".to_string()); + } + + // Create session via API + let req = crate::api_client::CreateSessionRequest { + name: options.name, + rows: options.rows, + cols: options.cols, + cwd: options.cwd, + env: options.env, + shell: options.shell, + }; + + let session = state.api_client.create_session(req).await?; + + // Update menu bar session count + let sessions = state.api_client.list_sessions().await?; + crate::tray_menu::TrayMenuManager::update_session_count(&app, sessions.len()).await; + + Ok(Terminal { + id: session.id, + name: session.name, + pid: session.pid, + rows: session.rows, + cols: session.cols, + created_at: session.created_at, + }) +} + +#[tauri::command] +pub async fn list_terminals(state: State<'_, AppState>) -> Result, String> { + // Check if server is running + if !state.backend_manager.is_running().await { + return Ok(Vec::new()); + } + + // List sessions via API + let sessions = state.api_client.list_sessions().await?; + + Ok(sessions.into_iter().map(|s| Terminal { + id: s.id, + name: s.name, + pid: s.pid, + rows: s.rows, + cols: s.cols, + created_at: s.created_at, + }).collect()) +} + +#[tauri::command] +pub async fn close_terminal( + id: String, + state: State<'_, AppState>, + app: tauri::AppHandle, +) -> Result<(), String> { + // Check if server is running + if !state.backend_manager.is_running().await { + return Err("Server is not running".to_string()); + } + + // Close session via API + state.api_client.close_session(&id).await?; + + // Update menu bar session count + let sessions = state.api_client.list_sessions().await?; + crate::tray_menu::TrayMenuManager::update_session_count(&app, sessions.len()).await; + + Ok(()) +} + +#[tauri::command] +pub async fn resize_terminal( + id: String, + rows: u16, + cols: u16, + state: State<'_, AppState>, +) -> Result<(), String> { + // Check if server is running + if !state.backend_manager.is_running().await { + return Err("Server is not running".to_string()); + } + + // Resize session via API + state.api_client.resize_session(&id, rows, cols).await +} + +#[tauri::command] +pub async fn write_to_terminal( + id: String, + data: Vec, + state: State<'_, AppState>, +) -> Result<(), String> { + // Check if server is running + if !state.backend_manager.is_running().await { + return Err("Server is not running".to_string()); + } + + // Send input via API + let result = state.api_client.send_input(&id, &data).await; + + // Notify session monitor of activity + if result.is_ok() { + state.session_monitor.notify_activity(&id).await; + } + + result +} + +#[tauri::command] +pub async fn read_from_terminal(id: String, state: State<'_, AppState>) -> Result, String> { + // Check if server is running + if !state.backend_manager.is_running().await { + return Err("Server is not running".to_string()); + } + + // Get output via API + let result = state.api_client.get_session_output(&id).await; + + // Notify session monitor of activity + if result.is_ok() { + state.session_monitor.notify_activity(&id).await; + } + + result +} + +#[tauri::command] +pub async fn start_server( + state: State<'_, AppState>, + app: tauri::AppHandle, +) -> Result { + // Check if server is already running + if state.backend_manager.is_running().await { + // Get port from settings + let settings = crate::settings::Settings::load().unwrap_or_default(); + let port = settings.dashboard.port; + + // Check if ngrok is active + let url = if let Some(ngrok_tunnel) = state.ngrok_manager.get_tunnel_status() { + ngrok_tunnel.url + } else { + format!("http://127.0.0.1:{}", port) + }; + + return Ok(ServerStatus { + running: true, + port, + url, + }); + } + + // Load settings + let settings = crate::settings::Settings::load().unwrap_or_default(); + let port = settings.dashboard.port; + + // Start the Node.js server + state.backend_manager.start().await?; + + // Handle access mode + let url = match settings.dashboard.access_mode.as_str() { + "network" => { + // For network mode, the Node.js server handles the binding + format!("http://0.0.0.0:{}", port) + } + "ngrok" => { + // Try to start ngrok tunnel if auth token is configured + if let Some(auth_token) = settings.advanced.ngrok_auth_token { + if !auth_token.is_empty() { + match state + .ngrok_manager + .start_tunnel(port, Some(auth_token)) + .await + { + Ok(tunnel) => tunnel.url, + Err(e) => { + tracing::error!("Failed to start ngrok tunnel: {}", e); + // Stop the server since ngrok failed + let _ = state.backend_manager.stop().await; + return Err(format!("Failed to start ngrok tunnel: {}", e)); + } + } + } else { + let _ = state.backend_manager.stop().await; + return Err("Ngrok auth token is required for ngrok access mode".to_string()); + } + } else { + let _ = state.backend_manager.stop().await; + return Err("Ngrok auth token is required for ngrok access mode".to_string()); + } + } + _ => { + format!("http://127.0.0.1:{}", port) + } + }; + + // Update menu bar server status + crate::tray_menu::TrayMenuManager::update_server_status(&app, port, true).await; + + Ok(ServerStatus { + running: true, + port, + url, + }) +} + +#[tauri::command] +pub async fn stop_server(state: State<'_, AppState>, app: tauri::AppHandle) -> Result<(), String> { + // Stop the Node.js server + state.backend_manager.stop().await?; + + // Also stop ngrok tunnel if active + let _ = state.ngrok_manager.stop_tunnel().await; + + // Update menu bar server status + crate::tray_menu::TrayMenuManager::update_server_status(&app, 4020, false).await; + + Ok(()) +} + +#[tauri::command] +pub async fn get_server_status(state: State<'_, AppState>) -> Result { + if state.backend_manager.is_running().await { + // Get port from settings + let settings = crate::settings::Settings::load().unwrap_or_default(); + let port = settings.dashboard.port; + + // Check if ngrok is active and return its URL + let url = if let Some(ngrok_tunnel) = state.ngrok_manager.get_tunnel_status() { + ngrok_tunnel.url + } else { + // Check settings to determine the correct URL format + match settings.dashboard.access_mode.as_str() { + "network" => format!("http://0.0.0.0:{}", port), + _ => format!("http://127.0.0.1:{}", port), + } + }; + + Ok(ServerStatus { + running: true, + port, + url, + }) + } else { + Ok(ServerStatus { + running: false, + port: 0, + url: String::new(), + }) + } +} + +#[tauri::command] +pub fn get_app_version() -> String { + env!("CARGO_PKG_VERSION").to_string() +} + +#[tauri::command] +pub async fn restart_server(state: State<'_, AppState>, app: tauri::AppHandle) -> Result { + // First stop the server + stop_server(state.clone(), app.clone()).await?; + + // Wait a moment for clean shutdown + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + // Start the server again + start_server(state, app).await +} + +#[tauri::command] +pub async fn show_server_console(app_handle: tauri::AppHandle) -> Result<(), String> { + // Check if server console window already exists + if let Some(window) = app_handle.get_webview_window("server-console") { + window.show().map_err(|e| e.to_string())?; + window.set_focus().map_err(|e| e.to_string())?; + } else { + // Create a new window for the server console + tauri::WebviewWindowBuilder::new( + &app_handle, + "server-console", + tauri::WebviewUrl::App("server-console.html".into()), + ) + .title("Server Console - VibeTunnel") + .inner_size(900.0, 600.0) + .resizable(true) + .decorations(true) + .center() + .build() + .map_err(|e| e.to_string())?; + } + + Ok(()) +} + +#[tauri::command] +pub async fn show_welcome_screen(state: State<'_, AppState>) -> Result<(), String> { + let welcome_manager = &state.welcome_manager; + welcome_manager.show_welcome_window().await +} + +#[tauri::command] +pub async fn purge_all_settings( + app_handle: tauri::AppHandle, + _state: State<'_, AppState>, +) -> Result<(), String> { + // Create default settings and save to clear the file + let default_settings = crate::settings::Settings::default(); + default_settings.save().map_err(|e| e.to_string())?; + + // Quit the app after a short delay + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + app_handle.exit(0); + }); + + Ok(()) +} + +#[tauri::command] +pub async fn update_dock_icon_visibility(app_handle: tauri::AppHandle) -> Result<(), String> { + #[cfg(target_os = "macos")] + { + let settings = crate::settings::Settings::load().unwrap_or_default(); + let has_visible_windows = app_handle + .windows() + .values() + .any(|w| w.is_visible().unwrap_or(false)); + + if has_visible_windows { + // Always show dock icon when windows are visible + let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular); + } else if settings.general.show_dock_icon { + // Show dock icon if setting is enabled + let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular); + } else { + // Hide dock icon if setting is disabled and no windows are visible + let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory); + } + } + Ok(()) +} + + +// TTY Forwarding Commands +#[derive(Debug, Serialize, Deserialize)] +pub struct StartTTYForwardOptions { + pub local_port: u16, + pub remote_host: Option, + pub remote_port: Option, + pub shell: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TTYForwardInfo { + pub id: String, + pub local_port: u16, + pub remote_host: String, + pub remote_port: u16, + pub connected: bool, + pub client_count: usize, +} + +#[tauri::command] +pub async fn start_tty_forward( + options: StartTTYForwardOptions, + state: State<'_, AppState>, +) -> Result { + let tty_forward_manager = &state.tty_forward_manager; + + let remote_host = options + .remote_host + .unwrap_or_else(|| "localhost".to_string()); + let remote_port = options.remote_port.unwrap_or(22); + + tty_forward_manager + .start_forward(options.local_port, remote_host, remote_port, options.shell) + .await +} + +#[tauri::command] +pub async fn stop_tty_forward(id: String, state: State<'_, AppState>) -> Result<(), String> { + let tty_forward_manager = &state.tty_forward_manager; + tty_forward_manager.stop_forward(&id).await +} + +#[tauri::command] +pub async fn list_tty_forwards(state: State<'_, AppState>) -> Result, String> { + let tty_forward_manager = &state.tty_forward_manager; + let forwards = tty_forward_manager.list_forwards().await; + + Ok(forwards + .into_iter() + .map(|f| TTYForwardInfo { + id: f.id, + local_port: f.local_port, + remote_host: f.remote_host, + remote_port: f.remote_port, + connected: f.connected, + client_count: f.client_count, + }) + .collect()) +} + +#[tauri::command] +pub async fn get_tty_forward( + id: String, + state: State<'_, AppState>, +) -> Result, String> { + let tty_forward_manager = &state.tty_forward_manager; + + Ok(tty_forward_manager + .get_forward(&id) + .await + .map(|f| TTYForwardInfo { + id: f.id, + local_port: f.local_port, + remote_host: f.remote_host, + remote_port: f.remote_port, + connected: f.connected, + client_count: f.client_count, + })) +} + +// Session Monitoring Commands +#[tauri::command] +pub async fn get_session_stats( + state: State<'_, AppState>, +) -> Result { + let session_monitor = &state.session_monitor; + Ok(session_monitor.get_stats().await) +} + +#[tauri::command] +pub async fn get_monitored_sessions( + state: State<'_, AppState>, +) -> Result, String> { + let session_monitor = &state.session_monitor; + Ok(session_monitor.get_sessions().await) +} + +#[tauri::command] +pub async fn start_session_monitoring(state: State<'_, AppState>) -> Result<(), String> { + let session_monitor = &state.session_monitor; + session_monitor.start_monitoring().await; + Ok(()) +} + +// Port Conflict Resolution Commands +#[tauri::command] +pub async fn check_port_availability(port: u16) -> Result { + Ok(crate::port_conflict::PortConflictResolver::is_port_available(port).await) +} + +#[tauri::command] +pub async fn detect_port_conflict( + port: u16, +) -> Result, String> { + Ok(crate::port_conflict::PortConflictResolver::detect_conflict(port).await) +} + +#[tauri::command] +pub async fn resolve_port_conflict( + conflict: crate::port_conflict::PortConflict, +) -> Result<(), String> { + crate::port_conflict::PortConflictResolver::resolve_conflict(&conflict).await +} + +#[tauri::command] +pub async fn force_kill_process( + conflict: crate::port_conflict::PortConflict, +) -> Result<(), String> { + crate::port_conflict::PortConflictResolver::force_kill_process(&conflict).await +} + +#[tauri::command] +pub async fn find_available_ports(near_port: u16, count: usize) -> Result, String> { + let mut available_ports = Vec::new(); + let start = near_port.saturating_sub(10).max(1024); + let end = near_port.saturating_add(100).min(65535); + + for port in start..=end { + if port != near_port + && crate::port_conflict::PortConflictResolver::is_port_available(port).await + { + available_ports.push(port); + if available_ports.len() >= count { + break; + } + } + } + + Ok(available_ports) +} + +// Network Utilities Commands +#[tauri::command] +pub async fn get_local_ip_address() -> Result, String> { + Ok(crate::network_utils::NetworkUtils::get_local_ip_address()) +} + +#[tauri::command] +pub async fn get_all_ip_addresses() -> Result, String> { + Ok(crate::network_utils::NetworkUtils::get_all_ip_addresses()) +} + +#[tauri::command] +pub async fn get_network_interfaces() -> Result, String> +{ + Ok(crate::network_utils::NetworkUtils::get_all_interfaces()) +} + +#[tauri::command] +pub async fn get_hostname() -> Result, String> { + Ok(crate::network_utils::NetworkUtils::get_hostname()) +} + +#[tauri::command] +pub async fn test_network_connectivity(host: String, port: u16) -> Result { + Ok(crate::network_utils::NetworkUtils::test_connectivity(&host, port).await) +} + +#[tauri::command] +pub async fn get_network_stats() -> Result { + Ok(crate::network_utils::NetworkUtils::get_network_stats()) +} + +// Notification Commands +#[derive(Debug, Serialize, Deserialize)] +pub struct ShowNotificationOptions { + pub notification_type: crate::notification_manager::NotificationType, + pub priority: crate::notification_manager::NotificationPriority, + pub title: String, + pub body: String, + pub actions: Vec, + #[serde(default)] + pub metadata: HashMap, +} + +#[tauri::command] +pub async fn show_notification( + options: ShowNotificationOptions, + state: State<'_, AppState>, +) -> Result { + let notification_manager = &state.notification_manager; + notification_manager + .show_notification( + options.notification_type, + options.priority, + options.title, + options.body, + options.actions, + options.metadata, + ) + .await +} + +#[tauri::command] +pub async fn get_notifications( + state: State<'_, AppState>, +) -> Result, String> { + let notification_manager = &state.notification_manager; + Ok(notification_manager.get_notifications().await) +} + +#[tauri::command] +pub async fn get_notification_history( + limit: Option, + state: State<'_, AppState>, +) -> Result, String> { + let notification_manager = &state.notification_manager; + Ok(notification_manager.get_history(limit).await) +} + +#[tauri::command] +pub async fn mark_notification_as_read( + notification_id: String, + state: State<'_, AppState>, +) -> Result<(), String> { + let notification_manager = &state.notification_manager; + notification_manager.mark_as_read(¬ification_id).await +} + +#[tauri::command] +pub async fn mark_all_notifications_as_read(state: State<'_, AppState>) -> Result<(), String> { + let notification_manager = &state.notification_manager; + notification_manager.mark_all_as_read().await +} + +#[tauri::command] +pub async fn clear_notification( + notification_id: String, + state: State<'_, AppState>, +) -> Result<(), String> { + let notification_manager = &state.notification_manager; + notification_manager + .clear_notification(¬ification_id) + .await +} + +#[tauri::command] +pub async fn clear_all_notifications(state: State<'_, AppState>) -> Result<(), String> { + let notification_manager = &state.notification_manager; + notification_manager.clear_all_notifications().await +} + +#[tauri::command] +pub async fn get_unread_notification_count(state: State<'_, AppState>) -> Result { + let notification_manager = &state.notification_manager; + Ok(notification_manager.get_unread_count().await) +} + +#[tauri::command] +pub async fn update_notification_settings( + settings: crate::notification_manager::NotificationSettings, + state: State<'_, AppState>, +) -> Result<(), String> { + let notification_manager = &state.notification_manager; + notification_manager.update_settings(settings).await; + Ok(()) +} + +#[tauri::command] +pub async fn get_notification_settings( + state: State<'_, AppState>, +) -> Result { + let notification_manager = &state.notification_manager; + Ok(notification_manager.get_settings().await) +} + +// Welcome/Tutorial Commands +#[tauri::command] +pub async fn get_welcome_state( + state: State<'_, AppState>, +) -> Result { + let welcome_manager = &state.welcome_manager; + Ok(welcome_manager.get_state().await) +} + +#[tauri::command] +pub async fn should_show_welcome(state: State<'_, AppState>) -> Result { + let welcome_manager = &state.welcome_manager; + Ok(welcome_manager.should_show_welcome().await) +} + +#[tauri::command] +pub async fn get_tutorials( + state: State<'_, AppState>, +) -> Result, String> { + let welcome_manager = &state.welcome_manager; + Ok(welcome_manager.get_tutorials().await) +} + +#[tauri::command] +pub async fn get_tutorial_category( + category_id: String, + state: State<'_, AppState>, +) -> Result, String> { + let welcome_manager = &state.welcome_manager; + Ok(welcome_manager.get_tutorial_category(&category_id).await) +} + +#[tauri::command] +pub async fn complete_tutorial_step( + step_id: String, + state: State<'_, AppState>, +) -> Result<(), String> { + let welcome_manager = &state.welcome_manager; + welcome_manager.complete_step(&step_id).await +} + +#[tauri::command] +pub async fn skip_tutorial(state: State<'_, AppState>) -> Result<(), String> { + let welcome_manager = &state.welcome_manager; + welcome_manager.skip_tutorial().await +} + +#[tauri::command] +pub async fn reset_tutorial(state: State<'_, AppState>) -> Result<(), String> { + let welcome_manager = &state.welcome_manager; + welcome_manager.reset_tutorial().await +} + +#[tauri::command] +pub async fn get_tutorial_progress( + state: State<'_, AppState>, +) -> Result { + let welcome_manager = &state.welcome_manager; + Ok(welcome_manager.get_progress().await) +} + +#[tauri::command] +pub async fn show_welcome_window(state: State<'_, AppState>) -> Result<(), String> { + let welcome_manager = &state.welcome_manager; + welcome_manager.show_welcome_window().await +} + +// Advanced Settings Commands + +#[tauri::command] +pub async fn get_all_advanced_settings() -> Result, String> { + let settings = crate::settings::Settings::load().unwrap_or_default(); + let mut all_settings = HashMap::new(); + + // Convert all settings sections to JSON values + all_settings.insert( + "tty_forward".to_string(), + serde_json::to_value(&settings.tty_forward).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "monitoring".to_string(), + serde_json::to_value(&settings.monitoring).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "network".to_string(), + serde_json::to_value(&settings.network).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "port".to_string(), + serde_json::to_value(&settings.port).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "notifications".to_string(), + serde_json::to_value(&settings.notifications).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "terminal_integrations".to_string(), + serde_json::to_value(&settings.terminal_integrations).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "updates".to_string(), + serde_json::to_value(&settings.updates).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "security".to_string(), + serde_json::to_value(&settings.security).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "debug".to_string(), + serde_json::to_value(&settings.debug).unwrap_or(serde_json::Value::Null), + ); + + Ok(all_settings) +} + +#[tauri::command] +pub async fn update_advanced_settings( + section: String, + value: serde_json::Value, +) -> Result<(), String> { + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + + match section.as_str() { + "tty_forward" => { + settings.tty_forward = serde_json::from_value(value) + .map_err(|e| format!("Invalid TTY forward settings: {}", e))?; + } + "monitoring" => { + settings.monitoring = serde_json::from_value(value) + .map_err(|e| format!("Invalid monitoring settings: {}", e))?; + } + "network" => { + settings.network = serde_json::from_value(value) + .map_err(|e| format!("Invalid network settings: {}", e))?; + } + "port" => { + settings.port = serde_json::from_value(value) + .map_err(|e| format!("Invalid port settings: {}", e))?; + } + "notifications" => { + settings.notifications = serde_json::from_value(value) + .map_err(|e| format!("Invalid notification settings: {}", e))?; + } + "terminal_integrations" => { + settings.terminal_integrations = serde_json::from_value(value) + .map_err(|e| format!("Invalid terminal integration settings: {}", e))?; + } + "updates" => { + settings.updates = serde_json::from_value(value) + .map_err(|e| format!("Invalid update settings: {}", e))?; + } + "security" => { + settings.security = serde_json::from_value(value) + .map_err(|e| format!("Invalid security settings: {}", e))?; + } + "debug" => { + settings.debug = serde_json::from_value(value) + .map_err(|e| format!("Invalid debug settings: {}", e))?; + } + _ => return Err(format!("Unknown settings section: {}", section)), + } + + settings.save() +} + +#[tauri::command] +pub async fn reset_settings_section(section: String) -> Result<(), String> { + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + let defaults = crate::settings::Settings::default(); + + match section.as_str() { + "tty_forward" => settings.tty_forward = defaults.tty_forward, + "monitoring" => settings.monitoring = defaults.monitoring, + "network" => settings.network = defaults.network, + "port" => settings.port = defaults.port, + "notifications" => settings.notifications = defaults.notifications, + "terminal_integrations" => settings.terminal_integrations = defaults.terminal_integrations, + "updates" => settings.updates = defaults.updates, + "security" => settings.security = defaults.security, + "debug" => settings.debug = defaults.debug, + "all" => settings = defaults, + _ => return Err(format!("Unknown settings section: {}", section)), + } + + settings.save() +} + +#[tauri::command] +pub async fn export_settings() -> Result { + let settings = crate::settings::Settings::load().unwrap_or_default(); + toml::to_string_pretty(&settings).map_err(|e| format!("Failed to export settings: {}", e)) +} + +#[tauri::command] +pub async fn import_settings(toml_content: String) -> Result<(), String> { + let settings: crate::settings::Settings = + toml::from_str(&toml_content).map_err(|e| format!("Failed to parse settings: {}", e))?; + settings.save() +} + +// Permissions Commands +#[tauri::command] +pub async fn check_all_permissions( + state: State<'_, AppState>, +) -> Result, String> { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager.check_all_permissions().await) +} + +#[tauri::command] +pub async fn check_permission( + permission_type: crate::permissions::PermissionType, + state: State<'_, AppState>, +) -> Result { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager.check_permission(permission_type).await) +} + +#[tauri::command] +pub async fn check_permission_silent( + permission_type: crate::permissions::PermissionType, + state: State<'_, AppState>, +) -> Result { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager + .check_permission_silent(permission_type) + .await) +} + +#[tauri::command] +pub async fn request_permission( + permission_type: crate::permissions::PermissionType, + state: State<'_, AppState>, +) -> Result { + let permissions_manager = &state.permissions_manager; + permissions_manager + .request_permission(permission_type) + .await +} + +#[tauri::command] +pub async fn get_permission_info( + permission_type: crate::permissions::PermissionType, + state: State<'_, AppState>, +) -> Result, String> { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager + .get_permission_info(permission_type) + .await) +} + +#[tauri::command] +pub async fn get_all_permissions( + state: State<'_, AppState>, +) -> Result, String> { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager.get_all_permissions().await) +} + +#[tauri::command] +pub async fn get_required_permissions( + state: State<'_, AppState>, +) -> Result, String> { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager.get_required_permissions().await) +} + +#[tauri::command] +pub async fn get_missing_required_permissions( + state: State<'_, AppState>, +) -> Result, String> { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager.get_missing_required_permissions().await) +} + +#[tauri::command] +pub async fn all_required_permissions_granted(state: State<'_, AppState>) -> Result { + let permissions_manager = &state.permissions_manager; + Ok(permissions_manager.all_required_permissions_granted().await) +} + +#[tauri::command] +pub async fn open_system_permission_settings( + permission_type: crate::permissions::PermissionType, + state: State<'_, AppState>, +) -> Result<(), String> { + let permissions_manager = &state.permissions_manager; + permissions_manager + .open_system_settings(permission_type) + .await +} + +#[tauri::command] +pub async fn get_permission_stats( + state: State<'_, AppState>, +) -> Result { + let permissions_manager = &state.permissions_manager; + let all_permissions = permissions_manager.get_all_permissions().await; + + let stats = crate::permissions::PermissionStats { + total_permissions: all_permissions.len(), + granted_permissions: all_permissions + .iter() + .filter(|p| p.status == crate::permissions::PermissionStatus::Granted) + .count(), + denied_permissions: all_permissions + .iter() + .filter(|p| p.status == crate::permissions::PermissionStatus::Denied) + .count(), + required_permissions: all_permissions.iter().filter(|p| p.required).count(), + missing_required: all_permissions + .iter() + .filter(|p| p.required && p.status != crate::permissions::PermissionStatus::Granted) + .count(), + platform: std::env::consts::OS.to_string(), + }; + + Ok(stats) +} + +// Update Manager Commands +#[tauri::command] +pub async fn check_for_updates( + state: State<'_, AppState>, +) -> Result, String> { + let update_manager = &state.update_manager; + update_manager.check_for_updates().await +} + +#[tauri::command] +pub async fn download_update(state: State<'_, AppState>) -> Result<(), String> { + let update_manager = &state.update_manager; + update_manager.download_update().await +} + +#[tauri::command] +pub async fn install_update(state: State<'_, AppState>) -> Result<(), String> { + let update_manager = &state.update_manager; + update_manager.install_update().await +} + +#[tauri::command] +pub async fn cancel_update(state: State<'_, AppState>) -> Result<(), String> { + let update_manager = &state.update_manager; + update_manager.cancel_update().await +} + +#[tauri::command] +pub async fn get_update_state( + state: State<'_, AppState>, +) -> Result { + let update_manager = &state.update_manager; + Ok(update_manager.get_state().await) +} + +#[tauri::command] +pub async fn get_updater_settings( + state: State<'_, AppState>, +) -> Result { + let update_manager = &state.update_manager; + Ok(update_manager.get_settings().await) +} + +#[tauri::command] +pub async fn update_updater_settings( + settings: crate::updater::UpdaterSettings, + state: State<'_, AppState>, +) -> Result<(), String> { + let update_manager = &state.update_manager; + update_manager.update_settings(settings).await +} + +#[tauri::command] +pub async fn switch_update_channel( + channel: crate::updater::UpdateChannel, + state: State<'_, AppState>, +) -> Result<(), String> { + let update_manager = &state.update_manager; + update_manager.switch_channel(channel).await +} + +#[tauri::command] +pub async fn get_update_history( + limit: Option, + state: State<'_, AppState>, +) -> Result, String> { + let update_manager = &state.update_manager; + Ok(update_manager.get_update_history(limit).await) +} + +// Backend Manager Commands +#[tauri::command] +pub async fn get_available_backends( + state: State<'_, AppState>, +) -> Result, String> { + let backend_manager = &state.backend_manager; + Ok(backend_manager.get_available_backends().await) +} + +#[tauri::command] +pub async fn get_backend_config( + backend_type: crate::backend_manager::BackendType, + state: State<'_, AppState>, +) -> Result, String> { + let backend_manager = &state.backend_manager; + Ok(backend_manager.get_backend_config(backend_type).await) +} + +#[tauri::command] +pub async fn is_backend_installed( + backend_type: crate::backend_manager::BackendType, + state: State<'_, AppState>, +) -> Result { + let backend_manager = &state.backend_manager; + Ok(backend_manager.is_backend_installed(backend_type).await) +} + +#[tauri::command] +pub async fn install_backend( + backend_type: crate::backend_manager::BackendType, + state: State<'_, AppState>, +) -> Result<(), String> { + let backend_manager = &state.backend_manager; + backend_manager.install_backend(backend_type).await +} + +#[tauri::command] +pub async fn start_backend( + backend_type: crate::backend_manager::BackendType, + state: State<'_, AppState>, +) -> Result { + let backend_manager = &state.backend_manager; + backend_manager.start_backend(backend_type).await +} + +#[tauri::command] +pub async fn stop_backend(instance_id: String, state: State<'_, AppState>) -> Result<(), String> { + let backend_manager = &state.backend_manager; + backend_manager.stop_backend(&instance_id).await +} + +#[tauri::command] +pub async fn switch_backend( + backend_type: crate::backend_manager::BackendType, + state: State<'_, AppState>, +) -> Result<(), String> { + let backend_manager = &state.backend_manager; + backend_manager.switch_backend(backend_type).await +} + +#[tauri::command] +pub async fn get_active_backend( + state: State<'_, AppState>, +) -> Result, String> { + let backend_manager = &state.backend_manager; + Ok(backend_manager.get_active_backend().await) +} + +#[tauri::command] +pub async fn get_backend_instances( + state: State<'_, AppState>, +) -> Result, String> { + let backend_manager = &state.backend_manager; + Ok(backend_manager.get_backend_instances().await) +} + +#[tauri::command] +pub async fn check_backend_health( + instance_id: String, + state: State<'_, AppState>, +) -> Result { + let backend_manager = &state.backend_manager; + backend_manager.check_backend_health(&instance_id).await +} + +#[tauri::command] +pub async fn get_backend_stats( + state: State<'_, AppState>, +) -> Result { + let backend_manager = &state.backend_manager; + + let backends = backend_manager.get_available_backends().await; + let instances = backend_manager.get_backend_instances().await; + let active_backend = backend_manager.get_active_backend().await; + + let mut health_summary = std::collections::HashMap::new(); + for instance in &instances { + *health_summary.entry(instance.health_status).or_insert(0) += 1; + } + + let mut installed_count = 0; + for backend in &backends { + if backend_manager + .is_backend_installed(backend.backend_type) + .await + { + installed_count += 1; + } + } + + Ok(crate::backend_manager::BackendStats { + total_backends: backends.len(), + installed_backends: installed_count, + running_instances: instances + .iter() + .filter(|i| i.status == crate::backend_manager::BackendStatus::Running) + .count(), + active_backend, + health_summary, + }) +} + +// Debug Features Commands +#[derive(Debug, Serialize, Deserialize)] +pub struct LogDebugMessageOptions { + pub level: crate::debug_features::LogLevel, + pub component: String, + pub message: String, + pub metadata: HashMap, +} + +#[tauri::command] +pub async fn get_debug_settings( + state: State<'_, AppState>, +) -> Result { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.get_settings().await) +} + +#[tauri::command] +pub async fn update_debug_settings( + settings: crate::debug_features::DebugSettings, + state: State<'_, AppState>, +) -> Result<(), String> { + let debug_features_manager = &state.debug_features_manager; + debug_features_manager.update_settings(settings).await; + Ok(()) +} + +#[tauri::command] +pub async fn log_debug_message( + options: LogDebugMessageOptions, + state: State<'_, AppState>, +) -> Result<(), String> { + let debug_features_manager = &state.debug_features_manager; + debug_features_manager + .log( + options.level, + &options.component, + &options.message, + options.metadata, + ) + .await; + Ok(()) +} + +#[tauri::command] +pub async fn record_performance_metric( + name: String, + value: f64, + unit: String, + tags: HashMap, + state: State<'_, AppState>, +) -> Result<(), String> { + let debug_features_manager = &state.debug_features_manager; + debug_features_manager + .record_metric(&name, value, &unit, tags) + .await; + Ok(()) +} + +#[tauri::command] +pub async fn take_memory_snapshot( + state: State<'_, AppState>, +) -> Result { + let debug_features_manager = &state.debug_features_manager; + debug_features_manager.take_memory_snapshot().await +} + +#[tauri::command] +pub async fn get_debug_logs( + limit: Option, + level: Option, + state: State<'_, AppState>, +) -> Result, String> { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.get_logs(limit, level).await) +} + +#[tauri::command] +pub async fn get_performance_metrics( + limit: Option, + state: State<'_, AppState>, +) -> Result, String> { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.get_performance_metrics(limit).await) +} + +#[tauri::command] +pub async fn get_memory_snapshots( + limit: Option, + state: State<'_, AppState>, +) -> Result, String> { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.get_memory_snapshots(limit).await) +} + +#[tauri::command] +pub async fn get_network_requests( + limit: Option, + state: State<'_, AppState>, +) -> Result, String> { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.get_network_requests(limit).await) +} + +#[tauri::command] +pub async fn run_api_tests( + tests: Vec, + state: State<'_, AppState>, +) -> Result, String> { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.run_api_tests(tests).await) +} + +// API Testing Commands +#[tauri::command] +pub async fn get_api_test_config( + state: State<'_, AppState>, +) -> Result { + let api_testing_manager = &state.api_testing_manager; + Ok(api_testing_manager.get_config().await) +} + +#[tauri::command] +pub async fn update_api_test_config( + config: crate::api_testing::APITestRunnerConfig, + state: State<'_, AppState>, +) -> Result<(), String> { + let api_testing_manager = &state.api_testing_manager; + api_testing_manager.update_config(config).await; + Ok(()) +} + +#[tauri::command] +pub async fn add_api_test_suite( + suite: crate::api_testing::APITestSuite, + state: State<'_, AppState>, +) -> Result<(), String> { + let api_testing_manager = &state.api_testing_manager; + api_testing_manager.add_test_suite(suite).await; + Ok(()) +} + +#[tauri::command] +pub async fn get_api_test_suite( + suite_id: String, + state: State<'_, AppState>, +) -> Result, String> { + let api_testing_manager = &state.api_testing_manager; + Ok(api_testing_manager.get_test_suite(&suite_id).await) +} + +#[tauri::command] +pub async fn list_api_test_suites( + state: State<'_, AppState>, +) -> Result, String> { + let api_testing_manager = &state.api_testing_manager; + Ok(api_testing_manager.list_test_suites().await) +} + +#[tauri::command] +pub async fn run_single_api_test( + test: crate::api_testing::APITest, + variables: HashMap, + state: State<'_, AppState>, +) -> Result { + let api_testing_manager = &state.api_testing_manager; + Ok(api_testing_manager.run_test(&test, &variables).await) +} + +#[tauri::command] +pub async fn run_api_test_suite( + suite_id: String, + state: State<'_, AppState>, +) -> Result, String> { + let api_testing_manager = &state.api_testing_manager; + Ok(api_testing_manager.run_test_suite(&suite_id).await) +} + +#[tauri::command] +pub async fn get_api_test_history( + limit: Option, + state: State<'_, AppState>, +) -> Result, String> { + let api_testing_manager = &state.api_testing_manager; + Ok(api_testing_manager.get_test_history(limit).await) +} + +#[tauri::command] +pub async fn clear_api_test_history(state: State<'_, AppState>) -> Result<(), String> { + let api_testing_manager = &state.api_testing_manager; + api_testing_manager.clear_test_history().await; + Ok(()) +} + +#[tauri::command] +pub async fn import_postman_collection( + json_data: String, + state: State<'_, AppState>, +) -> Result { + let api_testing_manager = &state.api_testing_manager; + api_testing_manager + .import_postman_collection(&json_data) + .await +} + +#[tauri::command] +pub async fn export_api_test_suite( + suite_id: String, + state: State<'_, AppState>, +) -> Result { + let api_testing_manager = &state.api_testing_manager; + api_testing_manager.export_test_suite(&suite_id).await +} + +#[tauri::command] +pub async fn run_benchmarks( + configs: Vec, + state: State<'_, AppState>, +) -> Result, String> { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.run_benchmarks(configs).await) +} + +#[tauri::command] +pub async fn generate_diagnostic_report( + state: State<'_, AppState>, +) -> Result { + let debug_features_manager = &state.debug_features_manager; + Ok(debug_features_manager.generate_diagnostic_report().await) +} + +#[tauri::command] +pub async fn clear_debug_data(state: State<'_, AppState>) -> Result<(), String> { + let debug_features_manager = &state.debug_features_manager; + debug_features_manager.clear_all_data().await; + Ok(()) +} + +#[tauri::command] +pub async fn set_debug_mode(enabled: bool, state: State<'_, AppState>) -> Result<(), String> { + let debug_features_manager = &state.debug_features_manager; + debug_features_manager.set_debug_mode(enabled).await; + Ok(()) +} + +#[tauri::command] +pub async fn get_debug_stats( + state: State<'_, AppState>, +) -> Result { + let debug_features_manager = &state.debug_features_manager; + + let logs = debug_features_manager.get_logs(None, None).await; + let mut logs_by_level = HashMap::new(); + for log in &logs { + let level = format!("{:?}", log.level); + *logs_by_level.entry(level).or_insert(0) += 1; + } + + let metrics = debug_features_manager.get_performance_metrics(None).await; + let snapshots = debug_features_manager.get_memory_snapshots(None).await; + let requests = debug_features_manager.get_network_requests(None).await; + + Ok(crate::debug_features::DebugStats { + total_logs: logs.len(), + logs_by_level, + total_metrics: metrics.len(), + total_snapshots: snapshots.len(), + total_requests: requests.len(), + total_test_results: 0, // TODO: Track test results + total_benchmarks: 0, // TODO: Track benchmarks + }) +} + +// Auth Cache Commands +#[derive(Debug, Serialize, Deserialize)] +pub struct StoreTokenOptions { + pub key: String, + pub token: crate::auth_cache::CachedToken, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetTokenOptions { + pub key: String, + pub scope: crate::auth_cache::AuthScope, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StoreCredentialOptions { + pub key: String, + pub credential: crate::auth_cache::AuthCredential, +} + +#[tauri::command] +pub async fn get_auth_cache_config( + state: State<'_, AppState>, +) -> Result { + let auth_cache_manager = &state.auth_cache_manager; + Ok(auth_cache_manager.get_config().await) +} + +#[tauri::command] +pub async fn update_auth_cache_config( + config: crate::auth_cache::AuthCacheConfig, + state: State<'_, AppState>, +) -> Result<(), String> { + let auth_cache_manager = &state.auth_cache_manager; + auth_cache_manager.update_config(config).await; + Ok(()) +} + +#[tauri::command] +pub async fn store_auth_token( + options: StoreTokenOptions, + state: State<'_, AppState>, +) -> Result<(), String> { + let auth_cache_manager = &state.auth_cache_manager; + auth_cache_manager + .store_token(&options.key, options.token) + .await +} + +#[tauri::command] +pub async fn get_auth_token( + options: GetTokenOptions, + state: State<'_, AppState>, +) -> Result, String> { + let auth_cache_manager = &state.auth_cache_manager; + Ok(auth_cache_manager + .get_token(&options.key, &options.scope) + .await) +} + +#[tauri::command] +pub async fn store_auth_credential( + options: StoreCredentialOptions, + state: State<'_, AppState>, +) -> Result<(), String> { + let auth_cache_manager = &state.auth_cache_manager; + auth_cache_manager + .store_credential(&options.key, options.credential) + .await +} + +#[tauri::command] +pub async fn get_auth_credential( + key: String, + state: State<'_, AppState>, +) -> Result, String> { + let auth_cache_manager = &state.auth_cache_manager; + Ok(auth_cache_manager.get_credential(&key).await) +} + +#[tauri::command] +pub async fn clear_auth_cache_entry(key: String, state: State<'_, AppState>) -> Result<(), String> { + let auth_cache_manager = &state.auth_cache_manager; + auth_cache_manager.clear_entry(&key).await; + Ok(()) +} + +#[tauri::command] +pub async fn clear_all_auth_cache(state: State<'_, AppState>) -> Result<(), String> { + let auth_cache_manager = &state.auth_cache_manager; + auth_cache_manager.clear_all().await; + Ok(()) +} + +#[tauri::command] +pub async fn get_auth_cache_stats( + state: State<'_, AppState>, +) -> Result { + let auth_cache_manager = &state.auth_cache_manager; + Ok(auth_cache_manager.get_stats().await) +} + +#[tauri::command] +pub async fn list_auth_cache_entries( + state: State<'_, AppState>, +) -> Result, u64)>, String> { + let auth_cache_manager = &state.auth_cache_manager; + Ok(auth_cache_manager.list_entries().await) +} + +#[tauri::command] +pub async fn export_auth_cache(state: State<'_, AppState>) -> Result { + let auth_cache_manager = &state.auth_cache_manager; + auth_cache_manager.export_cache().await +} + +#[tauri::command] +pub async fn import_auth_cache( + json_data: String, + state: State<'_, AppState>, +) -> Result<(), String> { + let auth_cache_manager = &state.auth_cache_manager; + auth_cache_manager.import_cache(&json_data).await +} + +#[tauri::command] +pub fn hash_password(password: String) -> String { + crate::auth_cache::AuthCacheManager::hash_password(&password) +} + +#[tauri::command] +pub fn create_auth_cache_key( + service: String, + username: Option, + resource: Option, +) -> String { + crate::auth_cache::create_cache_key(&service, username.as_deref(), resource.as_deref()) +} + +// Terminal Integrations Commands +#[tauri::command] +pub async fn detect_installed_terminals( + state: State<'_, AppState>, +) -> Result, String> { + let terminal_integrations_manager = &state.terminal_integrations_manager; + Ok(terminal_integrations_manager.detect_terminals().await) +} + +#[tauri::command] +pub async fn get_default_terminal( + state: State<'_, AppState>, +) -> Result { + let terminal_integrations_manager = &state.terminal_integrations_manager; + Ok(terminal_integrations_manager.get_default_terminal().await) +} + +#[tauri::command] +pub async fn set_default_terminal( + emulator: crate::terminal_integrations::TerminalEmulator, + state: State<'_, AppState>, +) -> Result<(), String> { + let terminal_integrations_manager = &state.terminal_integrations_manager; + terminal_integrations_manager + .set_default_terminal(emulator) + .await +} + +#[tauri::command] +pub async fn launch_terminal_emulator( + emulator: Option, + options: crate::terminal_integrations::TerminalLaunchOptions, + state: State<'_, AppState>, +) -> Result<(), String> { + let terminal_integrations_manager = &state.terminal_integrations_manager; + terminal_integrations_manager + .launch_terminal(emulator, options) + .await +} + +#[tauri::command] +pub async fn get_terminal_config( + emulator: crate::terminal_integrations::TerminalEmulator, + state: State<'_, AppState>, +) -> Result, String> { + let terminal_integrations_manager = &state.terminal_integrations_manager; + Ok(terminal_integrations_manager + .get_terminal_config(emulator) + .await) +} + +#[tauri::command] +pub async fn update_terminal_config( + config: crate::terminal_integrations::TerminalConfig, + state: State<'_, AppState>, +) -> Result<(), String> { + let terminal_integrations_manager = &state.terminal_integrations_manager; + terminal_integrations_manager + .update_terminal_config(config) + .await; + Ok(()) +} + +#[tauri::command] +pub async fn list_detected_terminals( + state: State<'_, AppState>, +) -> Result, String> { + let terminal_integrations_manager = &state.terminal_integrations_manager; + Ok(terminal_integrations_manager + .list_detected_terminals() + .await) +} + +#[tauri::command] +pub async fn create_terminal_ssh_url( + emulator: crate::terminal_integrations::TerminalEmulator, + user: String, + host: String, + port: u16, + state: State<'_, AppState>, +) -> Result, String> { + let terminal_integrations_manager = &state.terminal_integrations_manager; + Ok(terminal_integrations_manager + .create_ssh_url(emulator, &user, &host, port) + .await) +} + +#[tauri::command] +pub async fn get_terminal_integration_stats( + state: State<'_, AppState>, +) -> Result { + let terminal_integrations_manager = &state.terminal_integrations_manager; + + let detected = terminal_integrations_manager + .list_detected_terminals() + .await; + let default = terminal_integrations_manager.get_default_terminal().await; + + let mut terminals_by_platform = HashMap::new(); + for info in &detected { + if let Some(config) = &info.config { + for platform in &config.platform { + terminals_by_platform + .entry(platform.clone()) + .or_insert_with(Vec::new) + .push(info.emulator); + } + } + } + + Ok(crate::terminal_integrations::TerminalIntegrationStats { + total_terminals: detected.len(), + installed_terminals: detected.iter().filter(|t| t.installed).count(), + default_terminal: default, + terminals_by_platform, + }) +} + +// Settings UI Commands +#[tauri::command] +pub async fn get_all_settings() -> Result, String> { + let settings = crate::settings::Settings::load().unwrap_or_default(); + let mut all_settings = HashMap::new(); + + all_settings.insert( + "general".to_string(), + serde_json::to_value(&settings.general).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "dashboard".to_string(), + serde_json::to_value(&settings.dashboard).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "advanced".to_string(), + serde_json::to_value(&settings.advanced).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "tty_forward".to_string(), + serde_json::to_value(&settings.tty_forward).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "monitoring".to_string(), + serde_json::to_value(&settings.monitoring).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "network".to_string(), + serde_json::to_value(&settings.network).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "port".to_string(), + serde_json::to_value(&settings.port).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "notifications".to_string(), + serde_json::to_value(&settings.notifications).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "terminal_integrations".to_string(), + serde_json::to_value(&settings.terminal_integrations).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "updates".to_string(), + serde_json::to_value(&settings.updates).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "security".to_string(), + serde_json::to_value(&settings.security).unwrap_or(serde_json::Value::Null), + ); + all_settings.insert( + "debug".to_string(), + serde_json::to_value(&settings.debug).unwrap_or(serde_json::Value::Null), + ); + + Ok(all_settings) +} + +#[tauri::command] +pub async fn update_setting(section: String, key: String, value: String) -> Result<(), String> { + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + + // Parse the JSON value + let json_value: serde_json::Value = + serde_json::from_str(&value).map_err(|e| format!("Invalid JSON value: {}", e))?; + + match section.as_str() { + "general" => match key.as_str() { + "launch_at_login" => { + settings.general.launch_at_login = json_value.as_bool().unwrap_or(false) + } + "show_dock_icon" => { + settings.general.show_dock_icon = json_value.as_bool().unwrap_or(true) + } + "default_terminal" => { + settings.general.default_terminal = + json_value.as_str().unwrap_or("system").to_string() + } + "default_shell" => { + settings.general.default_shell = + json_value.as_str().unwrap_or("default").to_string() + } + "show_welcome_on_startup" => { + settings.general.show_welcome_on_startup = json_value.as_bool() + } + "theme" => settings.general.theme = json_value.as_str().map(|s| s.to_string()), + "language" => settings.general.language = json_value.as_str().map(|s| s.to_string()), + "check_updates_automatically" => { + settings.general.check_updates_automatically = json_value.as_bool() + } + _ => return Err(format!("Unknown general setting: {}", key)), + }, + "dashboard" => match key.as_str() { + "server_port" => { + settings.dashboard.server_port = json_value.as_u64().unwrap_or(4020) as u16 + } + "enable_password" => { + settings.dashboard.enable_password = json_value.as_bool().unwrap_or(false) + } + "password" => { + settings.dashboard.password = json_value.as_str().unwrap_or("").to_string() + } + "access_mode" => { + settings.dashboard.access_mode = + json_value.as_str().unwrap_or("localhost").to_string() + } + "auto_cleanup" => { + settings.dashboard.auto_cleanup = json_value.as_bool().unwrap_or(true) + } + "session_limit" => { + settings.dashboard.session_limit = json_value.as_u64().map(|v| v as u32) + } + "idle_timeout_minutes" => { + settings.dashboard.idle_timeout_minutes = json_value.as_u64().map(|v| v as u32) + } + "enable_cors" => settings.dashboard.enable_cors = json_value.as_bool(), + _ => return Err(format!("Unknown dashboard setting: {}", key)), + }, + "advanced" => match key.as_str() { + "debug_mode" => settings.advanced.debug_mode = json_value.as_bool().unwrap_or(false), + "log_level" => { + settings.advanced.log_level = json_value.as_str().unwrap_or("info").to_string() + } + "session_timeout" => { + settings.advanced.session_timeout = json_value.as_u64().unwrap_or(0) as u32 + } + "ngrok_auth_token" => { + settings.advanced.ngrok_auth_token = json_value.as_str().map(|s| s.to_string()) + } + "ngrok_region" => { + settings.advanced.ngrok_region = json_value.as_str().map(|s| s.to_string()) + } + "ngrok_subdomain" => { + settings.advanced.ngrok_subdomain = json_value.as_str().map(|s| s.to_string()) + } + "enable_telemetry" => settings.advanced.enable_telemetry = json_value.as_bool(), + "experimental_features" => { + settings.advanced.experimental_features = json_value.as_bool() + } + _ => return Err(format!("Unknown advanced setting: {}", key)), + }, + "debug" => { + // Ensure debug settings exist + if settings.debug.is_none() { + settings.debug = Some(crate::settings::DebugSettings { + enable_debug_menu: false, + show_performance_stats: false, + enable_verbose_logging: false, + log_to_file: false, + log_file_path: None, + max_log_file_size_mb: None, + enable_dev_tools: false, + show_internal_errors: false, + }); + } + + if let Some(ref mut debug) = settings.debug { + match key.as_str() { + "enable_debug_menu" => { + debug.enable_debug_menu = json_value.as_bool().unwrap_or(false) + } + "show_performance_stats" => { + debug.show_performance_stats = json_value.as_bool().unwrap_or(false) + } + "enable_verbose_logging" => { + debug.enable_verbose_logging = json_value.as_bool().unwrap_or(false) + } + "log_to_file" => debug.log_to_file = json_value.as_bool().unwrap_or(false), + "log_file_path" => { + debug.log_file_path = json_value.as_str().map(|s| s.to_string()) + } + "max_log_file_size_mb" => { + debug.max_log_file_size_mb = json_value.as_u64().map(|v| v as u32) + } + "enable_dev_tools" => { + debug.enable_dev_tools = json_value.as_bool().unwrap_or(false) + } + "show_internal_errors" => { + debug.show_internal_errors = json_value.as_bool().unwrap_or(false) + } + _ => return Err(format!("Unknown debug setting: {}", key)), + } + } + } + _ => return Err(format!("Unknown settings section: {}", section)), + } + + settings.save() +} + +#[tauri::command] +pub async fn set_dashboard_password( + password: String, + state: State<'_, AppState>, + app: tauri::AppHandle, +) -> Result<(), String> { + // Update settings + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + settings.dashboard.password = password.clone(); + settings.dashboard.enable_password = !password.is_empty(); + settings.save()?; + + // Update the running server's auth configuration if it's running + let server = state.http_server.read().await; + if server.is_some() { + drop(server); + // Restart server to apply new auth settings + restart_server(state, app).await?; + } + + Ok(()) +} + +#[tauri::command] +pub async fn restart_server_with_port(port: u16, state: State<'_, AppState>, app: tauri::AppHandle) -> Result<(), String> { + // Update settings with new port + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + settings.dashboard.server_port = port; + settings.save()?; + + // Restart the server + restart_server(state, app).await?; + Ok(()) +} + +#[tauri::command] +pub async fn update_server_bind_address( + address: String, + state: State<'_, AppState>, + app_handle: tauri::AppHandle, +) -> Result<(), String> { + // Update settings + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + let access_mode = if address == "127.0.0.1" { + "localhost" + } else { + "network" + }; + settings.dashboard.access_mode = access_mode.to_string(); + settings.save()?; + + // Update tray menu to reflect new access mode + crate::tray_menu::TrayMenuManager::update_access_mode(&app_handle, access_mode).await; + + // Restart server to apply new bind address + restart_server(state, app_handle).await?; + Ok(()) +} + +#[tauri::command] +pub async fn set_dock_icon_visibility( + visible: bool, + app_handle: tauri::AppHandle, +) -> Result<(), String> { + // Update settings + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + settings.general.show_dock_icon = visible; + settings.save()?; + + // Apply the change + update_dock_icon_visibility(app_handle).await +} + +#[tauri::command] +pub async fn set_log_level(level: String) -> Result<(), String> { + // Update settings + let mut settings = crate::settings::Settings::load().unwrap_or_default(); + settings.advanced.log_level = level.clone(); + settings.save()?; + + // TODO: Apply the log level change to the running logger + tracing::info!("Log level changed to: {}", level); + + Ok(()) +} + +#[tauri::command] +pub async fn test_api_endpoint( + endpoint: String, + state: State<'_, AppState>, +) -> Result { + let server = state.http_server.read().await; + + if let Some(http_server) = server.as_ref() { + let port = http_server.port(); + let url = format!("http://127.0.0.1:{}{}", port, endpoint); + + // Create a simple HTTP client request + let client = reqwest::Client::new(); + let response = client + .get(&url) + .send() + .await + .map_err(|e| format!("Request failed: {}", e))?; + + let status = response.status(); + let body = response + .text() + .await + .unwrap_or_else(|_| "Failed to read body".to_string()); + + // Try to parse as JSON, fallback to text + let json_body = serde_json::from_str::(&body) + .unwrap_or_else(|_| serde_json::json!({ "body": body })); + + Ok(serde_json::json!({ + "status": status.as_u16(), + "endpoint": endpoint, + "response": json_body, + })) + } else { + Err("Server is not running".to_string()) + } +} + +#[derive(Debug, Serialize, Clone)] +pub struct ServerLog { + pub timestamp: String, + pub level: String, + pub message: String, +} + +#[tauri::command] +pub async fn get_server_logs(limit: usize) -> Result, String> { + // TODO: Implement actual log collection from the server + // For now, return dummy logs for the UI + let logs = vec![ + ServerLog { + timestamp: chrono::Utc::now().to_rfc3339(), + level: "info".to_string(), + message: "Server started on port 4020".to_string(), + }, + ServerLog { + timestamp: chrono::Utc::now().to_rfc3339(), + level: "info".to_string(), + message: "Health check endpoint accessed".to_string(), + }, + ]; + + Ok(logs.into_iter().take(limit).collect()) +} + +#[tauri::command] +pub async fn export_logs(_app_handle: tauri::AppHandle) -> Result<(), String> { + // Get logs + let logs = get_server_logs(1000).await?; + + // Convert to text format + let log_text = logs + .into_iter() + .map(|log| { + format!( + "[{}] {} - {}", + log.timestamp, + log.level.to_uppercase(), + log.message + ) + }) + .collect::>() + .join("\n"); + + // Save to file + let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S"); + let filename = format!("vibetunnel_logs_{}.txt", timestamp); + + // In Tauri v2, we should use the dialog plugin instead + // For now, let's just save to a default location + let downloads_dir = + dirs::download_dir().ok_or_else(|| "Could not find downloads directory".to_string())?; + let path = downloads_dir.join(&filename); + std::fs::write(&path, log_text).map_err(|e| e.to_string())?; + + Ok(()) +} + +#[tauri::command] +pub async fn get_local_ip() -> Result { + get_local_ip_address() + .await + .map(|opt| opt.unwrap_or_else(|| "127.0.0.1".to_string())) +} + +#[tauri::command] +pub async fn detect_terminals() -> Result { + crate::terminal_detector::detect_terminals() +} + +// Keychain commands +#[tauri::command] +pub async fn keychain_set_password(key: String, password: String) -> Result<(), String> { + crate::keychain::KeychainManager::set_password(&key, &password).map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_get_password(key: String) -> Result, String> { + crate::keychain::KeychainManager::get_password(&key).map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_delete_password(key: String) -> Result<(), String> { + crate::keychain::KeychainManager::delete_password(&key).map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_set_dashboard_password(password: String) -> Result<(), String> { + crate::keychain::KeychainManager::set_dashboard_password(&password).map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_get_dashboard_password() -> Result, String> { + crate::keychain::KeychainManager::get_dashboard_password().map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_delete_dashboard_password() -> Result<(), String> { + crate::keychain::KeychainManager::delete_dashboard_password().map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_set_ngrok_auth_token(token: String) -> Result<(), String> { + crate::keychain::KeychainManager::set_ngrok_auth_token(&token).map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_get_ngrok_auth_token() -> Result, String> { + crate::keychain::KeychainManager::get_ngrok_auth_token().map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_delete_ngrok_auth_token() -> Result<(), String> { + crate::keychain::KeychainManager::delete_ngrok_auth_token().map_err(|e| e.message) +} + +#[tauri::command] +pub async fn keychain_list_keys() -> Result, String> { + Ok(crate::keychain::KeychainManager::list_stored_keys()) +} + +#[tauri::command] +pub async fn request_all_permissions( + state: State<'_, AppState>, +) -> Result, String> { + let permissions_manager = &state.permissions_manager; + let mut results = Vec::new(); + + // Get all permissions that need to be requested + let all_permissions = permissions_manager.get_all_permissions().await; + + for permission_info in all_permissions { + // Only request permissions that are not already granted + if permission_info.status != crate::permissions::PermissionStatus::Granted + && permission_info.status != crate::permissions::PermissionStatus::NotApplicable + { + match permissions_manager + .request_permission(permission_info.permission_type) + .await + { + Ok(result) => results.push(result), + Err(e) => { + results.push(crate::permissions::PermissionRequestResult { + permission_type: permission_info.permission_type, + status: crate::permissions::PermissionStatus::Denied, + message: Some(e), + requires_restart: false, + requires_system_settings: false, + }); + } + } + } + } + + Ok(results) +} + +#[tauri::command] +pub async fn test_terminal(terminal: String, state: State<'_, AppState>) -> Result<(), String> { + // Use the terminal spawn service to test launching a terminal + state + .terminal_spawn_service + .spawn_terminal(crate::terminal_spawn_service::TerminalSpawnRequest { + session_id: "test".to_string(), + terminal_type: Some(terminal), + command: None, + working_directory: None, + environment: None, + }) + .await + .map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/tauri/src-tauri/src/debug_features.rs b/tauri/src-tauri/src/debug_features.rs new file mode 100644 index 00000000..bab6f12f --- /dev/null +++ b/tauri/src-tauri/src/debug_features.rs @@ -0,0 +1,688 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, VecDeque}; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::RwLock; + +/// Debug feature types +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum DebugFeature { + APITesting, + PerformanceMonitoring, + MemoryProfiling, + NetworkInspector, + EventLogger, + StateInspector, + LogViewer, + CrashReporter, + BenchmarkRunner, + DiagnosticReport, +} + +/// Debug log level +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +/// Debug log entry +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LogEntry { + pub timestamp: DateTime, + pub level: LogLevel, + pub component: String, + pub message: String, + pub metadata: HashMap, +} + +/// Performance metric +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PerformanceMetric { + pub name: String, + pub value: f64, + pub unit: String, + pub timestamp: DateTime, + pub tags: HashMap, +} + +/// Memory snapshot +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MemorySnapshot { + pub timestamp: DateTime, + pub heap_used_mb: f64, + pub heap_total_mb: f64, + pub external_mb: f64, + pub process_rss_mb: f64, + pub details: HashMap, +} + +/// Network request log +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetworkRequest { + pub id: String, + pub timestamp: DateTime, + pub method: String, + pub url: String, + pub status: Option, + pub duration_ms: Option, + pub request_headers: HashMap, + pub response_headers: HashMap, + pub request_body: Option, + pub response_body: Option, + pub error: Option, +} + +/// API test case +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestCase { + pub id: String, + pub name: String, + pub endpoint: String, + pub method: String, + pub headers: HashMap, + pub body: Option, + pub expected_status: u16, + pub expected_body: Option, + pub timeout_ms: u64, +} + +/// API test result +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct APITestResult { + pub test_id: String, + pub success: bool, + pub actual_status: Option, + pub actual_body: Option, + pub duration_ms: u64, + pub error: Option, + pub timestamp: DateTime, +} + +/// Benchmark configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BenchmarkConfig { + pub name: String, + pub iterations: u32, + pub warmup_iterations: u32, + pub timeout_ms: u64, + pub collect_memory: bool, + pub collect_cpu: bool, +} + +/// Benchmark result +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BenchmarkResult { + pub name: String, + pub iterations: u32, + pub mean_ms: f64, + pub median_ms: f64, + pub min_ms: f64, + pub max_ms: f64, + pub std_dev_ms: f64, + pub memory_usage_mb: Option, + pub cpu_usage_percent: Option, + pub timestamp: DateTime, +} + +/// Diagnostic report +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiagnosticReport { + pub timestamp: DateTime, + pub system_info: SystemInfo, + pub app_info: AppInfo, + pub performance_summary: PerformanceSummary, + pub error_summary: ErrorSummary, + pub recommendations: Vec, +} + +/// System information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SystemInfo { + pub os: String, + pub arch: String, + pub cpu_count: usize, + pub total_memory_mb: u64, + pub available_memory_mb: u64, + pub disk_space_mb: u64, + pub node_version: Option, + pub rust_version: String, +} + +/// Application information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AppInfo { + pub version: String, + pub build_date: String, + pub uptime_seconds: u64, + pub active_sessions: usize, + pub total_requests: u64, + pub error_count: u64, +} + +/// Performance summary +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PerformanceSummary { + pub avg_response_time_ms: f64, + pub p95_response_time_ms: f64, + pub p99_response_time_ms: f64, + pub requests_per_second: f64, + pub cpu_usage_percent: f64, + pub memory_usage_mb: f64, +} + +/// Error summary +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ErrorSummary { + pub total_errors: u64, + pub errors_by_type: HashMap, + pub recent_errors: Vec, +} + +/// Debug settings +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DebugSettings { + pub enabled: bool, + pub log_level: LogLevel, + pub max_log_entries: usize, + pub enable_performance_monitoring: bool, + pub enable_memory_profiling: bool, + pub enable_network_inspector: bool, + pub enable_crash_reporting: bool, + pub log_to_file: bool, + pub log_file_path: Option, + pub performance_sample_interval_ms: u64, + pub memory_sample_interval_ms: u64, +} + +impl Default for DebugSettings { + fn default() -> Self { + Self { + enabled: cfg!(debug_assertions), + log_level: LogLevel::Info, + max_log_entries: 10000, + enable_performance_monitoring: false, + enable_memory_profiling: false, + enable_network_inspector: false, + enable_crash_reporting: true, + log_to_file: false, + log_file_path: None, + performance_sample_interval_ms: 1000, + memory_sample_interval_ms: 5000, + } + } +} + +/// Debug features manager +pub struct DebugFeaturesManager { + settings: Arc>, + logs: Arc>>, + performance_metrics: Arc>>, + memory_snapshots: Arc>>, + network_requests: Arc>>, + api_test_results: Arc>>>, + benchmark_results: Arc>>, + notification_manager: Option>, +} + +impl DebugFeaturesManager { + /// Create a new debug features manager + pub fn new() -> Self { + Self { + settings: Arc::new(RwLock::new(DebugSettings::default())), + logs: Arc::new(RwLock::new(VecDeque::new())), + performance_metrics: Arc::new(RwLock::new(VecDeque::new())), + memory_snapshots: Arc::new(RwLock::new(VecDeque::new())), + network_requests: Arc::new(RwLock::new(HashMap::new())), + api_test_results: Arc::new(RwLock::new(HashMap::new())), + benchmark_results: Arc::new(RwLock::new(Vec::new())), + notification_manager: None, + } + } + + /// Set the notification manager + pub fn set_notification_manager( + &mut self, + notification_manager: Arc, + ) { + self.notification_manager = Some(notification_manager); + } + + /// Get debug settings + pub async fn get_settings(&self) -> DebugSettings { + self.settings.read().await.clone() + } + + /// Update debug settings + pub async fn update_settings(&self, settings: DebugSettings) { + *self.settings.write().await = settings; + } + + /// Log a message + pub async fn log( + &self, + level: LogLevel, + component: &str, + message: &str, + metadata: HashMap, + ) { + let settings = self.settings.read().await; + + // Check if logging is enabled and level is appropriate + if !settings.enabled || level < settings.log_level { + return; + } + + let entry = LogEntry { + timestamp: Utc::now(), + level, + component: component.to_string(), + message: message.to_string(), + metadata, + }; + + // Add to in-memory log + let mut logs = self.logs.write().await; + logs.push_back(entry.clone()); + + // Limit log size + while logs.len() > settings.max_log_entries { + logs.pop_front(); + } + + // Log to file if enabled + if settings.log_to_file { + if let Some(path) = &settings.log_file_path { + let _ = self.write_log_to_file(&entry, path).await; + } + } + } + + /// Record a performance metric + pub async fn record_metric( + &self, + name: &str, + value: f64, + unit: &str, + tags: HashMap, + ) { + let settings = self.settings.read().await; + + if !settings.enabled || !settings.enable_performance_monitoring { + return; + } + + let metric = PerformanceMetric { + name: name.to_string(), + value, + unit: unit.to_string(), + timestamp: Utc::now(), + tags, + }; + + let mut metrics = self.performance_metrics.write().await; + metrics.push_back(metric); + + // Keep only last 1000 metrics + while metrics.len() > 1000 { + metrics.pop_front(); + } + } + + /// Take a memory snapshot + pub async fn take_memory_snapshot(&self) -> Result { + let settings = self.settings.read().await; + + if !settings.enabled || !settings.enable_memory_profiling { + return Err("Memory profiling is disabled".to_string()); + } + + // TODO: Implement actual memory profiling + let snapshot = MemorySnapshot { + timestamp: Utc::now(), + heap_used_mb: 0.0, + heap_total_mb: 0.0, + external_mb: 0.0, + process_rss_mb: 0.0, + details: HashMap::new(), + }; + + let mut snapshots = self.memory_snapshots.write().await; + snapshots.push_back(snapshot.clone()); + + // Keep only last 100 snapshots + while snapshots.len() > 100 { + snapshots.pop_front(); + } + + Ok(snapshot) + } + + /// Log a network request + #[allow(dead_code)] + pub async fn log_network_request(&self, request: NetworkRequest) { + let settings = self.settings.read().await; + + if !settings.enabled || !settings.enable_network_inspector { + return; + } + + let mut requests = self.network_requests.write().await; + requests.insert(request.id.clone(), request); + + // Keep only last 500 requests + if requests.len() > 500 { + // Remove oldest entries + let mut ids: Vec<_> = requests.keys().cloned().collect(); + ids.sort(); + for id in ids.iter().take(requests.len() - 500) { + requests.remove(id); + } + } + } + + /// Run API tests + pub async fn run_api_tests(&self, tests: Vec) -> Vec { + let mut results = Vec::new(); + + for test in tests { + let result = self.run_single_api_test(&test).await; + results.push(result.clone()); + + // Store result + let mut test_results = self.api_test_results.write().await; + test_results + .entry(test.id.clone()) + .or_insert_with(Vec::new) + .push(result); + } + + results + } + + /// Run a single API test + async fn run_single_api_test(&self, test: &APITestCase) -> APITestResult { + let start = std::time::Instant::now(); + + // TODO: Implement actual API testing + let duration_ms = start.elapsed().as_millis() as u64; + + APITestResult { + test_id: test.id.clone(), + success: false, + actual_status: None, + actual_body: None, + duration_ms, + error: Some("API testing not yet implemented".to_string()), + timestamp: Utc::now(), + } + } + + /// Run benchmarks + pub async fn run_benchmarks(&self, configs: Vec) -> Vec { + let mut results = Vec::new(); + + for config in configs { + let result = self.run_single_benchmark(&config).await; + results.push(result.clone()); + + // Store result + self.benchmark_results.write().await.push(result); + } + + results + } + + /// Run a single benchmark + async fn run_single_benchmark(&self, config: &BenchmarkConfig) -> BenchmarkResult { + // TODO: Implement actual benchmarking + BenchmarkResult { + name: config.name.clone(), + iterations: config.iterations, + mean_ms: 0.0, + median_ms: 0.0, + min_ms: 0.0, + max_ms: 0.0, + std_dev_ms: 0.0, + memory_usage_mb: None, + cpu_usage_percent: None, + timestamp: Utc::now(), + } + } + + /// Generate diagnostic report + pub async fn generate_diagnostic_report(&self) -> DiagnosticReport { + let system_info = self.get_system_info().await; + let app_info = self.get_app_info().await; + let performance_summary = self.get_performance_summary().await; + let error_summary = self.get_error_summary().await; + let recommendations = self.generate_recommendations( + &system_info, + &app_info, + &performance_summary, + &error_summary, + ); + + DiagnosticReport { + timestamp: Utc::now(), + system_info, + app_info, + performance_summary, + error_summary, + recommendations, + } + } + + /// Get recent logs + pub async fn get_logs(&self, limit: Option, level: Option) -> Vec { + let logs = self.logs.read().await; + let iter = logs.iter().rev(); + + let filtered: Vec<_> = if let Some(min_level) = level { + iter.filter(|log| log.level >= min_level).cloned().collect() + } else { + iter.cloned().collect() + }; + + match limit { + Some(n) => filtered.into_iter().take(n).collect(), + None => filtered, + } + } + + /// Get performance metrics + pub async fn get_performance_metrics(&self, limit: Option) -> Vec { + let metrics = self.performance_metrics.read().await; + match limit { + Some(n) => metrics.iter().rev().take(n).cloned().collect(), + None => metrics.iter().cloned().collect(), + } + } + + /// Get memory snapshots + pub async fn get_memory_snapshots(&self, limit: Option) -> Vec { + let snapshots = self.memory_snapshots.read().await; + match limit { + Some(n) => snapshots.iter().rev().take(n).cloned().collect(), + None => snapshots.iter().cloned().collect(), + } + } + + /// Get network requests + pub async fn get_network_requests(&self, limit: Option) -> Vec { + let requests = self.network_requests.read().await; + let mut sorted: Vec<_> = requests.values().cloned().collect(); + sorted.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + + match limit { + Some(n) => sorted.into_iter().take(n).collect(), + None => sorted, + } + } + + /// Clear all debug data + pub async fn clear_all_data(&self) { + self.logs.write().await.clear(); + self.performance_metrics.write().await.clear(); + self.memory_snapshots.write().await.clear(); + self.network_requests.write().await.clear(); + self.api_test_results.write().await.clear(); + self.benchmark_results.write().await.clear(); + } + + /// Enable/disable debug mode + pub async fn set_debug_mode(&self, enabled: bool) { + self.settings.write().await.enabled = enabled; + + if let Some(notification_manager) = &self.notification_manager { + let message = if enabled { + "Debug mode enabled" + } else { + "Debug mode disabled" + }; + let _ = notification_manager + .notify_success("Debug Mode", message) + .await; + } + } + + // Helper methods + async fn write_log_to_file(&self, entry: &LogEntry, path: &PathBuf) -> Result<(), String> { + use tokio::io::AsyncWriteExt; + + let log_line = format!( + "[{}] [{}] [{}] {}\n", + entry.timestamp.format("%Y-%m-%d %H:%M:%S%.3f"), + format!("{:?}", entry.level), + entry.component, + entry.message + ); + + let mut file = tokio::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + .await + .map_err(|e| e.to_string())?; + + file.write_all(log_line.as_bytes()) + .await + .map_err(|e| e.to_string())?; + + Ok(()) + } + + async fn get_system_info(&self) -> SystemInfo { + SystemInfo { + os: std::env::consts::OS.to_string(), + arch: std::env::consts::ARCH.to_string(), + cpu_count: num_cpus::get(), + total_memory_mb: 0, // TODO: Get actual memory + available_memory_mb: 0, + disk_space_mb: 0, + node_version: None, + rust_version: "1.70.0".to_string(), // TODO: Get actual rust version + } + } + + async fn get_app_info(&self) -> AppInfo { + AppInfo { + version: env!("CARGO_PKG_VERSION").to_string(), + build_date: chrono::Utc::now().to_rfc3339(), // TODO: Get actual build date + uptime_seconds: 0, // TODO: Track uptime + active_sessions: 0, + total_requests: 0, + error_count: 0, + } + } + + async fn get_performance_summary(&self) -> PerformanceSummary { + PerformanceSummary { + avg_response_time_ms: 0.0, + p95_response_time_ms: 0.0, + p99_response_time_ms: 0.0, + requests_per_second: 0.0, + cpu_usage_percent: 0.0, + memory_usage_mb: 0.0, + } + } + + async fn get_error_summary(&self) -> ErrorSummary { + let logs = self.logs.read().await; + let errors: Vec<_> = logs + .iter() + .filter(|log| log.level == LogLevel::Error) + .cloned() + .collect(); + + let mut errors_by_type = HashMap::new(); + for error in &errors { + let error_type = error + .metadata + .get("type") + .and_then(|v| v.as_str()) + .unwrap_or("unknown") + .to_string(); + *errors_by_type.entry(error_type).or_insert(0) += 1; + } + + ErrorSummary { + total_errors: errors.len() as u64, + errors_by_type, + recent_errors: errors.into_iter().rev().take(10).collect(), + } + } + + fn generate_recommendations( + &self, + system: &SystemInfo, + _app: &AppInfo, + perf: &PerformanceSummary, + errors: &ErrorSummary, + ) -> Vec { + let mut recommendations = Vec::new(); + + if perf.cpu_usage_percent > 80.0 { + recommendations.push( + "High CPU usage detected. Consider optimizing performance-critical code." + .to_string(), + ); + } + + if perf.memory_usage_mb > (system.total_memory_mb as f64 * 0.8) { + recommendations.push("High memory usage detected. Check for memory leaks.".to_string()); + } + + if errors.total_errors > 100 { + recommendations + .push("High error rate detected. Review error logs for patterns.".to_string()); + } + + if perf.avg_response_time_ms > 1000.0 { + recommendations.push( + "Slow response times detected. Consider caching or query optimization.".to_string(), + ); + } + + recommendations + } +} + +/// Debug statistics +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DebugStats { + pub total_logs: usize, + pub logs_by_level: HashMap, + pub total_metrics: usize, + pub total_snapshots: usize, + pub total_requests: usize, + pub total_test_results: usize, + pub total_benchmarks: usize, +} + +// Re-export num_cpus if needed +extern crate num_cpus; diff --git a/tauri/src-tauri/src/fs_api.rs b/tauri/src-tauri/src/fs_api.rs new file mode 100644 index 00000000..4f2f5a14 --- /dev/null +++ b/tauri/src-tauri/src/fs_api.rs @@ -0,0 +1,466 @@ +use axum::{ + extract::Query, + http::{header, StatusCode}, + response::{IntoResponse, Response}, + Json, +}; +use base64::Engine; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use tokio::fs; +use tokio::io::AsyncReadExt; + +#[derive(Debug, Deserialize)] +pub struct FileQuery { + pub path: String, +} + +#[derive(Debug, Serialize)] +pub struct FileMetadata { + pub name: String, + pub path: String, + pub size: u64, + pub is_dir: bool, + pub is_file: bool, + pub is_symlink: bool, + pub readonly: bool, + pub hidden: bool, + pub created: Option, + pub modified: Option, + pub accessed: Option, + #[cfg(unix)] + pub permissions: Option, + pub mime_type: Option, +} + +#[derive(Debug, Deserialize)] +pub struct MoveRequest { + pub from: String, + pub to: String, +} + +#[derive(Debug, Deserialize)] +pub struct CopyRequest { + pub from: String, + pub to: String, + pub overwrite: Option, +} + +#[derive(Debug, Deserialize)] +pub struct WriteFileRequest { + pub path: String, + pub content: String, + pub encoding: Option, +} + +#[derive(Debug, Serialize)] +pub struct OperationResult { + pub success: bool, + pub message: String, +} + +/// Expand tilde to home directory +fn expand_path(path: &str) -> Result { + if path.starts_with('~') { + let home = dirs::home_dir().ok_or(StatusCode::INTERNAL_SERVER_ERROR)?; + Ok(home.join(path.strip_prefix("~/").unwrap_or(""))) + } else { + Ok(PathBuf::from(path)) + } +} + +/// Get detailed file metadata +pub async fn get_file_info( + Query(params): Query, +) -> Result, StatusCode> { + let path = expand_path(¶ms.path)?; + + let metadata = fs::metadata(&path) + .await + .map_err(|_| StatusCode::NOT_FOUND)?; + + let name = path + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_else(|| path.to_string_lossy().to_string()); + + let is_symlink = fs::symlink_metadata(&path) + .await + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false); + + let hidden = name.starts_with('.'); + + let created = metadata + .created() + .map(|t| { + let datetime: chrono::DateTime = t.into(); + datetime.to_rfc3339() + }) + .ok(); + + let modified = metadata + .modified() + .map(|t| { + let datetime: chrono::DateTime = t.into(); + datetime.to_rfc3339() + }) + .ok(); + + let accessed = metadata + .accessed() + .map(|t| { + let datetime: chrono::DateTime = t.into(); + datetime.to_rfc3339() + }) + .ok(); + + #[cfg(unix)] + let permissions = { + use std::os::unix::fs::PermissionsExt; + Some(format!("{:o}", metadata.permissions().mode() & 0o777)) + }; + + let mime_type = if metadata.is_file() { + // Simple MIME type detection based on extension + let ext = path.extension().and_then(|e| e.to_str()).unwrap_or(""); + + Some( + match ext { + "txt" => "text/plain", + "html" | "htm" => "text/html", + "css" => "text/css", + "js" => "application/javascript", + "json" => "application/json", + "png" => "image/png", + "jpg" | "jpeg" => "image/jpeg", + "gif" => "image/gif", + "pdf" => "application/pdf", + "zip" => "application/zip", + _ => "application/octet-stream", + } + .to_string(), + ) + } else { + None + }; + + Ok(Json(FileMetadata { + name, + path: path.to_string_lossy().to_string(), + size: metadata.len(), + is_dir: metadata.is_dir(), + is_file: metadata.is_file(), + is_symlink, + readonly: metadata.permissions().readonly(), + hidden, + created, + modified, + accessed, + #[cfg(unix)] + permissions, + #[cfg(not(unix))] + permissions: None, + mime_type, + })) +} + +/// Read file contents +pub async fn read_file(Query(params): Query) -> Result { + let path = expand_path(¶ms.path)?; + + // Check if file exists and is a file + let metadata = fs::metadata(&path) + .await + .map_err(|_| StatusCode::NOT_FOUND)?; + + if !metadata.is_file() { + return Err(StatusCode::BAD_REQUEST); + } + + // Read file contents + let mut file = fs::File::open(&path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let mut contents = Vec::new(); + file.read_to_end(&mut contents) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + // Determine content type + let content_type = path + .extension() + .and_then(|e| e.to_str()) + .and_then(|ext| match ext { + "txt" => Some("text/plain"), + "html" | "htm" => Some("text/html"), + "css" => Some("text/css"), + "js" => Some("application/javascript"), + "json" => Some("application/json"), + "png" => Some("image/png"), + "jpg" | "jpeg" => Some("image/jpeg"), + "gif" => Some("image/gif"), + "pdf" => Some("application/pdf"), + _ => None, + }) + .unwrap_or("application/octet-stream"); + + Ok(([(header::CONTENT_TYPE, content_type)], contents).into_response()) +} + +/// Write file contents +pub async fn write_file( + Json(req): Json, +) -> Result, StatusCode> { + let path = expand_path(&req.path)?; + + // Ensure parent directory exists + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + + // Write file + let content = if req.encoding.as_deref() == Some("base64") { + base64::engine::general_purpose::STANDARD + .decode(&req.content) + .map_err(|_| StatusCode::BAD_REQUEST)? + } else { + req.content.into_bytes() + }; + + fs::write(&path, content) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(OperationResult { + success: true, + message: format!("File written successfully: {}", path.display()), + })) +} + +/// Delete file or directory +pub async fn delete_file( + Query(params): Query, +) -> Result, StatusCode> { + let path = expand_path(¶ms.path)?; + + // Check if path exists + let metadata = fs::metadata(&path) + .await + .map_err(|_| StatusCode::NOT_FOUND)?; + + // Delete based on type + if metadata.is_dir() { + fs::remove_dir_all(&path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } else { + fs::remove_file(&path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + + Ok(Json(OperationResult { + success: true, + message: format!("Deleted: {}", path.display()), + })) +} + +/// Move/rename file or directory +pub async fn move_file(Json(req): Json) -> Result, StatusCode> { + let from_path = expand_path(&req.from)?; + let to_path = expand_path(&req.to)?; + + // Check if source exists + if !from_path.exists() { + return Err(StatusCode::NOT_FOUND); + } + + // Check if destination already exists + if to_path.exists() { + return Err(StatusCode::CONFLICT); + } + + // Ensure destination parent directory exists + if let Some(parent) = to_path.parent() { + fs::create_dir_all(parent) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + + // Move the file/directory + fs::rename(&from_path, &to_path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(OperationResult { + success: true, + message: format!( + "Moved from {} to {}", + from_path.display(), + to_path.display() + ), + })) +} + +/// Copy file or directory +pub async fn copy_file(Json(req): Json) -> Result, StatusCode> { + let from_path = expand_path(&req.from)?; + let to_path = expand_path(&req.to)?; + + // Check if source exists + let metadata = fs::metadata(&from_path) + .await + .map_err(|_| StatusCode::NOT_FOUND)?; + + // Check if destination already exists + if to_path.exists() && !req.overwrite.unwrap_or(false) { + return Err(StatusCode::CONFLICT); + } + + // Ensure destination parent directory exists + if let Some(parent) = to_path.parent() { + fs::create_dir_all(parent) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + + // Copy based on type + if metadata.is_file() { + fs::copy(&from_path, &to_path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } else if metadata.is_dir() { + // Recursive directory copy + copy_dir_recursive(&from_path, &to_path).await?; + } + + Ok(Json(OperationResult { + success: true, + message: format!( + "Copied from {} to {}", + from_path.display(), + to_path.display() + ), + })) +} + +/// Recursively copy a directory +async fn copy_dir_recursive(from: &PathBuf, to: &PathBuf) -> Result<(), StatusCode> { + fs::create_dir_all(to) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let mut entries = fs::read_dir(from) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + while let Some(entry) = entries + .next_entry() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + { + let from_path = entry.path(); + let to_path = to.join(entry.file_name()); + + let metadata = entry + .metadata() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + if metadata.is_file() { + fs::copy(&from_path, &to_path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } else if metadata.is_dir() { + Box::pin(copy_dir_recursive(&from_path, &to_path)).await?; + } + } + + Ok(()) +} + +/// Search for files matching a pattern +#[derive(Debug, Deserialize)] +pub struct SearchQuery { + pub path: String, + pub pattern: String, + pub max_depth: Option, +} + +#[derive(Debug, Serialize)] +pub struct SearchResult { + pub path: String, + pub name: String, + pub is_dir: bool, + pub size: u64, +} + +pub async fn search_files( + Query(params): Query, +) -> Result>, StatusCode> { + let base_path = expand_path(¶ms.path)?; + let pattern = params.pattern.to_lowercase(); + let max_depth = params.max_depth.unwrap_or(5); + + let mut results = Vec::new(); + search_recursive(&base_path, &pattern, 0, max_depth, &mut results).await?; + + Ok(Json(results)) +} + +async fn search_recursive( + path: &PathBuf, + pattern: &str, + depth: u32, + max_depth: u32, + results: &mut Vec, +) -> Result<(), StatusCode> { + if depth > max_depth { + return Ok(()); + } + + let mut entries = fs::read_dir(path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + while let Some(entry) = entries + .next_entry() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + { + let entry_path = entry.path(); + let file_name = entry.file_name().to_string_lossy().to_string(); + + if file_name.to_lowercase().contains(pattern) { + let metadata = entry + .metadata() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + results.push(SearchResult { + path: entry_path.to_string_lossy().to_string(), + name: file_name, + is_dir: metadata.is_dir(), + size: metadata.len(), + }); + } + + // Recurse into directories + if entry.file_type().await.map(|t| t.is_dir()).unwrap_or(false) { + Box::pin(search_recursive( + &entry_path, + pattern, + depth + 1, + max_depth, + results, + )) + .await?; + } + } + + Ok(()) +} diff --git a/tauri/src-tauri/src/keychain.rs b/tauri/src-tauri/src/keychain.rs new file mode 100644 index 00000000..7ece50b0 --- /dev/null +++ b/tauri/src-tauri/src/keychain.rs @@ -0,0 +1,143 @@ +use keyring::{Entry, Error as KeyringError}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +const SERVICE_NAME: &str = "VibeTunnel"; +const DASHBOARD_PASSWORD_KEY: &str = "dashboard_password"; +const NGROK_AUTH_TOKEN_KEY: &str = "ngrok_auth_token"; + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeychainError { + pub message: String, +} + +impl From for KeychainError { + fn from(err: KeyringError) -> Self { + Self { + message: err.to_string(), + } + } +} + +pub struct KeychainManager; + +impl KeychainManager { + /// Store a password in the system keychain + pub fn set_password(key: &str, password: &str) -> Result<(), KeychainError> { + let entry = Entry::new(SERVICE_NAME, key)?; + entry.set_password(password)?; + Ok(()) + } + + /// Retrieve a password from the system keychain + pub fn get_password(key: &str) -> Result, KeychainError> { + let entry = Entry::new(SERVICE_NAME, key)?; + match entry.get_password() { + Ok(password) => Ok(Some(password)), + Err(KeyringError::NoEntry) => Ok(None), + Err(err) => Err(err.into()), + } + } + + /// Delete a password from the system keychain + pub fn delete_password(key: &str) -> Result<(), KeychainError> { + let entry = Entry::new(SERVICE_NAME, key)?; + match entry.delete_credential() { + Ok(()) => Ok(()), + Err(KeyringError::NoEntry) => Ok(()), // Already deleted + Err(err) => Err(err.into()), + } + } + + /// Store the dashboard password + pub fn set_dashboard_password(password: &str) -> Result<(), KeychainError> { + Self::set_password(DASHBOARD_PASSWORD_KEY, password) + } + + /// Get the dashboard password + pub fn get_dashboard_password() -> Result, KeychainError> { + Self::get_password(DASHBOARD_PASSWORD_KEY) + } + + /// Delete the dashboard password + pub fn delete_dashboard_password() -> Result<(), KeychainError> { + Self::delete_password(DASHBOARD_PASSWORD_KEY) + } + + /// Store the ngrok auth token + pub fn set_ngrok_auth_token(token: &str) -> Result<(), KeychainError> { + Self::set_password(NGROK_AUTH_TOKEN_KEY, token) + } + + /// Get the ngrok auth token + pub fn get_ngrok_auth_token() -> Result, KeychainError> { + Self::get_password(NGROK_AUTH_TOKEN_KEY) + } + + /// Delete the ngrok auth token + pub fn delete_ngrok_auth_token() -> Result<(), KeychainError> { + Self::delete_password(NGROK_AUTH_TOKEN_KEY) + } + + /// Get all stored credentials (returns keys only, not passwords) + pub fn list_stored_keys() -> Vec { + let mut keys = Vec::new(); + + // Check if dashboard password exists + if Self::get_dashboard_password().unwrap_or(None).is_some() { + keys.push(DASHBOARD_PASSWORD_KEY.to_string()); + } + + // Check if ngrok token exists + if Self::get_ngrok_auth_token().unwrap_or(None).is_some() { + keys.push(NGROK_AUTH_TOKEN_KEY.to_string()); + } + + keys + } + + /// Migrate passwords from settings to keychain + #[allow(dead_code)] + pub fn migrate_from_settings(settings: &HashMap) -> Result<(), KeychainError> { + // Migrate dashboard password + if let Some(password) = settings.get("dashboard_password") { + if !password.is_empty() { + Self::set_dashboard_password(password)?; + } + } + + // Migrate ngrok auth token + if let Some(token) = settings.get("ngrok_auth_token") { + if !token.is_empty() { + Self::set_ngrok_auth_token(token)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_password_operations() { + let test_key = "test_password"; + let test_password = "super_secret_123"; + + // Store password + assert!(KeychainManager::set_password(test_key, test_password).is_ok()); + + // Retrieve password + let retrieved = KeychainManager::get_password(test_key).unwrap(); + assert_eq!(retrieved, Some(test_password.to_string())); + + // Delete password + assert!(KeychainManager::delete_password(test_key).is_ok()); + + // Verify deletion + let deleted = KeychainManager::get_password(test_key).unwrap(); + assert_eq!(deleted, None); + } +} diff --git a/tauri/src-tauri/src/lib.rs b/tauri/src-tauri/src/lib.rs new file mode 100644 index 00000000..7cb074c0 --- /dev/null +++ b/tauri/src-tauri/src/lib.rs @@ -0,0 +1,35 @@ +pub mod api_testing; +pub mod app_mover; +pub mod auth; +pub mod auth_cache; +pub mod auto_launch; +pub mod backend_manager; +pub mod cli_installer; +pub mod commands; +pub mod debug_features; +pub mod fs_api; +pub mod keychain; +pub mod network_utils; +pub mod ngrok; +pub mod notification_manager; +pub mod permissions; +pub mod port_conflict; +pub mod server; +pub mod session_monitor; +pub mod settings; +pub mod state; +pub mod terminal; +pub mod terminal_detector; +pub mod terminal_integrations; +pub mod terminal_spawn_service; +pub mod tray_menu; +pub mod tty_forward; +#[cfg(unix)] +pub mod unix_socket_server; +pub mod updater; +pub mod welcome; + +#[cfg(mobile)] +pub fn init() { + // Mobile-specific initialization +} diff --git a/tauri/src-tauri/src/main.rs b/tauri/src-tauri/src/main.rs new file mode 100644 index 00000000..3fb57aab --- /dev/null +++ b/tauri/src-tauri/src/main.rs @@ -0,0 +1,1024 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use tauri::menu::Menu; +use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; +use tauri::{AppHandle, Emitter, Manager, WindowEvent}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +mod api_client; +mod api_testing; +mod app_mover; +mod auth_cache; +mod auto_launch; +mod backend_manager; +mod cli_installer; +mod commands; +mod debug_features; +mod fs_api; +mod keychain; +mod network_utils; +mod ngrok; +mod notification_manager; +mod permissions; +mod port_conflict; +mod session_monitor; +mod settings; +mod state; +mod terminal; +mod terminal_detector; +mod terminal_integrations; +mod terminal_spawn_service; +mod tray_menu; +mod tty_forward; +#[cfg(unix)] +mod unix_socket_server; +mod updater; +mod welcome; + +use commands::ServerStatus; +use commands::*; +use state::AppState; + +#[tauri::command] +fn open_settings_window(app: AppHandle, tab: Option) -> Result<(), String> { + // Build URL with optional tab parameter + let url = if let Some(tab_name) = tab { + format!("settings.html?tab={}", tab_name) + } else { + "settings.html".to_string() + }; + + // Check if settings window already exists + if let Some(window) = app.get_webview_window("settings") { + // Navigate to the URL with the tab parameter if window exists + window + .eval(&format!("window.location.href = '{}'", url)) + .map_err(|e| e.to_string())?; + window.show().map_err(|e| e.to_string())?; + window.set_focus().map_err(|e| e.to_string())?; + } else { + // Create new settings window + let window = + tauri::WebviewWindowBuilder::new(&app, "settings", tauri::WebviewUrl::App(url.into())) + .title("VibeTunnel Settings") + .inner_size(1200.0, 800.0) + .resizable(true) + .decorations(true) + .center() + .build() + .map_err(|e| e.to_string())?; + + // Handle close event to destroy the window + let window_clone = window.clone(); + window.on_window_event(move |event| { + if let WindowEvent::CloseRequested { .. } = event { + let _ = window_clone.close(); + } + }); + } + Ok(()) +} + +#[tauri::command] +fn focus_terminal_window(session_id: String) -> Result<(), String> { + // Focus the terminal window for the given session + #[cfg(target_os = "macos")] + { + use std::process::Command; + + // Use AppleScript to focus the terminal window + let script = format!( + r#"tell application "System Events" + set allProcesses to name of every process + if "Terminal" is in allProcesses then + tell application "Terminal" + activate + repeat with w in windows + if name of w contains "{}" then + set index of w to 1 + return + end if + end repeat + end tell + end if + end tell"#, + session_id + ); + + let output = Command::new("osascript") + .arg("-e") + .arg(&script) + .output() + .map_err(|e| format!("Failed to execute AppleScript: {}", e))?; + + if !output.status.success() { + let error = String::from_utf8_lossy(&output.stderr); + return Err(format!("AppleScript failed: {}", error)); + } + } + + #[cfg(not(target_os = "macos"))] + { + // On other platforms, we can try to use wmctrl or similar tools + // For now, just return an error + return Err("Terminal window focus not implemented for this platform".to_string()); + } + + Ok(()) +} + +#[tauri::command] +fn open_session_detail_window(app: AppHandle, session_id: String) -> Result<(), String> { + // Build URL with session ID parameter + let url = format!("session-detail.html?id={}", session_id); + let window_id = format!("session-detail-{}", session_id); + + // Check if session detail window already exists for this session + if let Some(window) = app.get_webview_window(&window_id) { + window.show().map_err(|e| e.to_string())?; + window.set_focus().map_err(|e| e.to_string())?; + } else { + // Create new session detail window + let window = tauri::WebviewWindowBuilder::new( + &app, + window_id, + tauri::WebviewUrl::App(url.into()), + ) + .title("Session Details") + .inner_size(600.0, 450.0) + .resizable(true) + .decorations(true) + .center() + .build() + .map_err(|e| e.to_string())?; + + // Handle close event to destroy the window + let window_clone = window.clone(); + window.on_window_event(move |event| { + if let WindowEvent::CloseRequested { .. } = event { + let _ = window_clone.close(); + } + }); + } + Ok(()) +} + +fn update_tray_menu_status(app: &AppHandle, port: u16, session_count: usize) { + // Update tray menu status using the tray menu manager + let app_handle = app.clone(); + tauri::async_runtime::spawn(async move { + tray_menu::TrayMenuManager::update_server_status(&app_handle, port, true).await; + tray_menu::TrayMenuManager::update_session_count(&app_handle, session_count).await; + }); +} + +fn main() { + // Initialize tracing + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "vibetunnel=debug,tower_http=debug".into()), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + + tauri::Builder::default() + .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_notification::init()) + .plugin(tauri_plugin_window_state::Builder::default().build()) + .plugin(tauri_plugin_single_instance::init(|_app, _args, _cwd| { + // Don't show main window on startup - app runs in system tray + // let _ = show_main_window(app.app_handle().clone()); + })) + .manage(AppState::new()) + .invoke_handler(tauri::generate_handler![ + create_terminal, + list_terminals, + close_terminal, + resize_terminal, + write_to_terminal, + read_from_terminal, + start_server, + stop_server, + get_server_status, + get_app_version, + restart_server, + show_server_console, + show_welcome_screen, + purge_all_settings, + update_dock_icon_visibility, + show_main_window, + open_settings_window, + open_session_detail_window, + focus_terminal_window, + quit_app, + settings::get_settings, + settings::save_settings, + auto_launch::set_auto_launch, + auto_launch::get_auto_launch, + ngrok::start_ngrok_tunnel, + ngrok::stop_ngrok_tunnel, + ngrok::get_ngrok_status, + terminal_detector::detect_system_terminals, + terminal_detector::get_default_shell, + cli_installer::install_cli, + cli_installer::uninstall_cli, + cli_installer::check_cli_installed, + start_tty_forward, + stop_tty_forward, + list_tty_forwards, + get_tty_forward, + get_session_stats, + get_monitored_sessions, + start_session_monitoring, + check_port_availability, + detect_port_conflict, + resolve_port_conflict, + force_kill_process, + find_available_ports, + get_local_ip_address, + get_all_ip_addresses, + get_network_interfaces, + get_hostname, + test_network_connectivity, + get_network_stats, + show_notification, + get_notifications, + get_notification_history, + mark_notification_as_read, + mark_all_notifications_as_read, + clear_notification, + clear_all_notifications, + get_unread_notification_count, + update_notification_settings, + get_notification_settings, + get_welcome_state, + should_show_welcome, + get_tutorials, + get_tutorial_category, + complete_tutorial_step, + skip_tutorial, + reset_tutorial, + get_tutorial_progress, + show_welcome_window, + get_all_advanced_settings, + update_advanced_settings, + reset_settings_section, + export_settings, + import_settings, + check_all_permissions, + check_permission, + check_permission_silent, + request_permission, + get_permission_info, + get_all_permissions, + get_required_permissions, + get_missing_required_permissions, + all_required_permissions_granted, + open_system_permission_settings, + get_permission_stats, + check_for_updates, + download_update, + install_update, + cancel_update, + get_update_state, + get_updater_settings, + update_updater_settings, + switch_update_channel, + get_update_history, + get_available_backends, + get_backend_config, + is_backend_installed, + install_backend, + start_backend, + stop_backend, + switch_backend, + get_active_backend, + get_backend_instances, + check_backend_health, + get_backend_stats, + get_debug_settings, + update_debug_settings, + log_debug_message, + record_performance_metric, + take_memory_snapshot, + get_debug_logs, + get_performance_metrics, + get_memory_snapshots, + get_network_requests, + run_api_tests, + run_benchmarks, + generate_diagnostic_report, + clear_debug_data, + set_debug_mode, + get_debug_stats, + get_api_test_config, + update_api_test_config, + add_api_test_suite, + get_api_test_suite, + list_api_test_suites, + run_single_api_test, + run_api_test_suite, + get_api_test_history, + clear_api_test_history, + import_postman_collection, + export_api_test_suite, + get_auth_cache_config, + update_auth_cache_config, + store_auth_token, + get_auth_token, + store_auth_credential, + get_auth_credential, + clear_auth_cache_entry, + clear_all_auth_cache, + get_auth_cache_stats, + list_auth_cache_entries, + export_auth_cache, + import_auth_cache, + hash_password, + create_auth_cache_key, + detect_installed_terminals, + get_default_terminal, + set_default_terminal, + launch_terminal_emulator, + get_terminal_config, + update_terminal_config, + list_detected_terminals, + create_terminal_ssh_url, + get_terminal_integration_stats, + // Settings UI Commands + get_all_settings, + update_setting, + set_dashboard_password, + restart_server_with_port, + update_server_bind_address, + set_dock_icon_visibility, + set_log_level, + test_api_endpoint, + get_server_logs, + export_logs, + get_local_ip, + detect_terminals, + // App Mover Commands + app_mover::prompt_move_to_applications, + app_mover::is_in_applications_folder_command, + // Terminal Spawn Service Commands + terminal_spawn_service::spawn_terminal_for_session, + terminal_spawn_service::spawn_terminal_with_command, + terminal_spawn_service::spawn_custom_terminal, + // Keychain Commands + keychain_set_password, + keychain_get_password, + keychain_delete_password, + keychain_set_dashboard_password, + keychain_get_dashboard_password, + keychain_delete_dashboard_password, + keychain_set_ngrok_auth_token, + keychain_get_ngrok_auth_token, + keychain_delete_ngrok_auth_token, + keychain_list_keys, + // Welcome flow commands + request_all_permissions, + test_terminal, + ]) + .setup(|app| { + // Set app handle in managers + let state_clone = app.state::().inner().clone(); + let app_handle = app.handle().clone(); + let app_handle2 = app.handle().clone(); + let app_handle3 = app.handle().clone(); + let app_handle4 = app.handle().clone(); + let app_handle_for_move = app.handle().clone(); + + tauri::async_runtime::spawn(async move { + let state = state_clone; + state.notification_manager.set_app_handle(app_handle).await; + state.welcome_manager.set_app_handle(app_handle2).await; + state.permissions_manager.set_app_handle(app_handle3).await; + state.update_manager.set_app_handle(app_handle4).await; + + // Start background workers now that we have a runtime + state.terminal_spawn_service.clone().start_worker().await; + state.auth_cache_manager.start_cleanup_task().await; + + // Start Unix socket server for terminal spawning (macOS/Linux) + #[cfg(unix)] + { + if let Err(e) = state.unix_socket_server.start() { + tracing::error!("Failed to start Unix socket server: {}", e); + } + } + + // Start session monitoring + state.session_monitor.start_monitoring().await; + + // Load welcome state and check if should show welcome + let _ = state.welcome_manager.load_state().await; + if state.welcome_manager.should_show_welcome().await { + let _ = state.welcome_manager.show_welcome_window().await; + } + + // Check permissions on startup + let _ = state.permissions_manager.check_all_permissions().await; + + // Check if app should be moved to Applications folder (macOS only) + #[cfg(target_os = "macos")] + { + let app_handle_move = app_handle_for_move.clone(); + tokio::spawn(async move { + // Small delay to let the app fully initialize + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + let _ = app_mover::check_and_prompt_move(app_handle_move).await; + }); + } + + // Load updater settings and start auto-check + let _ = state.update_manager.load_settings().await; + state.update_manager.clone().start_auto_check().await; + }); + + // Create system tray icon using tray-icon.png for macOS (menu-bar-icon.png is for Windows/Linux) + let tray_icon = if let Ok(resource_dir) = app.path().resource_dir() { + // On macOS, use tray-icon.png which has the proper design for the menu bar + let icon_name = if cfg!(target_os = "macos") { + "tray-icon.png" + } else { + "menu-bar-icon.png" + }; + + let icon_path = resource_dir.join(icon_name); + if let Ok(icon_data) = std::fs::read(&icon_path) { + tauri::image::Image::from_bytes(&icon_data).ok() + } else { + // Try alternative path + let icon_path2 = resource_dir.join("icons").join(icon_name); + if let Ok(icon_data) = std::fs::read(&icon_path2) { + tauri::image::Image::from_bytes(&icon_data).ok() + } else { + // Fallback to default icon + app.default_window_icon().cloned() + } + } + } else { + // Fallback to default icon if resource dir not found + app.default_window_icon().cloned() + }; + + if let Some(icon) = tray_icon { + // Create enhanced tray menu + let menu = tray_menu::TrayMenuManager::create_menu(&app.handle())?; + + // Build tray icon with template mode for macOS + let _tray = TrayIconBuilder::with_id("main") + .icon(icon) + .icon_as_template(true) // Enable template mode for proper macOS tinting + .tooltip("VibeTunnel") + .menu(&menu) + .on_menu_event(move |app, event| { + handle_tray_menu_event(app, event.id.as_ref()); + }) + .on_tray_icon_event(|tray, event| { + if let TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } = event + { + // Get server status and open dashboard in browser + let app = tray.app_handle(); + let state = app.state::(); + let server_guard = state.http_server.blocking_read(); + if let Some(server) = server_guard.as_ref() { + let url = format!("http://127.0.0.1:{}", server.port()); + let _ = open::that(url); + } + } + }) + .build(app)?; + } + + // Load settings to determine initial dock icon visibility + let settings = settings::Settings::load().unwrap_or_default(); + + // Set initial dock icon visibility on macOS + #[cfg(target_os = "macos")] + { + if !settings.general.show_dock_icon { + app.set_activation_policy(tauri::ActivationPolicy::Accessory); + } + } + + // Auto-start server with monitoring + let app_handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + tracing::info!("Starting server with monitoring..."); + start_server_with_monitoring(app_handle).await; + }); + + Ok(()) + }) + .on_menu_event(handle_menu_event) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} + +#[cfg(target_os = "macos")] +#[allow(dead_code)] +fn create_app_menu(app: &tauri::App) -> Result, tauri::Error> { + // Create the menu using the builder pattern + let menu = Menu::new(app)?; + + // For now, return a basic menu + // TODO: Once we understand the correct Tauri v2 menu API, implement full menu + Ok(menu) +} + +fn handle_tray_menu_event(app: &AppHandle, event_id: &str) { + match event_id { + "dashboard" => { + // Get server status and open dashboard in browser + let state = app.state::(); + let server_guard = state.http_server.blocking_read(); + if let Some(server) = server_guard.as_ref() { + let url = format!("http://127.0.0.1:{}", server.port()); + let _ = open::that(url); + } + } + "show_tutorial" => { + // Show welcome window instead + let state = app.state::(); + let welcome_manager = state.welcome_manager.clone(); + tauri::async_runtime::spawn(async move { + let _ = welcome_manager.show_welcome_window().await; + }); + } + "website" => { + let _ = open::that("https://vibetunnel.sh"); + } + "report_issue" => { + let _ = open::that("https://github.com/amantus-ai/vibetunnel/issues"); + } + "check_updates" => { + // TODO: Implement update check + tracing::info!("Check for updates"); + } + "about" => { + // TODO: Show about dialog + tracing::info!("About VibeTunnel"); + } + "settings" => { + // Open native settings window + let _ = open_settings_window(app.clone(), None); + } + "quit" => { + quit_app(app.clone()); + } + _ => { + // Handle session clicks (format: "session_") + if event_id.starts_with("session_") { + let session_id = event_id.strip_prefix("session_").unwrap_or(""); + if !session_id.is_empty() { + // Open session detail window + let _ = open_session_detail_window(app.clone(), session_id.to_string()); + } + } + } + } +} + +fn handle_menu_event(app: &AppHandle, event: tauri::menu::MenuEvent) { + match event.id.as_ref() { + "settings" => { + // Open native settings window instead of main window + let _ = open_settings_window(app.clone(), None); + } + "new-terminal" => { + // Terminal creation should be done via the web dashboard + // Open dashboard in browser + let state = app.state::(); + let server_guard = state.http_server.blocking_read(); + if let Some(server) = server_guard.as_ref() { + let url = format!("http://127.0.0.1:{}", server.port()); + let _ = open::that(url); + } + } + "reload" => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.eval("window.location.reload()"); + } + } + "show-dashboard" => { + // Open dashboard in browser instead of showing main window + let state = app.state::(); + let server_guard = state.http_server.blocking_read(); + if let Some(server) = server_guard.as_ref() { + let url = format!("http://127.0.0.1:{}", server.port()); + let _ = open::that(url); + } + } + "quit" => { + quit_app(app.clone()); + } + "hide" => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.hide(); + } + } + "minimize" => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.minimize(); + } + } + "zoom" => { + if let Some(window) = app.get_webview_window("main") { + // Toggle maximize state + if window.is_maximized().unwrap_or(false) { + let _ = window.unmaximize(); + } else { + let _ = window.maximize(); + } + } + } + "fullscreen" => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_fullscreen(!window.is_fullscreen().unwrap_or(false)); + } + } + "close-window" => { + if let Some(window) = app.get_webview_window("main") { + let _ = window.close(); + } + } + "cut" | "copy" | "paste" | "select-all" | "undo" | "redo" => { + // These are handled by the system automatically for text fields + // For terminal, we'll emit events + if let Some(window) = app.get_webview_window("main") { + let _ = window.emit(&format!("menu:{}", event.id.as_ref()), ()); + } + } + _ => {} + } +} + +#[tauri::command] +fn show_main_window(app: AppHandle) -> Result<(), String> { + let window = if let Some(window) = app.get_webview_window("main") { + window + } else { + // Create main window if it doesn't exist + tauri::WebviewWindowBuilder::new(&app, "main", tauri::WebviewUrl::App("index.html".into())) + .title("VibeTunnel") + .inner_size(1200.0, 800.0) + .center() + .resizable(true) + .decorations(true) + .build() + .map_err(|e| e.to_string())? + }; + + window.show().map_err(|e| e.to_string())?; + window.set_focus().map_err(|e| e.to_string())?; + + // Show dock icon on macOS when window is shown + #[cfg(target_os = "macos")] + { + let _ = app.set_activation_policy(tauri::ActivationPolicy::Regular); + } + + // Handle window close event to hide instead of quit + let window_clone = window.clone(); + let app_clone = app.clone(); + window.on_window_event(move |event| { + if let WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + let _ = window_clone.hide(); + + // Hide dock icon on macOS when window is hidden (only if settings say so) + #[cfg(target_os = "macos")] + { + if let Ok(settings) = settings::Settings::load() { + if !settings.general.show_dock_icon { + let _ = app_clone.set_activation_policy(tauri::ActivationPolicy::Accessory); + } + } + } + } + }); + + Ok(()) +} + +#[tauri::command] +fn quit_app(app: AppHandle) { + // Stop monitoring before exit + let state = app.state::(); + state + .server_monitoring + .store(false, std::sync::atomic::Ordering::Relaxed); + + // Close all terminal sessions + let terminal_manager = state.terminal_manager.clone(); + tauri::async_runtime::block_on(async move { + let _ = terminal_manager.close_all_sessions().await; + }); + + app.exit(0); +} + +async fn start_server_with_monitoring(app_handle: AppHandle) { + let state = app_handle.state::(); + let state_clone = state.inner().clone(); + + // Start initial server + match start_server_internal(&*state).await { + Ok(status) => { + tracing::info!("Server started on port {}", status.port); + *state.server_target_port.write().await = Some(status.port); + + // Update tray menu with server status + update_tray_menu_status(&app_handle, status.port, 0); + + // Show notification + let _ = state + .notification_manager + .notify_server_status(true, status.port) + .await; + } + Err(e) => { + tracing::error!("Failed to start server: {}", e); + let _ = state + .notification_manager + .notify_error( + "Server Start Failed", + &format!("Failed to start server: {}", e), + ) + .await; + } + } + + // Monitor server health + let monitoring_state = state_clone.clone(); + let monitoring_app = app_handle.clone(); + + tauri::async_runtime::spawn(async move { + let mut check_interval = tokio::time::interval(tokio::time::Duration::from_secs(5)); + + while monitoring_state + .server_monitoring + .load(std::sync::atomic::Ordering::Relaxed) + { + check_interval.tick().await; + + // Check if server is still running + let server_running = { + let server = monitoring_state.http_server.read().await; + server.is_some() + }; + + if server_running { + // Perform health check + let health_check_result = perform_server_health_check(&monitoring_state).await; + + if !health_check_result { + tracing::warn!("Server health check failed, attempting restart..."); + + // Stop current server + let _ = stop_server_internal(&monitoring_state).await; + + // Wait a bit before restart + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + + // Restart server + match start_server_internal(&monitoring_state).await { + Ok(status) => { + tracing::info!("Server restarted on port {}", status.port); + *monitoring_state.server_target_port.write().await = Some(status.port); + + // Update tray menu with server status + update_tray_menu_status(&monitoring_app, status.port, 0); + + // Notify frontend of server restart + if let Some(window) = monitoring_app.get_webview_window("main") { + let _ = window.emit("server:restarted", &status); + } + + // Show notification + let _ = monitoring_state + .notification_manager + .notify_server_status(true, status.port) + .await; + } + Err(e) => { + tracing::error!("Failed to restart server: {}", e); + let _ = monitoring_state + .notification_manager + .notify_error( + "Server Restart Failed", + &format!("Failed to restart server: {}", e), + ) + .await; + } + } + } + } else { + // Server is not running, attempt to start it + let target_port = *monitoring_state.server_target_port.read().await; + if target_port.is_some() { + tracing::info!("Server not running, attempting to start..."); + + match start_server_internal(&monitoring_state).await { + Ok(status) => { + tracing::info!("Server started on port {}", status.port); + + // Notify frontend of server restart + if let Some(window) = monitoring_app.get_webview_window("main") { + let _ = window.emit("server:restarted", &status); + } + + // Show notification + let _ = monitoring_state + .notification_manager + .notify_server_status(true, status.port) + .await; + } + Err(e) => { + tracing::error!("Failed to start server: {}", e); + let _ = monitoring_state + .notification_manager + .notify_error( + "Server Start Failed", + &format!("Failed to start server: {}", e), + ) + .await; + } + } + } + } + } + + tracing::info!("Server monitoring stopped"); + }); +} + +async fn perform_server_health_check(state: &AppState) -> bool { + // Try to get server status + match get_server_status_internal(state).await { + Ok(status) if status.running => { + // Server reports as running, perform additional check + // by trying to access the API endpoint + let url = format!("http://127.0.0.1:{}/api/sessions", status.port); + + match reqwest::Client::new() + .get(&url) + .timeout(std::time::Duration::from_secs(2)) + .send() + .await + { + Ok(response) => response.status().is_success(), + Err(_) => false, + } + } + _ => false, + } +} + +// Internal server management functions that work directly with AppState +async fn start_server_internal(state: &AppState) -> Result { + let mut server = state.http_server.write().await; + + if let Some(http_server) = server.as_ref() { + // Get actual port from running server + let port = http_server.port(); + + // Check if ngrok is active + let url = if let Some(ngrok_tunnel) = state.ngrok_manager.get_tunnel_status() { + ngrok_tunnel.url + } else { + format!("http://127.0.0.1:{}", port) + }; + + return Ok(ServerStatus { + running: true, + port, + url, + }); + } + + // Load settings to check if password is enabled + let settings = crate::settings::Settings::load().unwrap_or_default(); + + // Start HTTP server with auth if configured + let mut http_server = + if settings.dashboard.enable_password && !settings.dashboard.password.is_empty() { + let auth_config = crate::auth::AuthConfig::new(true, Some(settings.dashboard.password)); + HttpServer::with_auth( + state.terminal_manager.clone(), + state.session_monitor.clone(), + auth_config, + ) + } else { + HttpServer::new( + state.terminal_manager.clone(), + state.session_monitor.clone(), + ) + }; + + // Start server with appropriate access mode + let (port, url) = match settings.dashboard.access_mode.as_str() { + "network" => { + let port = http_server.start_with_mode("network").await?; + (port, format!("http://0.0.0.0:{}", port)) + } + "ngrok" => { + // For ngrok mode, start in localhost and let ngrok handle the tunneling + let port = http_server.start_with_mode("localhost").await?; + + // Try to start ngrok tunnel if auth token is configured + let url = if let Some(auth_token) = settings.advanced.ngrok_auth_token { + if !auth_token.is_empty() { + match state + .ngrok_manager + .start_tunnel(port, Some(auth_token)) + .await + { + Ok(tunnel) => tunnel.url, + Err(e) => { + tracing::error!("Failed to start ngrok tunnel: {}", e); + return Err(format!("Failed to start ngrok tunnel: {}", e)); + } + } + } else { + return Err("Ngrok auth token is required for ngrok access mode".to_string()); + } + } else { + return Err("Ngrok auth token is required for ngrok access mode".to_string()); + }; + + (port, url) + } + _ => { + let port = http_server.start_with_mode("localhost").await?; + (port, format!("http://127.0.0.1:{}", port)) + } + }; + + *server = Some(http_server); + + Ok(ServerStatus { + running: true, + port, + url, + }) +} + +async fn stop_server_internal(state: &AppState) -> Result<(), String> { + let mut server = state.http_server.write().await; + + if let Some(mut http_server) = server.take() { + http_server.stop().await?; + } + + // Also stop ngrok tunnel if active + let _ = state.ngrok_manager.stop_tunnel().await; + + Ok(()) +} + +async fn get_server_status_internal(state: &AppState) -> Result { + let server = state.http_server.read().await; + + if let Some(http_server) = server.as_ref() { + let port = http_server.port(); + + // Check if ngrok is active and return its URL + let url = if let Some(ngrok_tunnel) = state.ngrok_manager.get_tunnel_status() { + ngrok_tunnel.url + } else { + // Check settings to determine the correct URL format + let settings = crate::settings::Settings::load().unwrap_or_default(); + match settings.dashboard.access_mode.as_str() { + "network" => format!("http://0.0.0.0:{}", port), + _ => format!("http://127.0.0.1:{}", port), + } + }; + + Ok(ServerStatus { + running: true, + port, + url, + }) + } else { + Ok(ServerStatus { + running: false, + port: 0, + url: String::new(), + }) + } +} diff --git a/tauri/src-tauri/src/network_utils.rs b/tauri/src-tauri/src/network_utils.rs new file mode 100644 index 00000000..4a496f2a --- /dev/null +++ b/tauri/src-tauri/src/network_utils.rs @@ -0,0 +1,297 @@ +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use tracing::error; + +/// Network interface information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetworkInterface { + pub name: String, + pub addresses: Vec, + pub is_up: bool, + pub is_loopback: bool, +} + +/// IP address with type information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IpAddress { + pub address: String, + pub is_ipv4: bool, + pub is_ipv6: bool, + pub is_private: bool, +} + +/// Network utilities +pub struct NetworkUtils; + +impl NetworkUtils { + /// Get the primary local IP address + pub fn get_local_ip_address() -> Option { + // Try to get network interfaces + let interfaces = Self::get_all_interfaces(); + + // First, try to find a private network address (192.168.x.x, 10.x.x.x, 172.16-31.x.x) + for interface in &interfaces { + if interface.is_loopback || !interface.is_up { + continue; + } + + for addr in &interface.addresses { + if addr.is_ipv4 && addr.is_private { + return Some(addr.address.clone()); + } + } + } + + // If no private address found, return any non-loopback IPv4 + for interface in &interfaces { + if interface.is_loopback || !interface.is_up { + continue; + } + + for addr in &interface.addresses { + if addr.is_ipv4 { + return Some(addr.address.clone()); + } + } + } + + None + } + + /// Get all IP addresses + pub fn get_all_ip_addresses() -> Vec { + let interfaces = Self::get_all_interfaces(); + let mut addresses = Vec::new(); + + for interface in interfaces { + if interface.is_loopback { + continue; + } + + for addr in interface.addresses { + addresses.push(addr.address); + } + } + + addresses + } + + /// Get all network interfaces + pub fn get_all_interfaces() -> Vec { + #[cfg(unix)] + { + Self::get_interfaces_unix() + } + + #[cfg(windows)] + { + Self::get_interfaces_windows() + } + + #[cfg(not(any(unix, windows)))] + { + Vec::new() + } + } + + #[cfg(unix)] + fn get_interfaces_unix() -> Vec { + use nix::ifaddrs::getifaddrs; + + let mut interfaces = std::collections::HashMap::new(); + + match getifaddrs() { + Ok(addrs) => { + for ifaddr in addrs { + let name = ifaddr.interface_name.clone(); + let flags = ifaddr.flags; + + let interface = + interfaces + .entry(name.clone()) + .or_insert_with(|| NetworkInterface { + name, + addresses: Vec::new(), + is_up: flags.contains(nix::net::if_::InterfaceFlags::IFF_UP), + is_loopback: flags + .contains(nix::net::if_::InterfaceFlags::IFF_LOOPBACK), + }); + + if let Some(address) = ifaddr.address { + if let Some(sockaddr) = address.as_sockaddr_in() { + let ip = IpAddr::V4(Ipv4Addr::from(sockaddr.ip())); + interface.addresses.push(IpAddress { + address: ip.to_string(), + is_ipv4: true, + is_ipv6: false, + is_private: Self::is_private_ip(&ip), + }); + } else if let Some(sockaddr) = address.as_sockaddr_in6() { + let ip = IpAddr::V6(sockaddr.ip()); + interface.addresses.push(IpAddress { + address: ip.to_string(), + is_ipv4: false, + is_ipv6: true, + is_private: Self::is_private_ip(&ip), + }); + } + } + } + } + Err(e) => { + error!("Failed to get network interfaces: {}", e); + } + } + + interfaces.into_values().collect() + } + + #[cfg(windows)] + fn get_interfaces_windows() -> Vec { + use ipconfig::get_adapters; + + let mut interfaces = Vec::new(); + + match get_adapters() { + Ok(adapters) => { + for adapter in adapters { + let mut addresses = Vec::new(); + + // Get IPv4 addresses + for addr in adapter.ipv4_addresses() { + addresses.push(IpAddress { + address: addr.to_string(), + is_ipv4: true, + is_ipv6: false, + is_private: Self::is_private_ipv4(addr), + }); + } + + // Get IPv6 addresses + for addr in adapter.ipv6_addresses() { + addresses.push(IpAddress { + address: addr.to_string(), + is_ipv4: false, + is_ipv6: true, + is_private: Self::is_private_ipv6(addr), + }); + } + + interfaces.push(NetworkInterface { + name: adapter.friendly_name().to_string(), + addresses, + is_up: adapter.oper_status() == ipconfig::OperStatus::IfOperStatusUp, + is_loopback: adapter.if_type() == ipconfig::IfType::SoftwareLoopback, + }); + } + } + Err(e) => { + error!("Failed to get network interfaces: {}", e); + } + } + + interfaces + } + + /// Check if an IP address is private + fn is_private_ip(ip: &IpAddr) -> bool { + match ip { + IpAddr::V4(ipv4) => Self::is_private_ipv4(ipv4), + IpAddr::V6(ipv6) => Self::is_private_ipv6(ipv6), + } + } + + /// Check if an IPv4 address is private + fn is_private_ipv4(ip: &Ipv4Addr) -> bool { + let octets = ip.octets(); + + // 10.0.0.0/8 + if octets[0] == 10 { + return true; + } + + // 172.16.0.0/12 + if octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31) { + return true; + } + + // 192.168.0.0/16 + if octets[0] == 192 && octets[1] == 168 { + return true; + } + + false + } + + /// Check if an IPv6 address is private + fn is_private_ipv6(ip: &Ipv6Addr) -> bool { + // Check for link-local addresses (fe80::/10) + let segments = ip.segments(); + if segments[0] & 0xffc0 == 0xfe80 { + return true; + } + + // Check for unique local addresses (fc00::/7) + if segments[0] & 0xfe00 == 0xfc00 { + return true; + } + + false + } + + /// Get hostname + pub fn get_hostname() -> Option { + hostname::get() + .ok() + .and_then(|name| name.into_string().ok()) + } + + /// Test network connectivity to a host + pub async fn test_connectivity(host: &str, port: u16) -> bool { + use std::time::Duration; + use tokio::net::TcpStream; + use tokio::time::timeout; + + let addr = format!("{}:{}", host, port); + match timeout(Duration::from_secs(3), TcpStream::connect(&addr)).await { + Ok(Ok(_)) => true, + _ => false, + } + } + + /// Get network statistics + pub fn get_network_stats() -> NetworkStats { + NetworkStats { + hostname: Self::get_hostname(), + primary_ip: Self::get_local_ip_address(), + all_ips: Self::get_all_ip_addresses(), + interface_count: Self::get_all_interfaces().len(), + } + } +} + +/// Network statistics +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetworkStats { + pub hostname: Option, + pub primary_ip: Option, + pub all_ips: Vec, + pub interface_count: usize, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_private_ipv4() { + assert!(NetworkUtils::is_private_ipv4(&"10.0.0.1".parse().unwrap())); + assert!(NetworkUtils::is_private_ipv4( + &"172.16.0.1".parse().unwrap() + )); + assert!(NetworkUtils::is_private_ipv4( + &"192.168.1.1".parse().unwrap() + )); + assert!(!NetworkUtils::is_private_ipv4(&"8.8.8.8".parse().unwrap())); + } +} diff --git a/tauri/src-tauri/src/ngrok.rs b/tauri/src-tauri/src/ngrok.rs new file mode 100644 index 00000000..03f34974 --- /dev/null +++ b/tauri/src-tauri/src/ngrok.rs @@ -0,0 +1,141 @@ +use crate::state::AppState; +use serde::{Deserialize, Serialize}; +use std::process::{Child, Command}; +use std::sync::{Arc, Mutex}; +use tauri::State; +use tracing::info; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NgrokTunnel { + pub url: String, + pub port: u16, + pub status: String, +} + +pub struct NgrokManager { + process: Arc>>, + tunnel_info: Arc>>, +} + +impl NgrokManager { + pub fn new() -> Self { + Self { + process: Arc::new(Mutex::new(None)), + tunnel_info: Arc::new(Mutex::new(None)), + } + } + + pub async fn start_tunnel( + &self, + port: u16, + auth_token: Option, + ) -> Result { + // Check if ngrok is installed + let ngrok_path = which::which("ngrok") + .map_err(|_| "ngrok not found. Please install ngrok first.".to_string())?; + + // Set auth token if provided + if let Some(token) = auth_token { + Command::new(&ngrok_path) + .args(&["config", "add-authtoken", &token]) + .output() + .map_err(|e| format!("Failed to set ngrok auth token: {}", e))?; + } + + // Start ngrok tunnel + let child = Command::new(&ngrok_path) + .args(&["http", &port.to_string(), "--log=stdout"]) + .spawn() + .map_err(|e| format!("Failed to start ngrok: {}", e))?; + + // Wait a bit for ngrok to start + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + + // Get tunnel information via ngrok API + let tunnel_info = self.get_tunnel_info().await?; + + // Store process and tunnel info + *self.process.lock().unwrap() = Some(child); + *self.tunnel_info.lock().unwrap() = Some(tunnel_info.clone()); + + info!("ngrok tunnel started: {}", tunnel_info.url); + + Ok(tunnel_info) + } + + pub async fn stop_tunnel(&self) -> Result<(), String> { + if let Some(mut child) = self.process.lock().unwrap().take() { + child + .kill() + .map_err(|e| format!("Failed to stop ngrok: {}", e))?; + + info!("ngrok tunnel stopped"); + } + + *self.tunnel_info.lock().unwrap() = None; + + Ok(()) + } + + pub fn get_tunnel_status(&self) -> Option { + self.tunnel_info.lock().unwrap().clone() + } + + async fn get_tunnel_info(&self) -> Result { + // Query ngrok local API + let response = reqwest::get("http://localhost:4040/api/tunnels") + .await + .map_err(|e| format!("Failed to query ngrok API: {}", e))?; + + let data: serde_json::Value = response + .json() + .await + .map_err(|e| format!("Failed to parse ngrok API response: {}", e))?; + + // Extract tunnel URL + let tunnels = data["tunnels"] + .as_array() + .ok_or_else(|| "No tunnels found".to_string())?; + + let tunnel = tunnels + .iter() + .find(|t| t["proto"].as_str() == Some("https")) + .or_else(|| tunnels.first()) + .ok_or_else(|| "No tunnel found".to_string())?; + + let url = tunnel["public_url"] + .as_str() + .ok_or_else(|| "No public URL found".to_string())?; + + let port = tunnel["config"]["addr"] + .as_str() + .and_then(|addr| addr.split(':').last()) + .and_then(|p| p.parse::().ok()) + .unwrap_or(3000); + + Ok(NgrokTunnel { + url: url.to_string(), + port, + status: "active".to_string(), + }) + } +} + +#[tauri::command] +pub async fn start_ngrok_tunnel( + port: u16, + auth_token: Option, + state: State<'_, AppState>, +) -> Result { + state.ngrok_manager.start_tunnel(port, auth_token).await +} + +#[tauri::command] +pub async fn stop_ngrok_tunnel(state: State<'_, AppState>) -> Result<(), String> { + state.ngrok_manager.stop_tunnel().await +} + +#[tauri::command] +pub async fn get_ngrok_status(state: State<'_, AppState>) -> Result, String> { + Ok(state.ngrok_manager.get_tunnel_status()) +} diff --git a/tauri/src-tauri/src/notification_manager.rs b/tauri/src-tauri/src/notification_manager.rs new file mode 100644 index 00000000..45162de0 --- /dev/null +++ b/tauri/src-tauri/src/notification_manager.rs @@ -0,0 +1,412 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use tauri::{AppHandle, Emitter}; +use tauri_plugin_notification::NotificationExt; +use tokio::sync::RwLock; + +/// Notification type enumeration +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum NotificationType { + Info, + Success, + Warning, + Error, + ServerStatus, + UpdateAvailable, + PermissionRequired, + SessionEvent, +} + +/// Notification priority levels +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum NotificationPriority { + Low, + Normal, + High, + Critical, +} + +/// Notification structure +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Notification { + pub id: String, + pub notification_type: NotificationType, + pub priority: NotificationPriority, + pub title: String, + pub body: String, + pub timestamp: DateTime, + pub read: bool, + pub actions: Vec, + pub metadata: HashMap, +} + +/// Notification action +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NotificationAction { + pub id: String, + pub label: String, + pub action_type: String, +} + +/// Notification settings +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NotificationSettings { + pub enabled: bool, + pub show_in_system: bool, + pub play_sound: bool, + pub enabled_types: HashMap, +} + +impl Default for NotificationSettings { + fn default() -> Self { + let mut enabled_types = HashMap::new(); + enabled_types.insert(NotificationType::Info, true); + enabled_types.insert(NotificationType::Success, true); + enabled_types.insert(NotificationType::Warning, true); + enabled_types.insert(NotificationType::Error, true); + enabled_types.insert(NotificationType::ServerStatus, true); + enabled_types.insert(NotificationType::UpdateAvailable, true); + enabled_types.insert(NotificationType::PermissionRequired, true); + enabled_types.insert(NotificationType::SessionEvent, false); + + Self { + enabled: true, + show_in_system: true, + play_sound: true, + enabled_types, + } + } +} + +/// Notification manager +pub struct NotificationManager { + app_handle: Arc>>, + notifications: Arc>>, + settings: Arc>, + notification_history: Arc>>, + max_history_size: usize, +} + +impl NotificationManager { + /// Create a new notification manager + pub fn new() -> Self { + Self { + app_handle: Arc::new(RwLock::new(None)), + notifications: Arc::new(RwLock::new(HashMap::new())), + settings: Arc::new(RwLock::new(NotificationSettings::default())), + notification_history: Arc::new(RwLock::new(Vec::new())), + max_history_size: 100, + } + } + + /// Set the app handle + pub async fn set_app_handle(&self, app_handle: AppHandle) { + *self.app_handle.write().await = Some(app_handle); + } + + /// Update notification settings + pub async fn update_settings(&self, settings: NotificationSettings) { + *self.settings.write().await = settings; + } + + /// Get notification settings + pub async fn get_settings(&self) -> NotificationSettings { + self.settings.read().await.clone() + } + + /// Show a notification + pub async fn show_notification( + &self, + notification_type: NotificationType, + priority: NotificationPriority, + title: String, + body: String, + actions: Vec, + metadata: HashMap, + ) -> Result { + let settings = self.settings.read().await; + + // Check if notifications are enabled + if !settings.enabled { + return Ok("notifications_disabled".to_string()); + } + + // Check if this notification type is enabled + if let Some(&enabled) = settings.enabled_types.get(¬ification_type) { + if !enabled { + return Ok("notification_type_disabled".to_string()); + } + } + + let notification_id = uuid::Uuid::new_v4().to_string(); + let notification = Notification { + id: notification_id.clone(), + notification_type, + priority, + title: title.clone(), + body: body.clone(), + timestamp: Utc::now(), + read: false, + actions, + metadata, + }; + + // Store notification + self.notifications + .write() + .await + .insert(notification_id.clone(), notification.clone()); + + // Add to history + let mut history = self.notification_history.write().await; + history.push(notification.clone()); + + // Trim history if it exceeds max size + if history.len() > self.max_history_size { + let drain_count = history.len() - self.max_history_size; + history.drain(0..drain_count); + } + + // Show system notification if enabled + if settings.show_in_system { + match self + .show_system_notification(&title, &body, notification_type) + .await + { + Ok(_) => {} + Err(e) => { + tracing::error!("Failed to show system notification: {}", e); + } + } + } + + // Emit notification event to frontend + if let Some(app_handle) = self.app_handle.read().await.as_ref() { + app_handle + .emit("notification:new", ¬ification) + .map_err(|e| format!("Failed to emit notification event: {}", e))?; + } + + Ok(notification_id) + } + + /// Show a system notification using Tauri's notification plugin + async fn show_system_notification( + &self, + title: &str, + body: &str, + notification_type: NotificationType, + ) -> Result<(), String> { + let app_handle_guard = self.app_handle.read().await; + let app_handle = app_handle_guard + .as_ref() + .ok_or_else(|| "App handle not set".to_string())?; + + let mut builder = app_handle.notification().builder().title(title).body(body); + + // Set icon based on notification type + let icon = match notification_type { + NotificationType::Success => Some("✅"), + NotificationType::Warning => Some("⚠️"), + NotificationType::Error => Some("❌"), + NotificationType::UpdateAvailable => Some("🔄"), + NotificationType::PermissionRequired => Some("🔐"), + NotificationType::ServerStatus => Some("🖥️"), + NotificationType::SessionEvent => Some("💻"), + NotificationType::Info => Some("ℹ️"), + }; + + if let Some(icon_str) = icon { + builder = builder.icon(icon_str); + } + + builder + .show() + .map_err(|e| format!("Failed to show notification: {}", e))?; + + Ok(()) + } + + /// Mark notification as read + pub async fn mark_as_read(&self, notification_id: &str) -> Result<(), String> { + let mut notifications = self.notifications.write().await; + if let Some(notification) = notifications.get_mut(notification_id) { + notification.read = true; + + // Update history + let mut history = self.notification_history.write().await; + if let Some(hist_notification) = history.iter_mut().find(|n| n.id == notification_id) { + hist_notification.read = true; + } + + Ok(()) + } else { + Err("Notification not found".to_string()) + } + } + + /// Mark all notifications as read + pub async fn mark_all_as_read(&self) -> Result<(), String> { + let mut notifications = self.notifications.write().await; + for notification in notifications.values_mut() { + notification.read = true; + } + + let mut history = self.notification_history.write().await; + for notification in history.iter_mut() { + notification.read = true; + } + + Ok(()) + } + + /// Get all notifications + pub async fn get_notifications(&self) -> Vec { + self.notifications.read().await.values().cloned().collect() + } + + /// Get unread notification count + pub async fn get_unread_count(&self) -> usize { + self.notifications + .read() + .await + .values() + .filter(|n| !n.read) + .count() + } + + /// Get notification history + pub async fn get_history(&self, limit: Option) -> Vec { + let history = self.notification_history.read().await; + match limit { + Some(l) => history.iter().rev().take(l).cloned().collect(), + None => history.clone(), + } + } + + /// Clear notification + pub async fn clear_notification(&self, notification_id: &str) -> Result<(), String> { + self.notifications.write().await.remove(notification_id); + Ok(()) + } + + /// Clear all notifications + pub async fn clear_all_notifications(&self) -> Result<(), String> { + self.notifications.write().await.clear(); + Ok(()) + } + + /// Show server status notification + pub async fn notify_server_status(&self, running: bool, port: u16) -> Result { + let (title, body) = if running { + ( + "Server Started".to_string(), + format!("VibeTunnel server is now running on port {}", port), + ) + } else { + ( + "Server Stopped".to_string(), + "VibeTunnel server has been stopped".to_string(), + ) + }; + + self.show_notification( + NotificationType::ServerStatus, + NotificationPriority::Normal, + title, + body, + vec![], + HashMap::new(), + ) + .await + } + + /// Show update available notification + pub async fn notify_update_available( + &self, + version: &str, + download_url: &str, + ) -> Result { + let mut metadata = HashMap::new(); + metadata.insert( + "version".to_string(), + serde_json::Value::String(version.to_string()), + ); + metadata.insert( + "download_url".to_string(), + serde_json::Value::String(download_url.to_string()), + ); + + self.show_notification( + NotificationType::UpdateAvailable, + NotificationPriority::High, + "Update Available".to_string(), + format!( + "VibeTunnel {} is now available. Click to download.", + version + ), + vec![NotificationAction { + id: "download".to_string(), + label: "Download".to_string(), + action_type: "open_url".to_string(), + }], + metadata, + ) + .await + } + + /// Show permission required notification + pub async fn notify_permission_required( + &self, + permission: &str, + reason: &str, + ) -> Result { + let mut metadata = HashMap::new(); + metadata.insert( + "permission".to_string(), + serde_json::Value::String(permission.to_string()), + ); + + self.show_notification( + NotificationType::PermissionRequired, + NotificationPriority::High, + "Permission Required".to_string(), + format!("{} permission is required: {}", permission, reason), + vec![NotificationAction { + id: "grant".to_string(), + label: "Grant Permission".to_string(), + action_type: "request_permission".to_string(), + }], + metadata, + ) + .await + } + + /// Show error notification + pub async fn notify_error(&self, title: &str, error_message: &str) -> Result { + self.show_notification( + NotificationType::Error, + NotificationPriority::High, + title.to_string(), + error_message.to_string(), + vec![], + HashMap::new(), + ) + .await + } + + /// Show success notification + pub async fn notify_success(&self, title: &str, message: &str) -> Result { + self.show_notification( + NotificationType::Success, + NotificationPriority::Normal, + title.to_string(), + message.to_string(), + vec![], + HashMap::new(), + ) + .await + } +} diff --git a/tauri/src-tauri/src/permissions.rs b/tauri/src-tauri/src/permissions.rs new file mode 100644 index 00000000..7f4c3b62 --- /dev/null +++ b/tauri/src-tauri/src/permissions.rs @@ -0,0 +1,587 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use tauri::AppHandle; +use tokio::sync::RwLock; + +/// Permission type enumeration +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum PermissionType { + ScreenRecording, + Accessibility, + NetworkAccess, + FileSystemFull, + FileSystemRestricted, + TerminalAccess, + NotificationAccess, + CameraAccess, + MicrophoneAccess, + AutoStart, +} + +/// Permission status +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum PermissionStatus { + Granted, + Denied, + NotDetermined, + Restricted, + NotApplicable, +} + +/// Permission information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PermissionInfo { + pub permission_type: PermissionType, + pub status: PermissionStatus, + pub required: bool, + pub platform_specific: bool, + pub description: String, + pub last_checked: Option>, + pub request_count: u32, +} + +/// Permission request result +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PermissionRequestResult { + pub permission_type: PermissionType, + pub status: PermissionStatus, + pub message: Option, + pub requires_restart: bool, + pub requires_system_settings: bool, +} + +/// Platform-specific permission settings +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PlatformPermissions { + pub macos: HashMap, + pub windows: HashMap, + pub linux: HashMap, +} + +/// Permissions manager +pub struct PermissionsManager { + permissions: Arc>>, + app_handle: Arc>>, + notification_manager: Option>, +} + +impl PermissionsManager { + /// Create a new permissions manager + pub fn new() -> Self { + Self { + permissions: Arc::new(RwLock::new(Self::initialize_permissions())), + app_handle: Arc::new(RwLock::new(None)), + notification_manager: None, + } + } + + /// Set the app handle + pub async fn set_app_handle(&self, app_handle: AppHandle) { + *self.app_handle.write().await = Some(app_handle); + } + + /// Set the notification manager + pub fn set_notification_manager( + &mut self, + notification_manager: Arc, + ) { + self.notification_manager = Some(notification_manager); + } + + /// Initialize default permissions based on platform + fn initialize_permissions() -> HashMap { + let mut permissions = HashMap::new(); + + // Get current platform + let platform = std::env::consts::OS; + + match platform { + "macos" => { + permissions.insert( + PermissionType::ScreenRecording, + PermissionInfo { + permission_type: PermissionType::ScreenRecording, + status: PermissionStatus::NotDetermined, + required: false, + platform_specific: true, + description: "Required for recording terminal sessions with system UI" + .to_string(), + last_checked: None, + request_count: 0, + }, + ); + + permissions.insert( + PermissionType::Accessibility, + PermissionInfo { + permission_type: PermissionType::Accessibility, + status: PermissionStatus::NotDetermined, + required: false, + platform_specific: true, + description: "Required for advanced terminal integration features" + .to_string(), + last_checked: None, + request_count: 0, + }, + ); + + permissions.insert( + PermissionType::NotificationAccess, + PermissionInfo { + permission_type: PermissionType::NotificationAccess, + status: PermissionStatus::NotDetermined, + required: false, + platform_specific: true, + description: "Required to show system notifications".to_string(), + last_checked: None, + request_count: 0, + }, + ); + } + "windows" => { + permissions.insert( + PermissionType::TerminalAccess, + PermissionInfo { + permission_type: PermissionType::TerminalAccess, + status: PermissionStatus::NotDetermined, + required: true, + platform_specific: true, + description: "Required to create and manage terminal sessions".to_string(), + last_checked: None, + request_count: 0, + }, + ); + + permissions.insert( + PermissionType::AutoStart, + PermissionInfo { + permission_type: PermissionType::AutoStart, + status: PermissionStatus::NotDetermined, + required: false, + platform_specific: true, + description: "Required to start VibeTunnel with Windows".to_string(), + last_checked: None, + request_count: 0, + }, + ); + } + "linux" => { + permissions.insert( + PermissionType::FileSystemFull, + PermissionInfo { + permission_type: PermissionType::FileSystemFull, + status: PermissionStatus::Granted, + required: true, + platform_specific: false, + description: "Required for saving recordings and configurations" + .to_string(), + last_checked: None, + request_count: 0, + }, + ); + } + _ => {} + } + + // Add common permissions + permissions.insert( + PermissionType::NetworkAccess, + PermissionInfo { + permission_type: PermissionType::NetworkAccess, + status: PermissionStatus::Granted, + required: true, + platform_specific: false, + description: "Required for web server and remote access features".to_string(), + last_checked: None, + request_count: 0, + }, + ); + + permissions.insert( + PermissionType::FileSystemRestricted, + PermissionInfo { + permission_type: PermissionType::FileSystemRestricted, + status: PermissionStatus::Granted, + required: true, + platform_specific: false, + description: "Required for basic application functionality".to_string(), + last_checked: None, + request_count: 0, + }, + ); + + permissions + } + + /// Check all permissions + pub async fn check_all_permissions(&self) -> Vec { + let mut permissions = self.permissions.write().await; + + for (permission_type, info) in permissions.iter_mut() { + info.status = self.check_permission_internal(*permission_type).await; + info.last_checked = Some(Utc::now()); + } + + permissions.values().cloned().collect() + } + + /// Check specific permission + pub async fn check_permission(&self, permission_type: PermissionType) -> PermissionStatus { + let status = self.check_permission_internal(permission_type).await; + + // Update stored status + if let Some(info) = self.permissions.write().await.get_mut(&permission_type) { + info.status = status; + info.last_checked = Some(Utc::now()); + } + + status + } + + /// Check specific permission silently (without triggering any prompts or notifications) + pub async fn check_permission_silent( + &self, + permission_type: PermissionType, + ) -> PermissionStatus { + // Just check the status without updating or notifying + self.check_permission_internal(permission_type).await + } + + /// Internal permission checking logic + async fn check_permission_internal(&self, permission_type: PermissionType) -> PermissionStatus { + let platform = std::env::consts::OS; + + match (platform, permission_type) { + #[cfg(target_os = "macos")] + ("macos", PermissionType::ScreenRecording) => { + self.check_screen_recording_permission_macos().await + } + #[cfg(target_os = "macos")] + ("macos", PermissionType::Accessibility) => { + self.check_accessibility_permission_macos().await + } + #[cfg(target_os = "macos")] + ("macos", PermissionType::NotificationAccess) => { + self.check_notification_permission_macos().await + } + #[cfg(target_os = "windows")] + ("windows", PermissionType::TerminalAccess) => { + self.check_terminal_permission_windows().await + } + #[cfg(target_os = "windows")] + ("windows", PermissionType::AutoStart) => { + self.check_auto_start_permission_windows().await + } + _ => PermissionStatus::NotApplicable, + } + } + + /// Request permission + pub async fn request_permission( + &self, + permission_type: PermissionType, + ) -> Result { + // Update request count + if let Some(info) = self.permissions.write().await.get_mut(&permission_type) { + info.request_count += 1; + } + + let platform = std::env::consts::OS; + + match (platform, permission_type) { + #[cfg(target_os = "macos")] + ("macos", PermissionType::ScreenRecording) => { + self.request_screen_recording_permission_macos().await + } + #[cfg(target_os = "macos")] + ("macos", PermissionType::Accessibility) => { + self.request_accessibility_permission_macos().await + } + #[cfg(target_os = "macos")] + ("macos", PermissionType::NotificationAccess) => { + self.request_notification_permission_macos().await + } + _ => Ok(PermissionRequestResult { + permission_type, + status: PermissionStatus::NotApplicable, + message: Some("Permission not applicable on this platform".to_string()), + requires_restart: false, + requires_system_settings: false, + }), + } + } + + /// Get permission info + pub async fn get_permission_info( + &self, + permission_type: PermissionType, + ) -> Option { + self.permissions.read().await.get(&permission_type).cloned() + } + + /// Get all permissions + pub async fn get_all_permissions(&self) -> Vec { + self.permissions.read().await.values().cloned().collect() + } + + /// Get required permissions + pub async fn get_required_permissions(&self) -> Vec { + self.permissions + .read() + .await + .values() + .filter(|info| info.required) + .cloned() + .collect() + } + + /// Get missing required permissions + pub async fn get_missing_required_permissions(&self) -> Vec { + self.permissions + .read() + .await + .values() + .filter(|info| info.required && info.status != PermissionStatus::Granted) + .cloned() + .collect() + } + + /// Check if all required permissions are granted + pub async fn all_required_permissions_granted(&self) -> bool { + !self + .permissions + .read() + .await + .values() + .any(|info| info.required && info.status != PermissionStatus::Granted) + } + + /// Open system settings for permission + pub async fn open_system_settings( + &self, + permission_type: PermissionType, + ) -> Result<(), String> { + let platform = std::env::consts::OS; + + match (platform, permission_type) { + #[cfg(target_os = "macos")] + ("macos", PermissionType::ScreenRecording) => { + self.open_screen_recording_settings_macos().await + } + #[cfg(target_os = "macos")] + ("macos", PermissionType::Accessibility) => { + self.open_accessibility_settings_macos().await + } + #[cfg(target_os = "macos")] + ("macos", PermissionType::NotificationAccess) => { + self.open_notification_settings_macos().await + } + #[cfg(target_os = "windows")] + ("windows", PermissionType::AutoStart) => self.open_startup_settings_windows().await, + _ => Err("No system settings available for this permission".to_string()), + } + } + + // Platform-specific implementations + #[cfg(target_os = "macos")] + async fn check_screen_recording_permission_macos(&self) -> PermissionStatus { + // Use CGDisplayStream API to check screen recording permission + use std::process::Command; + + let output = Command::new("osascript") + .arg("-e") + .arg("tell application \"System Events\" to get properties") + .output(); + + match output { + Ok(output) if output.status.success() => PermissionStatus::Granted, + _ => PermissionStatus::NotDetermined, + } + } + + #[cfg(target_os = "macos")] + async fn request_screen_recording_permission_macos( + &self, + ) -> Result { + // Show notification about needing to grant permission + if let Some(notification_manager) = &self.notification_manager { + let _ = notification_manager + .notify_permission_required( + "Screen Recording", + "VibeTunnel needs screen recording permission to capture terminal sessions", + ) + .await; + } + + // Open system preferences + let _ = self.open_screen_recording_settings_macos().await; + + Ok(PermissionRequestResult { + permission_type: PermissionType::ScreenRecording, + status: PermissionStatus::NotDetermined, + message: Some( + "Please grant screen recording permission in System Settings".to_string(), + ), + requires_restart: true, + requires_system_settings: true, + }) + } + + #[cfg(target_os = "macos")] + async fn open_screen_recording_settings_macos(&self) -> Result<(), String> { + use std::process::Command; + + Command::new("open") + .arg("x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture") + .spawn() + .map_err(|e| format!("Failed to open system preferences: {}", e))?; + + Ok(()) + } + + #[cfg(target_os = "macos")] + async fn check_accessibility_permission_macos(&self) -> PermissionStatus { + use std::process::Command; + + let output = Command::new("osascript") + .arg("-e") + .arg("tell application \"System Events\" to get UI elements enabled") + .output(); + + match output { + Ok(output) if output.status.success() => { + let result = String::from_utf8_lossy(&output.stdout); + if result.trim() == "true" { + PermissionStatus::Granted + } else { + PermissionStatus::Denied + } + } + _ => PermissionStatus::NotDetermined, + } + } + + #[cfg(target_os = "macos")] + async fn request_accessibility_permission_macos( + &self, + ) -> Result { + let _ = self.open_accessibility_settings_macos().await; + + Ok(PermissionRequestResult { + permission_type: PermissionType::Accessibility, + status: PermissionStatus::NotDetermined, + message: Some("Please grant accessibility permission in System Settings".to_string()), + requires_restart: false, + requires_system_settings: true, + }) + } + + #[cfg(target_os = "macos")] + async fn open_accessibility_settings_macos(&self) -> Result<(), String> { + use std::process::Command; + + Command::new("open") + .arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") + .spawn() + .map_err(|e| format!("Failed to open system preferences: {}", e))?; + + Ok(()) + } + + #[cfg(target_os = "macos")] + async fn check_notification_permission_macos(&self) -> PermissionStatus { + // For now, assume granted as Tauri handles this + PermissionStatus::Granted + } + + #[cfg(target_os = "macos")] + async fn request_notification_permission_macos( + &self, + ) -> Result { + Ok(PermissionRequestResult { + permission_type: PermissionType::NotificationAccess, + status: PermissionStatus::Granted, + message: Some("Notification permission is handled by the system".to_string()), + requires_restart: false, + requires_system_settings: false, + }) + } + + #[cfg(target_os = "macos")] + async fn open_notification_settings_macos(&self) -> Result<(), String> { + use std::process::Command; + + Command::new("open") + .arg("x-apple.systempreferences:com.apple.preference.notifications") + .spawn() + .map_err(|e| format!("Failed to open system preferences: {}", e))?; + + Ok(()) + } + + #[cfg(target_os = "windows")] + async fn check_terminal_permission_windows(&self) -> PermissionStatus { + // On Windows, terminal access is generally granted + PermissionStatus::Granted + } + + #[cfg(target_os = "windows")] + async fn check_auto_start_permission_windows(&self) -> PermissionStatus { + // Check if auto-start is configured + use crate::auto_launch; + + match auto_launch::get_auto_launch().await { + Ok(enabled) => { + if enabled { + PermissionStatus::Granted + } else { + PermissionStatus::Denied + } + } + Err(_) => PermissionStatus::NotDetermined, + } + } + + #[cfg(target_os = "windows")] + async fn open_startup_settings_windows(&self) -> Result<(), String> { + use std::process::Command; + + Command::new("cmd") + .args(&["/c", "start", "ms-settings:startupapps"]) + .spawn() + .map_err(|e| format!("Failed to open startup settings: {}", e))?; + + Ok(()) + } + + /// Show permission required notification + #[allow(dead_code)] + pub async fn notify_permission_required( + &self, + permission_info: &PermissionInfo, + ) -> Result<(), String> { + if let Some(notification_manager) = &self.notification_manager { + notification_manager + .notify_permission_required( + &format!("{:?}", permission_info.permission_type), + &permission_info.description, + ) + .await?; + } + + Ok(()) + } +} + +/// Permission statistics +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PermissionStats { + pub total_permissions: usize, + pub granted_permissions: usize, + pub denied_permissions: usize, + pub required_permissions: usize, + pub missing_required: usize, + pub platform: String, +} diff --git a/tauri/src-tauri/src/port_conflict.rs b/tauri/src-tauri/src/port_conflict.rs new file mode 100644 index 00000000..2a2e3fcb --- /dev/null +++ b/tauri/src-tauri/src/port_conflict.rs @@ -0,0 +1,480 @@ +use serde::{Deserialize, Serialize}; +use std::net::TcpListener; +use std::process::Command; +use tracing::{error, info}; + +/// Information about a process using a port +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProcessDetails { + pub pid: u32, + pub name: String, + pub path: Option, + pub parent_pid: Option, +} + +impl ProcessDetails { + /// Check if this is a VibeTunnel process + pub fn is_vibetunnel(&self) -> bool { + if let Some(path) = &self.path { + return path.contains("vibetunnel") || path.contains("VibeTunnel"); + } + self.name.contains("vibetunnel") || self.name.contains("VibeTunnel") + } + + /// Check if this is one of our managed servers + pub fn is_managed_server(&self) -> bool { + self.name == "vibetunnel" + || self.name.contains("node") + && self + .path + .as_ref() + .map(|p| p.contains("VibeTunnel")) + .unwrap_or(false) + } +} + +/// Information about a port conflict +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PortConflict { + pub port: u16, + pub process: ProcessDetails, + pub root_process: Option, + pub suggested_action: ConflictAction, + pub alternative_ports: Vec, +} + +/// Suggested action for resolving a port conflict +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ConflictAction { + KillOurInstance { pid: u32, process_name: String }, + SuggestAlternativePort, + ReportExternalApp { name: String }, +} + +/// Port conflict resolver +pub struct PortConflictResolver; + +impl PortConflictResolver { + /// Check if a port is available + pub async fn is_port_available(port: u16) -> bool { + TcpListener::bind(format!("127.0.0.1:{}", port)).is_ok() + } + + /// Detect what process is using a port + pub async fn detect_conflict(port: u16) -> Option { + // First check if port is actually in use + if Self::is_port_available(port).await { + return None; + } + + // Platform-specific conflict detection + #[cfg(target_os = "macos")] + return Self::detect_conflict_macos(port).await; + + #[cfg(target_os = "linux")] + return Self::detect_conflict_linux(port).await; + + #[cfg(target_os = "windows")] + return Self::detect_conflict_windows(port).await; + } + + #[cfg(target_os = "macos")] + async fn detect_conflict_macos(port: u16) -> Option { + // Use lsof to find process using the port + let output = Command::new("/usr/sbin/lsof") + .args(&["-i", &format!(":{}", port), "-n", "-P", "-F"]) + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let process_info = Self::parse_lsof_output(&stdout)?; + + // Get root process + let root_process = Self::find_root_process(&process_info).await; + + // Find alternative ports + let alternatives = Self::find_available_ports(port, 3).await; + + // Determine action + let action = Self::determine_action(&process_info, &root_process); + + Some(PortConflict { + port, + process: process_info, + root_process, + suggested_action: action, + alternative_ports: alternatives, + }) + } + + #[cfg(target_os = "linux")] + async fn detect_conflict_linux(port: u16) -> Option { + // Try lsof first + if let Ok(output) = Command::new("lsof") + .args(&["-i", &format!(":{}", port), "-n", "-P", "-F"]) + .output() + { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + if let Some(process_info) = Self::parse_lsof_output(&stdout) { + let root_process = Self::find_root_process(&process_info).await; + let alternatives = Self::find_available_ports(port, 3).await; + let action = Self::determine_action(&process_info, &root_process); + + return Some(PortConflict { + port, + process: process_info, + root_process, + suggested_action: action, + alternative_ports: alternatives, + }); + } + } + } + + // Fallback to netstat + if let Ok(output) = Command::new("netstat").args(&["-tulpn"]).output() { + let stdout = String::from_utf8_lossy(&output.stdout); + // Parse netstat output (simplified) + for line in stdout.lines() { + if line.contains(&format!(":{}", port)) && line.contains("LISTEN") { + // Extract PID from line (format: "tcp ... LISTEN 1234/process") + if let Some(pid_part) = line.split_whitespace().last() { + if let Some(pid_str) = pid_part.split('/').next() { + if let Ok(pid) = pid_str.parse::() { + let name = + pid_part.split('/').nth(1).unwrap_or("unknown").to_string(); + let process_info = ProcessDetails { + pid, + name, + path: None, + parent_pid: None, + }; + + let alternatives = Self::find_available_ports(port, 3).await; + let action = Self::determine_action(&process_info, &None); + + return Some(PortConflict { + port, + process: process_info, + root_process: None, + suggested_action: action, + alternative_ports: alternatives, + }); + } + } + } + } + } + } + + None + } + + #[cfg(target_os = "windows")] + async fn detect_conflict_windows(port: u16) -> Option { + // Use netstat to find process using the port + let output = Command::new("netstat") + .args(&["-ano", "-p", "tcp"]) + .output() + .ok()?; + + let stdout = String::from_utf8_lossy(&output.stdout); + + // Parse netstat output to find the PID + for line in stdout.lines() { + if line.contains(&format!(":{}", port)) && line.contains("LISTENING") { + // Extract PID from the last column + if let Some(pid_str) = line.split_whitespace().last() { + if let Ok(pid) = pid_str.parse::() { + // Get process name using tasklist + if let Ok(tasklist_output) = Command::new("tasklist") + .args(&["/FI", &format!("PID eq {}", pid), "/FO", "CSV", "/NH"]) + .output() + { + let tasklist_stdout = String::from_utf8_lossy(&tasklist_output.stdout); + if let Some(line) = tasklist_stdout.lines().next() { + let parts: Vec<&str> = line.split(',').collect(); + if parts.len() > 0 { + let name = parts[0].trim_matches('"').to_string(); + let process_info = ProcessDetails { + pid, + name, + path: None, + parent_pid: None, + }; + + let alternatives = Self::find_available_ports(port, 3).await; + let action = Self::determine_action(&process_info, &None); + + return Some(PortConflict { + port, + process: process_info, + root_process: None, + suggested_action: action, + alternative_ports: alternatives, + }); + } + } + } + } + } + } + } + + None + } + + /// Parse lsof output + fn parse_lsof_output(output: &str) -> Option { + let mut pid: Option = None; + let mut name: Option = None; + let mut ppid: Option = None; + + // Parse lsof field output format + for line in output.lines() { + if line.starts_with('p') { + pid = line[1..].parse().ok(); + } else if line.starts_with('c') { + name = Some(line[1..].to_string()); + } else if line.starts_with('R') { + ppid = line[1..].parse().ok(); + } + } + + if let (Some(pid), Some(name)) = (pid, name) { + // Get additional process info + let path = Self::get_process_path(pid); + + Some(ProcessDetails { + pid, + name, + path, + parent_pid: ppid, + }) + } else { + None + } + } + + /// Get process path + fn get_process_path(pid: u32) -> Option { + #[cfg(unix)] + { + if let Ok(output) = Command::new("ps") + .args(&["-p", &pid.to_string(), "-o", "comm="]) + .output() + { + let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !path.is_empty() { + return Some(path); + } + } + } + + None + } + + /// Find root process + async fn find_root_process(process: &ProcessDetails) -> Option { + let mut current = process.clone(); + let mut visited = std::collections::HashSet::new(); + + while let Some(parent_pid) = current.parent_pid { + if parent_pid <= 1 || visited.contains(&parent_pid) { + break; + } + visited.insert(current.pid); + + // Get parent process info + if let Some(parent_info) = Self::get_process_info(parent_pid).await { + // If parent is VibeTunnel, it's our root + if parent_info.is_vibetunnel() { + return Some(parent_info); + } + current = parent_info; + } else { + break; + } + } + + None + } + + /// Get process info by PID + async fn get_process_info(pid: u32) -> Option { + #[cfg(unix)] + { + if let Ok(output) = Command::new("ps") + .args(&["-p", &pid.to_string(), "-o", "pid=,ppid=,comm="]) + .output() + { + let stdout = String::from_utf8_lossy(&output.stdout); + let parts: Vec<&str> = stdout.trim().split_whitespace().collect(); + + if parts.len() >= 3 { + let pid = parts[0].parse().ok()?; + let ppid = parts[1].parse().ok(); + let name = parts[2..].join(" "); + let path = Self::get_process_path(pid); + + return Some(ProcessDetails { + pid, + name, + path, + parent_pid: ppid, + }); + } + } + } + + #[cfg(windows)] + { + // Windows implementation would use WMI or similar + // For now, return None + } + + None + } + + /// Find available ports near a given port + async fn find_available_ports(near_port: u16, count: usize) -> Vec { + let mut available_ports = Vec::new(); + let start = near_port.saturating_sub(10).max(1024); + let end = near_port.saturating_add(100).min(65535); + + for port in start..=end { + if port != near_port && Self::is_port_available(port).await { + available_ports.push(port); + if available_ports.len() >= count { + break; + } + } + } + + available_ports + } + + /// Determine action for conflict resolution + fn determine_action( + process: &ProcessDetails, + root_process: &Option, + ) -> ConflictAction { + // If it's our managed server, kill it + if process.is_managed_server() { + return ConflictAction::KillOurInstance { + pid: process.pid, + process_name: process.name.clone(), + }; + } + + // If root process is VibeTunnel, kill the whole app + if let Some(root) = root_process { + if root.is_vibetunnel() { + return ConflictAction::KillOurInstance { + pid: root.pid, + process_name: root.name.clone(), + }; + } + } + + // If the process itself is VibeTunnel + if process.is_vibetunnel() { + return ConflictAction::KillOurInstance { + pid: process.pid, + process_name: process.name.clone(), + }; + } + + // Otherwise, it's an external app + ConflictAction::ReportExternalApp { + name: process.name.clone(), + } + } + + /// Resolve a port conflict + pub async fn resolve_conflict(conflict: &PortConflict) -> Result<(), String> { + match &conflict.suggested_action { + ConflictAction::KillOurInstance { pid, process_name } => { + info!( + "Killing conflicting process: {} (PID: {})", + process_name, pid + ); + + #[cfg(unix)] + { + let output = Command::new("kill") + .args(&["-9", &pid.to_string()]) + .output() + .map_err(|e| format!("Failed to execute kill command: {}", e))?; + + if !output.status.success() { + return Err(format!("Failed to kill process {}", pid)); + } + } + + #[cfg(windows)] + { + let output = Command::new("taskkill") + .args(&["/F", "/PID", &pid.to_string()]) + .output() + .map_err(|e| format!("Failed to execute taskkill command: {}", e))?; + + if !output.status.success() { + return Err(format!("Failed to kill process {}", pid)); + } + } + + // Wait for port to be released + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + Ok(()) + } + ConflictAction::SuggestAlternativePort | ConflictAction::ReportExternalApp { .. } => { + // These require user action + Err("This conflict requires user action to resolve".to_string()) + } + } + } + + /// Force kill a process + pub async fn force_kill_process(conflict: &PortConflict) -> Result<(), String> { + info!( + "Force killing process: {} (PID: {})", + conflict.process.name, conflict.process.pid + ); + + #[cfg(unix)] + { + let output = Command::new("kill") + .args(&["-9", &conflict.process.pid.to_string()]) + .output() + .map_err(|e| format!("Failed to execute kill command: {}", e))?; + + if !output.status.success() { + error!("Failed to kill process with regular permissions"); + return Err(format!("Failed to kill process {}", conflict.process.pid)); + } + } + + #[cfg(windows)] + { + let output = Command::new("taskkill") + .args(&["/F", "/PID", &conflict.process.pid.to_string()]) + .output() + .map_err(|e| format!("Failed to execute taskkill command: {}", e))?; + + if !output.status.success() { + return Err(format!("Failed to kill process {}", conflict.process.pid)); + } + } + + // Wait for port to be released + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + Ok(()) + } +} diff --git a/tauri/src-tauri/src/session_monitor.rs b/tauri/src-tauri/src/session_monitor.rs new file mode 100644 index 00000000..05927bd2 --- /dev/null +++ b/tauri/src-tauri/src/session_monitor.rs @@ -0,0 +1,275 @@ +use chrono::Utc; +use serde::{Deserialize, Serialize}; +use serde_json; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::{mpsc, RwLock}; +use tokio::time::{interval, Duration}; +use uuid::Uuid; + +/// Information about a terminal session +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SessionInfo { + pub id: String, + pub name: String, + pub pid: u32, + pub rows: u16, + pub cols: u16, + pub created_at: String, + pub last_activity: String, + pub is_active: bool, + pub client_count: usize, +} + +/// Session state change event +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SessionEvent { + SessionCreated { session: SessionInfo }, + SessionUpdated { session: SessionInfo }, + SessionClosed { id: String }, + SessionActivity { id: String, timestamp: String }, +} + +/// Session monitoring service +pub struct SessionMonitor { + sessions: Arc>>, + event_subscribers: Arc>>>, + terminal_manager: Arc, +} + +impl SessionMonitor { + pub fn new(terminal_manager: Arc) -> Self { + Self { + sessions: Arc::new(RwLock::new(HashMap::new())), + event_subscribers: Arc::new(RwLock::new(HashMap::new())), + terminal_manager, + } + } + + /// Start monitoring sessions + pub async fn start_monitoring(&self) { + let sessions = self.sessions.clone(); + let subscribers = self.event_subscribers.clone(); + let terminal_manager = self.terminal_manager.clone(); + + tokio::spawn(async move { + let mut monitor_interval = interval(Duration::from_secs(5)); + + loop { + monitor_interval.tick().await; + + // Get current sessions from terminal manager + let current_sessions = terminal_manager.list_sessions().await; + let mut sessions_map = sessions.write().await; + let mut updated_sessions = HashMap::new(); + + // Check for new or updated sessions + for session in current_sessions { + let session_info = SessionInfo { + id: session.id.clone(), + name: session.name.clone(), + pid: session.pid, + rows: session.rows, + cols: session.cols, + created_at: session.created_at.clone(), + last_activity: Utc::now().to_rfc3339(), + is_active: true, + client_count: 0, // TODO: Track actual client count + }; + + // Check if this is a new session + if !sessions_map.contains_key(&session.id) { + // Broadcast session created event + Self::broadcast_event( + &subscribers, + SessionEvent::SessionCreated { + session: session_info.clone(), + }, + ) + .await; + } else { + // Check if session was updated + if let Some(existing) = sessions_map.get(&session.id) { + if existing.rows != session_info.rows + || existing.cols != session_info.cols + { + // Broadcast session updated event + Self::broadcast_event( + &subscribers, + SessionEvent::SessionUpdated { + session: session_info.clone(), + }, + ) + .await; + } + } + } + + updated_sessions.insert(session.id.clone(), session_info); + } + + // Check for closed sessions + let closed_sessions: Vec = sessions_map + .keys() + .filter(|id| !updated_sessions.contains_key(*id)) + .cloned() + .collect(); + + for session_id in closed_sessions { + // Broadcast session closed event + Self::broadcast_event( + &subscribers, + SessionEvent::SessionClosed { + id: session_id.clone(), + }, + ) + .await; + } + + // Update the sessions map + *sessions_map = updated_sessions; + } + }); + } + + /// Subscribe to session events + #[allow(dead_code)] + pub async fn subscribe(&self) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded_channel(); + let subscriber_id = Uuid::new_v4().to_string(); + + self.event_subscribers + .write() + .await + .insert(subscriber_id, tx); + + rx + } + + /// Unsubscribe from session events + #[allow(dead_code)] + pub async fn unsubscribe(&self, subscriber_id: &str) { + self.event_subscribers.write().await.remove(subscriber_id); + } + + /// Get current session count + #[allow(dead_code)] + pub async fn get_session_count(&self) -> usize { + self.sessions.read().await.len() + } + + /// Get all sessions + pub async fn get_sessions(&self) -> Vec { + self.sessions.read().await.values().cloned().collect() + } + + /// Get a specific session + #[allow(dead_code)] + pub async fn get_session(&self, id: &str) -> Option { + self.sessions.read().await.get(id).cloned() + } + + /// Notify activity for a session + #[allow(dead_code)] + pub async fn notify_activity(&self, session_id: &str) { + if let Some(session) = self.sessions.write().await.get_mut(session_id) { + session.last_activity = Utc::now().to_rfc3339(); + + // Broadcast activity event + Self::broadcast_event( + &self.event_subscribers, + SessionEvent::SessionActivity { + id: session_id.to_string(), + timestamp: session.last_activity.clone(), + }, + ) + .await; + } + } + + /// Broadcast an event to all subscribers + async fn broadcast_event( + subscribers: &Arc>>>, + event: SessionEvent, + ) { + let subscribers_read = subscribers.read().await; + let mut dead_subscribers = Vec::new(); + + for (id, tx) in subscribers_read.iter() { + if tx.send(event.clone()).is_err() { + dead_subscribers.push(id.clone()); + } + } + + // Remove dead subscribers + if !dead_subscribers.is_empty() { + drop(subscribers_read); + let mut subscribers_write = subscribers.write().await; + for id in dead_subscribers { + subscribers_write.remove(&id); + } + } + } + + /// Create an SSE stream for session events + pub fn create_sse_stream( + self: Arc, + ) -> impl futures::Stream> + Send + 'static + { + async_stream::stream! { + // Subscribe to events + let (tx, mut rx) = mpsc::unbounded_channel(); + let subscriber_id = Uuid::new_v4().to_string(); + self.event_subscribers.write().await.insert(subscriber_id.clone(), tx); + + // Send initial sessions + let session_list = self.sessions.read().await.values().cloned().collect::>(); + let initial_event = serde_json::json!({ + "type": "initial", + "sessions": session_list, + "count": session_list.len() + }); + + yield Ok(format!("data: {}\n\n", initial_event)); + + // Send events as they come + while let Some(event) = rx.recv().await { + if let Ok(json) = serde_json::to_string(&event) { + yield Ok(format!("data: {}\n\n", json)); + } + } + + // Clean up subscriber on drop + self.event_subscribers.write().await.remove(&subscriber_id); + } + } +} + +/// Session statistics +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SessionStats { + pub total_sessions: usize, + pub active_sessions: usize, + pub total_clients: usize, + pub uptime_seconds: u64, + pub sessions_created_today: usize, +} + +impl SessionMonitor { + /// Get session statistics + pub async fn get_stats(&self) -> SessionStats { + let sessions = self.sessions.read().await; + let active_sessions = sessions.values().filter(|s| s.is_active).count(); + let total_clients = sessions.values().map(|s| s.client_count).sum(); + + // TODO: Track more detailed statistics + SessionStats { + total_sessions: sessions.len(), + active_sessions, + total_clients, + uptime_seconds: 0, // TODO: Track uptime + sessions_created_today: 0, // TODO: Track daily stats + } + } +} diff --git a/tauri/src-tauri/src/settings.rs b/tauri/src-tauri/src/settings.rs new file mode 100644 index 00000000..aa3e6566 --- /dev/null +++ b/tauri/src-tauri/src/settings.rs @@ -0,0 +1,466 @@ +use crate::state::AppState; +use directories::ProjectDirs; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; +use tauri::{Manager, State}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct GeneralSettings { + pub launch_at_login: bool, + pub show_dock_icon: bool, + pub default_terminal: String, + pub default_shell: String, + pub show_welcome_on_startup: Option, + pub theme: Option, + pub language: Option, + pub check_updates_automatically: Option, + pub prompt_move_to_applications: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DashboardSettings { + pub server_port: u16, + pub enable_password: bool, + pub password: String, + pub access_mode: String, + pub auto_cleanup: bool, + pub session_limit: Option, + pub idle_timeout_minutes: Option, + pub enable_cors: Option, + pub allowed_origins: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AdvancedSettings { + pub debug_mode: bool, + pub log_level: String, + pub session_timeout: u32, + pub ngrok_auth_token: Option, + pub ngrok_region: Option, + pub ngrok_subdomain: Option, + pub enable_telemetry: Option, + pub experimental_features: Option, +} + + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TTYForwardSettings { + pub enabled: bool, + pub default_port: u16, + pub bind_address: String, + pub max_connections: u32, + pub buffer_size: u32, + pub keep_alive: bool, + pub authentication: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MonitoringSettings { + pub enabled: bool, + pub collect_metrics: bool, + pub metric_interval_seconds: u32, + pub max_history_size: u32, + pub alert_on_high_cpu: bool, + pub alert_on_high_memory: bool, + pub cpu_threshold_percent: Option, + pub memory_threshold_percent: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NetworkSettings { + pub preferred_interface: Option, + pub enable_ipv6: bool, + pub dns_servers: Option>, + pub proxy_settings: Option, + pub connection_timeout_seconds: u32, + pub retry_attempts: u32, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ProxySettings { + pub enabled: bool, + pub proxy_type: String, + pub host: String, + pub port: u16, + pub username: Option, + pub password: Option, + pub bypass_list: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PortSettings { + pub auto_resolve_conflicts: bool, + pub preferred_port_range_start: u16, + pub preferred_port_range_end: u16, + pub excluded_ports: Option>, + pub conflict_resolution_strategy: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NotificationSettings { + pub enabled: bool, + pub show_in_system: bool, + pub play_sound: bool, + pub notification_types: HashMap, + pub do_not_disturb_enabled: Option, + pub do_not_disturb_start: Option, + pub do_not_disturb_end: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TerminalIntegrationSettings { + pub enabled_terminals: HashMap, + pub terminal_configs: HashMap, + pub default_terminal_override: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TerminalConfig { + pub path: Option, + pub args: Option>, + pub env: Option>, + pub working_directory: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct UpdateSettings { + pub channel: String, + pub check_frequency: String, + pub auto_download: bool, + pub auto_install: bool, + pub show_release_notes: bool, + pub include_pre_releases: bool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SecuritySettings { + pub enable_encryption: bool, + pub encryption_algorithm: Option, + pub require_authentication: bool, + pub session_token_expiry_hours: Option, + pub allowed_ip_addresses: Option>, + pub blocked_ip_addresses: Option>, + pub rate_limiting_enabled: bool, + pub rate_limit_requests_per_minute: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct DebugSettings { + pub enable_debug_menu: bool, + pub show_performance_stats: bool, + pub enable_verbose_logging: bool, + pub log_to_file: bool, + pub log_file_path: Option, + pub max_log_file_size_mb: Option, + pub enable_dev_tools: bool, + pub show_internal_errors: bool, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Settings { + pub general: GeneralSettings, + pub dashboard: DashboardSettings, + pub advanced: AdvancedSettings, + pub tty_forward: Option, + pub monitoring: Option, + pub network: Option, + pub port: Option, + pub notifications: Option, + pub terminal_integrations: Option, + pub updates: Option, + pub security: Option, + pub debug: Option, +} + +impl Default for Settings { + fn default() -> Self { + let mut default_notification_types = HashMap::new(); + default_notification_types.insert("info".to_string(), true); + default_notification_types.insert("success".to_string(), true); + default_notification_types.insert("warning".to_string(), true); + default_notification_types.insert("error".to_string(), true); + default_notification_types.insert("server_status".to_string(), true); + default_notification_types.insert("update_available".to_string(), true); + + let mut enabled_terminals = HashMap::new(); + enabled_terminals.insert("Terminal".to_string(), true); + enabled_terminals.insert("iTerm2".to_string(), true); + enabled_terminals.insert("Hyper".to_string(), true); + enabled_terminals.insert("Alacritty".to_string(), true); + enabled_terminals.insert("Warp".to_string(), true); + enabled_terminals.insert("Ghostty".to_string(), false); + enabled_terminals.insert("WezTerm".to_string(), false); + + Self { + general: GeneralSettings { + launch_at_login: false, + show_dock_icon: true, + default_terminal: "system".to_string(), + default_shell: "default".to_string(), + show_welcome_on_startup: Some(true), + theme: Some("auto".to_string()), + language: Some("en".to_string()), + check_updates_automatically: Some(true), + prompt_move_to_applications: None, + }, + dashboard: DashboardSettings { + server_port: 4020, + enable_password: false, + password: String::new(), + access_mode: "localhost".to_string(), + auto_cleanup: true, + session_limit: Some(10), + idle_timeout_minutes: Some(30), + enable_cors: Some(true), + allowed_origins: Some(vec!["*".to_string()]), + }, + advanced: AdvancedSettings { + debug_mode: false, + log_level: "info".to_string(), + session_timeout: 0, + ngrok_auth_token: None, + ngrok_region: Some("us".to_string()), + ngrok_subdomain: None, + enable_telemetry: Some(false), + experimental_features: Some(false), + }, + tty_forward: Some(TTYForwardSettings { + enabled: false, + default_port: 8022, + bind_address: "127.0.0.1".to_string(), + max_connections: 5, + buffer_size: 4096, + keep_alive: true, + authentication: None, + }), + monitoring: Some(MonitoringSettings { + enabled: true, + collect_metrics: true, + metric_interval_seconds: 5, + max_history_size: 1000, + alert_on_high_cpu: false, + alert_on_high_memory: false, + cpu_threshold_percent: Some(80), + memory_threshold_percent: Some(80), + }), + network: Some(NetworkSettings { + preferred_interface: None, + enable_ipv6: true, + dns_servers: None, + proxy_settings: None, + connection_timeout_seconds: 30, + retry_attempts: 3, + }), + port: Some(PortSettings { + auto_resolve_conflicts: true, + preferred_port_range_start: 4000, + preferred_port_range_end: 5000, + excluded_ports: None, + conflict_resolution_strategy: "increment".to_string(), + }), + notifications: Some(NotificationSettings { + enabled: true, + show_in_system: true, + play_sound: true, + notification_types: default_notification_types, + do_not_disturb_enabled: Some(false), + do_not_disturb_start: None, + do_not_disturb_end: None, + }), + terminal_integrations: Some(TerminalIntegrationSettings { + enabled_terminals, + terminal_configs: HashMap::new(), + default_terminal_override: None, + }), + updates: Some(UpdateSettings { + channel: "stable".to_string(), + check_frequency: "weekly".to_string(), + auto_download: false, + auto_install: false, + show_release_notes: true, + include_pre_releases: false, + }), + security: Some(SecuritySettings { + enable_encryption: false, + encryption_algorithm: Some("aes-256-gcm".to_string()), + require_authentication: false, + session_token_expiry_hours: Some(24), + allowed_ip_addresses: None, + blocked_ip_addresses: None, + rate_limiting_enabled: false, + rate_limit_requests_per_minute: Some(60), + }), + debug: Some(DebugSettings { + enable_debug_menu: false, + show_performance_stats: false, + enable_verbose_logging: false, + log_to_file: false, + log_file_path: None, + max_log_file_size_mb: Some(50), + enable_dev_tools: false, + show_internal_errors: false, + }), + } + } +} + +impl Settings { + pub fn load() -> Result { + let config_path = Self::config_path()?; + + let mut settings = if !config_path.exists() { + Self::default() + } else { + let contents = std::fs::read_to_string(&config_path) + .map_err(|e| format!("Failed to read settings: {}", e))?; + + toml::from_str(&contents).map_err(|e| format!("Failed to parse settings: {}", e))? + }; + + // Load passwords from keychain + if let Ok(Some(password)) = crate::keychain::KeychainManager::get_dashboard_password() { + settings.dashboard.password = password; + } + + if let Ok(Some(token)) = crate::keychain::KeychainManager::get_ngrok_auth_token() { + settings.advanced.ngrok_auth_token = Some(token); + } + + Ok(settings) + } + + pub fn save(&self) -> Result<(), String> { + let config_path = Self::config_path()?; + + // Ensure the config directory exists + if let Some(parent) = config_path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("Failed to create config directory: {}", e))?; + } + + // Clone settings to remove sensitive data before saving + let mut settings_to_save = self.clone(); + + // Save passwords to keychain and remove from TOML + if !self.dashboard.password.is_empty() { + crate::keychain::KeychainManager::set_dashboard_password(&self.dashboard.password) + .map_err(|e| { + format!( + "Failed to save dashboard password to keychain: {}", + e.message + ) + })?; + settings_to_save.dashboard.password = String::new(); + } + + if let Some(ref token) = self.advanced.ngrok_auth_token { + if !token.is_empty() { + crate::keychain::KeychainManager::set_ngrok_auth_token(token).map_err(|e| { + format!("Failed to save ngrok token to keychain: {}", e.message) + })?; + settings_to_save.advanced.ngrok_auth_token = None; + } + } + + let contents = toml::to_string_pretty(&settings_to_save) + .map_err(|e| format!("Failed to serialize settings: {}", e))?; + + std::fs::write(&config_path, contents) + .map_err(|e| format!("Failed to write settings: {}", e))?; + + Ok(()) + } + + fn config_path() -> Result { + let proj_dirs = ProjectDirs::from("com", "vibetunnel", "VibeTunnel") + .ok_or_else(|| "Failed to get project directories".to_string())?; + + Ok(proj_dirs.config_dir().join("settings.toml")) + } + + /// Migrate passwords from settings file to keychain (one-time operation) + #[allow(dead_code)] + pub fn migrate_passwords_to_keychain(&self) -> Result<(), String> { + // Check if we have passwords in the settings file that need migration + let config_path = Self::config_path()?; + if !config_path.exists() { + return Ok(()); + } + + let contents = std::fs::read_to_string(&config_path) + .map_err(|e| format!("Failed to read settings for migration: {}", e))?; + + let raw_settings: Settings = toml::from_str(&contents) + .map_err(|e| format!("Failed to parse settings for migration: {}", e))?; + + let mut migrated = false; + + // Migrate dashboard password if present in file + if !raw_settings.dashboard.password.is_empty() { + crate::keychain::KeychainManager::set_dashboard_password( + &raw_settings.dashboard.password, + ) + .map_err(|e| format!("Failed to migrate dashboard password: {}", e.message))?; + migrated = true; + } + + // Migrate ngrok token if present in file + if let Some(ref token) = raw_settings.advanced.ngrok_auth_token { + if !token.is_empty() { + crate::keychain::KeychainManager::set_ngrok_auth_token(token) + .map_err(|e| format!("Failed to migrate ngrok token: {}", e.message))?; + migrated = true; + } + } + + // If we migrated anything, save the settings again to remove passwords from file + if migrated { + self.save()?; + } + + Ok(()) + } +} + +#[tauri::command] +pub async fn get_settings(_state: State<'_, AppState>) -> Result { + Settings::load() +} + +#[tauri::command] +pub async fn save_settings( + settings: Settings, + _state: State<'_, AppState>, + app: tauri::AppHandle, +) -> Result<(), String> { + settings.save()?; + + // Apply settings that need immediate effect + if settings.general.launch_at_login { + crate::auto_launch::enable_auto_launch()?; + } else { + crate::auto_launch::disable_auto_launch()?; + } + + // Apply dock icon visibility on macOS + #[cfg(target_os = "macos")] + { + // Check if any windows are visible + let has_visible_windows = app + .windows() + .values() + .any(|w| w.is_visible().unwrap_or(false)); + + if !has_visible_windows && !settings.general.show_dock_icon { + // Hide dock icon if no windows are visible and setting is disabled + let _ = app.set_activation_policy(tauri::ActivationPolicy::Accessory); + } else if settings.general.show_dock_icon && !has_visible_windows { + // Show dock icon if setting is enabled (even with no windows) + let _ = app.set_activation_policy(tauri::ActivationPolicy::Regular); + } + // Note: If windows are visible, we always show the dock icon regardless of setting + } + + Ok(()) +} diff --git a/tauri/src-tauri/src/state.rs b/tauri/src-tauri/src/state.rs new file mode 100644 index 00000000..ff72653f --- /dev/null +++ b/tauri/src-tauri/src/state.rs @@ -0,0 +1,105 @@ +use crate::api_client::ApiClient; +use crate::api_testing::APITestingManager; +use crate::auth_cache::AuthCacheManager; +use crate::backend_manager::BackendManager; +use crate::debug_features::DebugFeaturesManager; +use crate::ngrok::NgrokManager; +use crate::notification_manager::NotificationManager; +use crate::permissions::PermissionsManager; +use crate::session_monitor::SessionMonitor; +use crate::terminal::TerminalManager; +use crate::terminal_integrations::TerminalIntegrationsManager; +use crate::terminal_spawn_service::TerminalSpawnService; +use crate::tty_forward::TTYForwardManager; +#[cfg(unix)] +use crate::unix_socket_server::UnixSocketServer; +use crate::updater::UpdateManager; +use crate::welcome::WelcomeManager; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[derive(Clone)] +pub struct AppState { + pub terminal_manager: Arc, + pub api_client: Arc, + pub ngrok_manager: Arc, + pub server_monitoring: Arc, + pub server_target_port: Arc>>, + pub tty_forward_manager: Arc, + pub session_monitor: Arc, + pub notification_manager: Arc, + pub welcome_manager: Arc, + pub permissions_manager: Arc, + pub update_manager: Arc, + pub backend_manager: Arc, + pub debug_features_manager: Arc, + pub api_testing_manager: Arc, + pub auth_cache_manager: Arc, + pub terminal_integrations_manager: Arc, + pub terminal_spawn_service: Arc, + #[cfg(unix)] + pub unix_socket_server: Arc, +} + +impl AppState { + pub fn new() -> Self { + let terminal_manager = Arc::new(TerminalManager::new()); + let session_monitor = Arc::new(SessionMonitor::new(terminal_manager.clone())); + let notification_manager = Arc::new(NotificationManager::new()); + let mut permissions_manager = PermissionsManager::new(); + permissions_manager.set_notification_manager(notification_manager.clone()); + + let current_version = env!("CARGO_PKG_VERSION").to_string(); + let mut update_manager = UpdateManager::new(current_version); + update_manager.set_notification_manager(notification_manager.clone()); + + // Get port from settings or use default + let settings = crate::settings::Settings::load().unwrap_or_default(); + let port = settings.dashboard.port; + let backend_manager = BackendManager::new(port); + let api_client = Arc::new(ApiClient::new(port)); + + let mut debug_features_manager = DebugFeaturesManager::new(); + debug_features_manager.set_notification_manager(notification_manager.clone()); + + let mut api_testing_manager = APITestingManager::new(); + api_testing_manager.set_notification_manager(notification_manager.clone()); + + let mut auth_cache_manager = AuthCacheManager::new(); + auth_cache_manager.set_notification_manager(notification_manager.clone()); + + let mut terminal_integrations_manager = TerminalIntegrationsManager::new(); + terminal_integrations_manager.set_notification_manager(notification_manager.clone()); + + let terminal_integrations_manager = Arc::new(terminal_integrations_manager); + let terminal_spawn_service = Arc::new(TerminalSpawnService::new( + terminal_integrations_manager.clone(), + )); + + #[cfg(unix)] + let unix_socket_server = Arc::new(UnixSocketServer::new(terminal_spawn_service.clone())); + + Self { + terminal_manager, + api_client, + ngrok_manager: Arc::new(NgrokManager::new()), + server_monitoring: Arc::new(AtomicBool::new(true)), + server_target_port: Arc::new(RwLock::new(None)), + tty_forward_manager: Arc::new(TTYForwardManager::new()), + session_monitor, + notification_manager, + welcome_manager: Arc::new(WelcomeManager::new()), + permissions_manager: Arc::new(permissions_manager), + update_manager: Arc::new(update_manager), + backend_manager: Arc::new(backend_manager), + debug_features_manager: Arc::new(debug_features_manager), + api_testing_manager: Arc::new(api_testing_manager), + auth_cache_manager: Arc::new(auth_cache_manager), + terminal_integrations_manager, + terminal_spawn_service, + #[cfg(unix)] + unix_socket_server, + } + } +} diff --git a/tauri/src-tauri/src/terminal.rs b/tauri/src-tauri/src/terminal.rs new file mode 100644 index 00000000..10a13c13 --- /dev/null +++ b/tauri/src-tauri/src/terminal.rs @@ -0,0 +1,292 @@ +use bytes::Bytes; +use chrono::Utc; +use portable_pty::{native_pty_system, Child, CommandBuilder, PtyPair, PtySize}; +use std::collections::HashMap; +use std::io::{Read, Write}; +use std::sync::{Arc, Mutex}; +use tokio::sync::{mpsc, RwLock}; +use tracing::{debug, error, info}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct TerminalManager { + sessions: Arc>>>>, +} + +pub struct TerminalSession { + pub _id: String, + pub name: String, + pub pid: u32, + pub rows: u16, + pub cols: u16, + pub created_at: String, + pub _cwd: String, + pty_pair: PtyPair, + #[allow(dead_code)] + child: Box, + writer: Box, + #[allow(dead_code)] + reader_thread: Option>, + #[allow(dead_code)] + output_tx: mpsc::UnboundedSender, + pub output_rx: Arc>>, +} + +impl TerminalManager { + pub fn new() -> Self { + Self { + sessions: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub async fn create_session( + &self, + name: String, + rows: u16, + cols: u16, + cwd: Option, + env: Option>, + shell: Option, + ) -> Result { + let id = Uuid::new_v4().to_string(); + + // Set up PTY + let pty_system = native_pty_system(); + let pty_pair = pty_system + .openpty(PtySize { + rows, + cols, + pixel_width: 0, + pixel_height: 0, + }) + .map_err(|e| format!("Failed to open PTY: {}", e))?; + + // Configure shell command + let shell = shell.unwrap_or_else(|| { + std::env::var("SHELL").unwrap_or_else(|_| { + if cfg!(target_os = "windows") { + "cmd.exe".to_string() + } else { + "/bin/bash".to_string() + } + }) + }); + + let mut cmd = CommandBuilder::new(&shell); + + // Set working directory + if let Some(cwd) = &cwd { + cmd.cwd(cwd); + } + + // Set environment variables + if let Some(env_vars) = env { + for (key, value) in env_vars { + cmd.env(key, value); + } + } + + // Spawn the shell process + let child = pty_pair + .slave + .spawn_command(cmd) + .map_err(|e| format!("Failed to spawn shell: {}", e))?; + + let pid = child.process_id().unwrap_or(0); + + // Set up output channel + let (output_tx, output_rx) = mpsc::unbounded_channel(); + + // Get reader and writer + let reader = pty_pair + .master + .try_clone_reader() + .map_err(|e| format!("Failed to clone reader: {}", e))?; + + let writer = pty_pair + .master + .take_writer() + .map_err(|e| format!("Failed to take writer: {}", e))?; + + // Start reader thread + let output_tx_clone = output_tx.clone(); + let reader_thread = std::thread::spawn(move || { + let mut reader = reader; + let mut buffer = [0u8; 4096]; + + loop { + match reader.read(&mut buffer) { + Ok(0) => { + debug!("PTY closed"); + break; + } + Ok(n) => { + let data = Bytes::copy_from_slice(&buffer[..n]); + + if output_tx_clone.send(data).is_err() { + debug!("Output channel closed"); + break; + } + } + Err(e) => { + error!("Error reading from PTY: {}", e); + break; + } + } + } + }); + + let session = TerminalSession { + _id: id.clone(), + name: name.clone(), + pid, + rows, + cols, + created_at: Utc::now().to_rfc3339(), + _cwd: cwd.unwrap_or_else(|| { + std::env::current_dir() + .unwrap() + .to_string_lossy() + .to_string() + }), + pty_pair, + child, + writer, + reader_thread: Some(reader_thread), + output_tx, + output_rx: Arc::new(Mutex::new(output_rx)), + }; + + // Store session + self.sessions + .write() + .await + .insert(id.clone(), Arc::new(RwLock::new(session))); + + info!("Created terminal session: {} ({})", name, id); + + Ok(crate::commands::Terminal { + id, + name, + pid, + rows, + cols, + created_at: Utc::now().to_rfc3339(), + }) + } + + pub async fn list_sessions(&self) -> Vec { + let sessions = self.sessions.read().await; + let mut result = Vec::new(); + + for (id, session) in sessions.iter() { + let session = session.read().await; + result.push(crate::commands::Terminal { + id: id.clone(), + name: session.name.clone(), + pid: session.pid, + rows: session.rows, + cols: session.cols, + created_at: session.created_at.clone(), + }); + } + + result + } + + pub async fn get_session(&self, id: &str) -> Option>> { + self.sessions.read().await.get(id).cloned() + } + + pub async fn close_all_sessions(&self) -> Result<(), String> { + let mut sessions = self.sessions.write().await; + let session_count = sessions.len(); + + // Clear all sessions + sessions.clear(); + + info!("Closed all {} terminal sessions", session_count); + Ok(()) + } + + pub async fn close_session(&self, id: &str) -> Result<(), String> { + let mut sessions = self.sessions.write().await; + + if let Some(session_arc) = sessions.remove(id) { + // Session will be dropped when it goes out of scope + drop(session_arc); + + info!("Closed terminal session: {}", id); + Ok(()) + } else { + Err(format!("Session not found: {}", id)) + } + } + + pub async fn resize_session(&self, id: &str, rows: u16, cols: u16) -> Result<(), String> { + if let Some(session_arc) = self.get_session(id).await { + let mut session = session_arc.write().await; + + session + .pty_pair + .master + .resize(PtySize { + rows, + cols, + pixel_width: 0, + pixel_height: 0, + }) + .map_err(|e| format!("Failed to resize PTY: {}", e))?; + + session.rows = rows; + session.cols = cols; + + debug!("Resized terminal {} to {}x{}", id, cols, rows); + Ok(()) + } else { + Err(format!("Session not found: {}", id)) + } + } + + pub async fn write_to_session(&self, id: &str, data: &[u8]) -> Result<(), String> { + if let Some(session_arc) = self.get_session(id).await { + let mut session = session_arc.write().await; + + session + .writer + .write_all(data) + .map_err(|e| format!("Failed to write to PTY: {}", e))?; + + session + .writer + .flush() + .map_err(|e| format!("Failed to flush PTY: {}", e))?; + + Ok(()) + } else { + Err(format!("Session not found: {}", id)) + } + } + + pub async fn read_from_session(&self, id: &str) -> Result, String> { + if let Some(session_arc) = self.get_session(id).await { + let session = session_arc.read().await; + let mut rx = session.output_rx.lock().unwrap(); + + // Try to receive data without blocking + match rx.try_recv() { + Ok(data) => Ok(data.to_vec()), + Err(mpsc::error::TryRecvError::Empty) => Ok(vec![]), + Err(mpsc::error::TryRecvError::Disconnected) => { + Err("Output channel disconnected".to_string()) + } + } + } else { + Err(format!("Session not found: {}", id)) + } + } +} + +// Make TerminalSession Send + Sync +unsafe impl Send for TerminalSession {} +unsafe impl Sync for TerminalSession {} diff --git a/tauri/src-tauri/src/terminal_detector.rs b/tauri/src-tauri/src/terminal_detector.rs new file mode 100644 index 00000000..5aa16adc --- /dev/null +++ b/tauri/src-tauri/src/terminal_detector.rs @@ -0,0 +1,250 @@ +use serde::{Deserialize, Serialize}; +use std::process::Command; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TerminalInfo { + pub name: String, + pub path: String, + pub available: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DetectedTerminals { + pub default: Option, + pub available: Vec, +} + +pub fn detect_terminals() -> Result { + let mut available_terminals = Vec::new(); + let mut default_terminal = None; + + #[cfg(target_os = "macos")] + { + // Check for Terminal.app + if let Ok(_) = Command::new("open").args(&["-Ra", "Terminal.app"]).output() { + available_terminals.push(TerminalInfo { + name: "Terminal".to_string(), + path: "/System/Applications/Utilities/Terminal.app".to_string(), + available: true, + }); + } + + // Check for iTerm2 + if let Ok(_) = Command::new("open").args(&["-Ra", "iTerm.app"]).output() { + available_terminals.push(TerminalInfo { + name: "iTerm2".to_string(), + path: "/Applications/iTerm.app".to_string(), + available: true, + }); + } + + // Check for Warp + if let Ok(output) = Command::new("which").arg("warp").output() { + if output.status.success() { + available_terminals.push(TerminalInfo { + name: "Warp".to_string(), + path: String::from_utf8_lossy(&output.stdout).trim().to_string(), + available: true, + }); + } + } + + // Check for Hyper + if let Ok(_) = Command::new("open").args(&["-Ra", "Hyper.app"]).output() { + available_terminals.push(TerminalInfo { + name: "Hyper".to_string(), + path: "/Applications/Hyper.app".to_string(), + available: true, + }); + } + + // Check for Alacritty + if let Ok(output) = Command::new("which").arg("alacritty").output() { + if output.status.success() { + available_terminals.push(TerminalInfo { + name: "Alacritty".to_string(), + path: String::from_utf8_lossy(&output.stdout).trim().to_string(), + available: true, + }); + } + } + + // Get default terminal from environment or system + if let Ok(term_program) = std::env::var("TERM_PROGRAM") { + match term_program.as_str() { + "Apple_Terminal" => { + default_terminal = Some(TerminalInfo { + name: "Terminal".to_string(), + path: "/System/Applications/Utilities/Terminal.app".to_string(), + available: true, + }); + } + "iTerm.app" => { + default_terminal = Some(TerminalInfo { + name: "iTerm2".to_string(), + path: "/Applications/iTerm.app".to_string(), + available: true, + }); + } + _ => {} + } + } + + // If no default found, use first available + if default_terminal.is_none() && !available_terminals.is_empty() { + default_terminal = Some(available_terminals[0].clone()); + } + } + + #[cfg(target_os = "windows")] + { + // Check for Windows Terminal + if let Ok(output) = Command::new("where").arg("wt.exe").output() { + if output.status.success() { + let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); + available_terminals.push(TerminalInfo { + name: "Windows Terminal".to_string(), + path: path.clone(), + available: true, + }); + default_terminal = Some(TerminalInfo { + name: "Windows Terminal".to_string(), + path, + available: true, + }); + } + } + + // Check for PowerShell + if let Ok(output) = Command::new("where").arg("powershell.exe").output() { + if output.status.success() { + available_terminals.push(TerminalInfo { + name: "PowerShell".to_string(), + path: String::from_utf8_lossy(&output.stdout).trim().to_string(), + available: true, + }); + } + } + + // Check for Command Prompt + if let Ok(output) = Command::new("where").arg("cmd.exe").output() { + if output.status.success() { + available_terminals.push(TerminalInfo { + name: "Command Prompt".to_string(), + path: String::from_utf8_lossy(&output.stdout).trim().to_string(), + available: true, + }); + } + } + + // Check for Git Bash + let git_bash_path = "C:\\Program Files\\Git\\git-bash.exe"; + if std::path::Path::new(git_bash_path).exists() { + available_terminals.push(TerminalInfo { + name: "Git Bash".to_string(), + path: git_bash_path.to_string(), + available: true, + }); + } + } + + #[cfg(target_os = "linux")] + { + // Check for various Linux terminals + let terminals = vec![ + ("gnome-terminal", "GNOME Terminal"), + ("konsole", "Konsole"), + ("xfce4-terminal", "XFCE Terminal"), + ("terminator", "Terminator"), + ("alacritty", "Alacritty"), + ("kitty", "Kitty"), + ("tilix", "Tilix"), + ("xterm", "XTerm"), + ]; + + for (cmd, name) in terminals { + if let Ok(output) = Command::new("which").arg(cmd).output() { + if output.status.success() { + available_terminals.push(TerminalInfo { + name: name.to_string(), + path: String::from_utf8_lossy(&output.stdout).trim().to_string(), + available: true, + }); + } + } + } + + // Try to detect default terminal from environment + if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") { + match desktop.to_lowercase().as_str() { + "gnome" | "ubuntu" => { + default_terminal = available_terminals + .iter() + .find(|t| t.name == "GNOME Terminal") + .cloned(); + } + "kde" => { + default_terminal = available_terminals + .iter() + .find(|t| t.name == "Konsole") + .cloned(); + } + "xfce" => { + default_terminal = available_terminals + .iter() + .find(|t| t.name == "XFCE Terminal") + .cloned(); + } + _ => {} + } + } + + // If no default found, use first available + if default_terminal.is_none() && !available_terminals.is_empty() { + default_terminal = Some(available_terminals[0].clone()); + } + } + + Ok(DetectedTerminals { + default: default_terminal, + available: available_terminals, + }) +} + +#[tauri::command] +pub async fn detect_system_terminals() -> Result { + detect_terminals() +} + +#[tauri::command] +pub async fn get_default_shell() -> Result { + #[cfg(unix)] + { + if let Ok(shell) = std::env::var("SHELL") { + return Ok(shell); + } + + // Fallback to common shells + let shells = vec!["/bin/zsh", "/bin/bash", "/bin/sh"]; + for shell in shells { + if std::path::Path::new(shell).exists() { + return Ok(shell.to_string()); + } + } + } + + #[cfg(windows)] + { + // On Windows, default to PowerShell + if let Ok(output) = Command::new("where").arg("powershell.exe").output() { + if output.status.success() { + return Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()); + } + } + + // Fallback to cmd + return Ok("cmd.exe".to_string()); + } + + Err("Could not detect default shell".to_string()) +} diff --git a/tauri/src-tauri/src/terminal_integrations.rs b/tauri/src-tauri/src/terminal_integrations.rs new file mode 100644 index 00000000..73adea9a --- /dev/null +++ b/tauri/src-tauri/src/terminal_integrations.rs @@ -0,0 +1,736 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; +use std::process::Command; +use std::sync::Arc; +use tokio::sync::RwLock; + +/// Terminal emulator type +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum TerminalEmulator { + SystemDefault, + Terminal, // macOS Terminal.app + ITerm2, // iTerm2 + Hyper, // Hyper + Alacritty, // Alacritty + Kitty, // Kitty + WezTerm, // WezTerm + Ghostty, // Ghostty + Warp, // Warp + WindowsTerminal, // Windows Terminal + ConEmu, // ConEmu + Cmder, // Cmder + Gnome, // GNOME Terminal + Konsole, // KDE Konsole + Xterm, // XTerm + Custom, // Custom terminal +} + +impl TerminalEmulator { + pub fn display_name(&self) -> &str { + match self { + TerminalEmulator::SystemDefault => "System Default", + TerminalEmulator::Terminal => "Terminal", + TerminalEmulator::ITerm2 => "iTerm2", + TerminalEmulator::Hyper => "Hyper", + TerminalEmulator::Alacritty => "Alacritty", + TerminalEmulator::Kitty => "Kitty", + TerminalEmulator::WezTerm => "WezTerm", + TerminalEmulator::Ghostty => "Ghostty", + TerminalEmulator::Warp => "Warp", + TerminalEmulator::WindowsTerminal => "Windows Terminal", + TerminalEmulator::ConEmu => "ConEmu", + TerminalEmulator::Cmder => "Cmder", + TerminalEmulator::Gnome => "GNOME Terminal", + TerminalEmulator::Konsole => "Konsole", + TerminalEmulator::Xterm => "XTerm", + TerminalEmulator::Custom => "Custom", + } + } +} + +/// Terminal integration configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalConfig { + pub emulator: TerminalEmulator, + pub name: String, + pub executable_path: PathBuf, + pub args_template: Vec, + pub env_vars: HashMap, + pub features: TerminalFeatures, + pub platform: Vec, // ["macos", "windows", "linux"] +} + +/// Terminal features +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalFeatures { + pub supports_tabs: bool, + pub supports_splits: bool, + pub supports_profiles: bool, + pub supports_themes: bool, + pub supports_scripting: bool, + pub supports_url_scheme: bool, + pub supports_remote_control: bool, +} + +/// Terminal launch options +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalLaunchOptions { + pub working_directory: Option, + pub command: Option, + pub args: Vec, + pub env_vars: HashMap, + pub title: Option, + pub profile: Option, + pub tab: bool, + pub split: Option, + pub window_size: Option<(u32, u32)>, +} + +/// Split direction +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum SplitDirection { + Horizontal, + Vertical, +} + +/// Terminal integration info +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalIntegrationInfo { + pub emulator: TerminalEmulator, + pub installed: bool, + pub version: Option, + pub path: Option, + pub is_default: bool, + pub config: Option, +} + +/// Terminal URL scheme +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalURLScheme { + pub scheme: String, + pub supports_ssh: bool, + pub supports_local: bool, + pub template: String, +} + +/// Terminal integrations manager +pub struct TerminalIntegrationsManager { + configs: Arc>>, + detected_terminals: Arc>>, + default_terminal: Arc>, + url_schemes: Arc>>, + notification_manager: Option>, +} + +impl TerminalIntegrationsManager { + /// Create a new terminal integrations manager + pub fn new() -> Self { + Self { + configs: Arc::new(RwLock::new(Self::initialize_default_configs())), + detected_terminals: Arc::new(RwLock::new(HashMap::new())), + default_terminal: Arc::new(RwLock::new(TerminalEmulator::SystemDefault)), + url_schemes: Arc::new(RwLock::new(Self::initialize_url_schemes())), + notification_manager: None, + } + } + + /// Set the notification manager + pub fn set_notification_manager( + &mut self, + notification_manager: Arc, + ) { + self.notification_manager = Some(notification_manager); + } + + /// Initialize default terminal configurations + fn initialize_default_configs() -> HashMap { + let mut configs = HashMap::new(); + + // WezTerm configuration + configs.insert( + TerminalEmulator::WezTerm, + TerminalConfig { + emulator: TerminalEmulator::WezTerm, + name: "WezTerm".to_string(), + executable_path: PathBuf::from("/Applications/WezTerm.app/Contents/MacOS/wezterm"), + args_template: vec![ + "start".to_string(), + "--cwd".to_string(), + "{working_directory}".to_string(), + "--".to_string(), + "{command}".to_string(), + "{args}".to_string(), + ], + env_vars: HashMap::new(), + features: TerminalFeatures { + supports_tabs: true, + supports_splits: true, + supports_profiles: true, + supports_themes: true, + supports_scripting: true, + supports_url_scheme: false, + supports_remote_control: true, + }, + platform: vec![ + "macos".to_string(), + "windows".to_string(), + "linux".to_string(), + ], + }, + ); + + // Ghostty configuration + configs.insert( + TerminalEmulator::Ghostty, + TerminalConfig { + emulator: TerminalEmulator::Ghostty, + name: "Ghostty".to_string(), + executable_path: PathBuf::from("/Applications/Ghostty.app/Contents/MacOS/ghostty"), + args_template: vec![ + "--working-directory".to_string(), + "{working_directory}".to_string(), + "--command".to_string(), + "{command}".to_string(), + "{args}".to_string(), + ], + env_vars: HashMap::new(), + features: TerminalFeatures { + supports_tabs: true, + supports_splits: true, + supports_profiles: true, + supports_themes: true, + supports_scripting: false, + supports_url_scheme: false, + supports_remote_control: false, + }, + platform: vec!["macos".to_string()], + }, + ); + + // iTerm2 configuration + configs.insert( + TerminalEmulator::ITerm2, + TerminalConfig { + emulator: TerminalEmulator::ITerm2, + name: "iTerm2".to_string(), + executable_path: PathBuf::from("/Applications/iTerm.app/Contents/MacOS/iTerm2"), + args_template: vec![], + env_vars: HashMap::new(), + features: TerminalFeatures { + supports_tabs: true, + supports_splits: true, + supports_profiles: true, + supports_themes: true, + supports_scripting: true, + supports_url_scheme: true, + supports_remote_control: true, + }, + platform: vec!["macos".to_string()], + }, + ); + + // Alacritty configuration + configs.insert( + TerminalEmulator::Alacritty, + TerminalConfig { + emulator: TerminalEmulator::Alacritty, + name: "Alacritty".to_string(), + executable_path: PathBuf::from( + "/Applications/Alacritty.app/Contents/MacOS/alacritty", + ), + args_template: vec![ + "--working-directory".to_string(), + "{working_directory}".to_string(), + "-e".to_string(), + "{command}".to_string(), + "{args}".to_string(), + ], + env_vars: HashMap::new(), + features: TerminalFeatures { + supports_tabs: false, + supports_splits: false, + supports_profiles: true, + supports_themes: true, + supports_scripting: false, + supports_url_scheme: false, + supports_remote_control: false, + }, + platform: vec![ + "macos".to_string(), + "windows".to_string(), + "linux".to_string(), + ], + }, + ); + + // Kitty configuration + configs.insert( + TerminalEmulator::Kitty, + TerminalConfig { + emulator: TerminalEmulator::Kitty, + name: "Kitty".to_string(), + executable_path: PathBuf::from("/Applications/kitty.app/Contents/MacOS/kitty"), + args_template: vec![ + "--directory".to_string(), + "{working_directory}".to_string(), + "{command}".to_string(), + "{args}".to_string(), + ], + env_vars: HashMap::new(), + features: TerminalFeatures { + supports_tabs: true, + supports_splits: true, + supports_profiles: true, + supports_themes: true, + supports_scripting: true, + supports_url_scheme: false, + supports_remote_control: true, + }, + platform: vec!["macos".to_string(), "linux".to_string()], + }, + ); + + configs + } + + /// Initialize URL schemes + fn initialize_url_schemes() -> HashMap { + let mut schemes = HashMap::new(); + + schemes.insert( + TerminalEmulator::ITerm2, + TerminalURLScheme { + scheme: "iterm2".to_string(), + supports_ssh: true, + supports_local: true, + template: "iterm2://ssh/{user}@{host}:{port}".to_string(), + }, + ); + + schemes + } + + /// Detect installed terminals + pub async fn detect_terminals(&self) -> Vec { + let mut detected = Vec::new(); + let configs = self.configs.read().await; + + for (emulator, config) in configs.iter() { + let info = self.check_terminal_installation(emulator, config).await; + if info.installed { + detected.push(info.clone()); + self.detected_terminals + .write() + .await + .insert(*emulator, info); + } + } + + // Check system default + let default_info = self.detect_system_default().await; + detected.insert(0, default_info); + + detected + } + + /// Check if a specific terminal is installed + async fn check_terminal_installation( + &self, + emulator: &TerminalEmulator, + config: &TerminalConfig, + ) -> TerminalIntegrationInfo { + let installed = config.executable_path.exists(); + let version = if installed { + self.get_terminal_version(emulator, &config.executable_path) + .await + } else { + None + }; + + TerminalIntegrationInfo { + emulator: *emulator, + installed, + version, + path: if installed { + Some(config.executable_path.clone()) + } else { + None + }, + is_default: false, + config: if installed { + Some(config.clone()) + } else { + None + }, + } + } + + /// Get terminal version + async fn get_terminal_version( + &self, + emulator: &TerminalEmulator, + path: &PathBuf, + ) -> Option { + match emulator { + TerminalEmulator::WezTerm => Command::new(path) + .arg("--version") + .output() + .ok() + .and_then(|output| String::from_utf8(output.stdout).ok()) + .map(|v| v.trim().to_string()), + TerminalEmulator::Alacritty => Command::new(path) + .arg("--version") + .output() + .ok() + .and_then(|output| String::from_utf8(output.stdout).ok()) + .map(|v| v.trim().to_string()), + _ => None, + } + } + + /// Detect system default terminal + async fn detect_system_default(&self) -> TerminalIntegrationInfo { + #[cfg(target_os = "macos")] + { + TerminalIntegrationInfo { + emulator: TerminalEmulator::Terminal, + installed: true, + version: None, + path: Some(PathBuf::from("/System/Applications/Utilities/Terminal.app")), + is_default: true, + config: None, + } + } + + #[cfg(target_os = "windows")] + { + TerminalIntegrationInfo { + emulator: TerminalEmulator::WindowsTerminal, + installed: true, + version: None, + path: None, + is_default: true, + config: None, + } + } + + #[cfg(target_os = "linux")] + { + TerminalIntegrationInfo { + emulator: TerminalEmulator::Gnome, + installed: true, + version: None, + path: None, + is_default: true, + config: None, + } + } + } + + /// Get default terminal + pub async fn get_default_terminal(&self) -> TerminalEmulator { + *self.default_terminal.read().await + } + + /// Set default terminal + pub async fn set_default_terminal(&self, emulator: TerminalEmulator) -> Result<(), String> { + // Check if terminal is installed + let detected = self.detected_terminals.read().await; + if emulator != TerminalEmulator::SystemDefault && !detected.contains_key(&emulator) { + return Err("Terminal not installed".to_string()); + } + + *self.default_terminal.write().await = emulator; + + // Notify user + if let Some(notification_manager) = &self.notification_manager { + let _ = notification_manager + .notify_success( + "Default Terminal Changed", + &format!("Default terminal set to {}", emulator.display_name()), + ) + .await; + } + + Ok(()) + } + + /// Launch terminal + pub async fn launch_terminal( + &self, + emulator: Option, + options: TerminalLaunchOptions, + ) -> Result<(), String> { + let emulator = emulator.unwrap_or(*self.default_terminal.read().await); + + match emulator { + TerminalEmulator::SystemDefault => self.launch_system_terminal(options).await, + _ => self.launch_specific_terminal(emulator, options).await, + } + } + + /// Launch system terminal + async fn launch_system_terminal(&self, options: TerminalLaunchOptions) -> Result<(), String> { + #[cfg(target_os = "macos")] + { + self.launch_macos_terminal(options).await + } + + #[cfg(target_os = "windows")] + { + self.launch_windows_terminal(options).await + } + + #[cfg(target_os = "linux")] + { + self.launch_linux_terminal(options).await + } + } + + /// Launch specific terminal + async fn launch_specific_terminal( + &self, + emulator: TerminalEmulator, + options: TerminalLaunchOptions, + ) -> Result<(), String> { + let configs = self.configs.read().await; + let config = configs + .get(&emulator) + .ok_or_else(|| "Terminal configuration not found".to_string())?; + + let mut command = Command::new(&config.executable_path); + + // Build command arguments + for arg_template in &config.args_template { + let arg = self.replace_template_variables(arg_template, &options); + if !arg.is_empty() { + command.arg(arg); + } + } + + // Set environment variables + for (key, value) in &config.env_vars { + command.env(key, value); + } + for (key, value) in &options.env_vars { + command.env(key, value); + } + + // Set working directory + if let Some(cwd) = &options.working_directory { + command.current_dir(cwd); + } + + // Launch terminal + command + .spawn() + .map_err(|e| format!("Failed to launch terminal: {}", e))?; + + Ok(()) + } + + /// Launch macOS terminal + #[cfg(target_os = "macos")] + async fn launch_macos_terminal(&self, options: TerminalLaunchOptions) -> Result<(), String> { + use std::process::Command; + + let mut script = String::from("tell application \"Terminal\"\n"); + script.push_str(" activate\n"); + + if options.tab { + script.push_str( + " tell application \"System Events\" to keystroke \"t\" using command down\n", + ); + } + + if let Some(cwd) = options.working_directory { + script.push_str(&format!( + " do script \"cd '{}'\" in front window\n", + cwd.display() + )); + } + + if let Some(command) = options.command { + let full_command = if options.args.is_empty() { + command + } else { + format!("{} {}", command, options.args.join(" ")) + }; + script.push_str(&format!( + " do script \"{}\" in front window\n", + full_command + )); + } + + script.push_str("end tell\n"); + + Command::new("osascript") + .arg("-e") + .arg(script) + .spawn() + .map_err(|e| format!("Failed to launch Terminal: {}", e))?; + + Ok(()) + } + + /// Launch Windows terminal + #[cfg(target_os = "windows")] + async fn launch_windows_terminal(&self, options: TerminalLaunchOptions) -> Result<(), String> { + use std::process::Command; + + let mut command = Command::new("wt.exe"); + + if let Some(cwd) = options.working_directory { + command.args(&["-d", cwd.to_str().unwrap_or(".")]); + } + + if options.tab { + command.arg("new-tab"); + } + + if let Some(cmd) = options.command { + command.args(&["--", &cmd]); + for arg in options.args { + command.arg(arg); + } + } + + command + .spawn() + .map_err(|e| format!("Failed to launch Windows Terminal: {}", e))?; + + Ok(()) + } + + /// Launch Linux terminal + #[cfg(target_os = "linux")] + async fn launch_linux_terminal(&self, options: TerminalLaunchOptions) -> Result<(), String> { + use std::process::Command; + + // Try common terminal emulators + let terminals = ["gnome-terminal", "konsole", "xfce4-terminal", "xterm"]; + + for terminal in &terminals { + if let Ok(output) = Command::new("which").arg(terminal).output() { + if output.status.success() { + let mut command = Command::new(terminal); + + if let Some(cwd) = &options.working_directory { + match *terminal { + "gnome-terminal" => { + command.arg("--working-directory").arg(cwd); + } + "konsole" => { + command.arg("--workdir").arg(cwd); + } + _ => {} + } + } + + if let Some(cmd) = &options.command { + match *terminal { + "gnome-terminal" => { + command.arg("--").arg(cmd); + } + "konsole" => { + command.arg("-e").arg(cmd); + } + _ => { + command.arg("-e").arg(cmd); + } + } + for arg in &options.args { + command.arg(arg); + } + } + + return command + .spawn() + .map_err(|e| format!("Failed to launch terminal: {}", e)) + .map(|_| ()); + } + } + } + + Err("No suitable terminal emulator found".to_string()) + } + + /// Create SSH URL + pub async fn create_ssh_url( + &self, + emulator: TerminalEmulator, + user: &str, + host: &str, + port: u16, + ) -> Option { + let schemes = self.url_schemes.read().await; + schemes.get(&emulator).map(|scheme| { + scheme + .template + .replace("{user}", user) + .replace("{host}", host) + .replace("{port}", &port.to_string()) + }) + } + + /// Get terminal configuration + pub async fn get_terminal_config(&self, emulator: TerminalEmulator) -> Option { + self.configs.read().await.get(&emulator).cloned() + } + + /// Update terminal configuration + pub async fn update_terminal_config(&self, config: TerminalConfig) { + self.configs.write().await.insert(config.emulator, config); + } + + /// List detected terminals + pub async fn list_detected_terminals(&self) -> Vec { + self.detected_terminals + .read() + .await + .values() + .cloned() + .collect() + } + + // Helper methods + fn replace_template_variables( + &self, + template: &str, + options: &TerminalLaunchOptions, + ) -> String { + let mut result = template.to_string(); + + if let Some(cwd) = &options.working_directory { + result = result.replace("{working_directory}", cwd.to_str().unwrap_or("")); + } + + if let Some(command) = &options.command { + result = result.replace("{command}", command); + } + + result = result.replace("{args}", &options.args.join(" ")); + + if let Some(title) = &options.title { + result = result.replace("{title}", title); + } + + // Remove empty placeholders + result = result.replace("{working_directory}", ""); + result = result.replace("{command}", ""); + result = result.replace("{args}", ""); + result = result.replace("{title}", ""); + + result.trim().to_string() + } +} + +/// Terminal integration statistics +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalIntegrationStats { + pub total_terminals: usize, + pub installed_terminals: usize, + pub default_terminal: TerminalEmulator, + pub terminals_by_platform: HashMap>, +} diff --git a/tauri/src-tauri/src/terminal_spawn_service.rs b/tauri/src-tauri/src/terminal_spawn_service.rs new file mode 100644 index 00000000..889478cb --- /dev/null +++ b/tauri/src-tauri/src/terminal_spawn_service.rs @@ -0,0 +1,208 @@ +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::sync::mpsc; + +/// Request to spawn a terminal +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalSpawnRequest { + pub session_id: String, + pub terminal_type: Option, + pub command: Option, + pub working_directory: Option, + pub environment: Option>, +} + +/// Response from terminal spawn +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalSpawnResponse { + pub success: bool, + pub error: Option, + pub terminal_pid: Option, +} + +/// Terminal Spawn Service - manages background terminal spawning +pub struct TerminalSpawnService { + request_tx: mpsc::Sender, + request_rx: Arc>>>, + #[allow(dead_code)] + terminal_integrations_manager: Arc, +} + +impl TerminalSpawnService { + pub fn new( + terminal_integrations_manager: Arc< + crate::terminal_integrations::TerminalIntegrationsManager, + >, + ) -> Self { + let (tx, rx) = mpsc::channel::(100); + + Self { + request_tx: tx, + request_rx: Arc::new(tokio::sync::Mutex::new(Some(rx))), + terminal_integrations_manager, + } + } + + /// Start the background worker - must be called after Tokio runtime is available + pub async fn start_worker(self: Arc) { + let rx = self.request_rx.lock().await.take(); + if let Some(mut rx) = rx { + let manager_clone = self.terminal_integrations_manager.clone(); + + tokio::spawn(async move { + while let Some(request) = rx.recv().await { + let manager = manager_clone.clone(); + tokio::spawn(async move { + let _ = Self::handle_spawn_request(request, manager).await; + }); + } + }); + } + } + + /// Queue a terminal spawn request + pub async fn spawn_terminal(&self, request: TerminalSpawnRequest) -> Result<(), String> { + self.request_tx + .send(request) + .await + .map_err(|e| format!("Failed to queue terminal spawn: {}", e)) + } + + /// Handle a spawn request + async fn handle_spawn_request( + request: TerminalSpawnRequest, + terminal_integrations_manager: Arc< + crate::terminal_integrations::TerminalIntegrationsManager, + >, + ) -> Result { + // Determine which terminal to use + let terminal_type = if let Some(terminal) = &request.terminal_type { + // Parse terminal type + match terminal.as_str() { + "Terminal" => crate::terminal_integrations::TerminalEmulator::Terminal, + "iTerm2" => crate::terminal_integrations::TerminalEmulator::ITerm2, + "Hyper" => crate::terminal_integrations::TerminalEmulator::Hyper, + "Alacritty" => crate::terminal_integrations::TerminalEmulator::Alacritty, + "Warp" => crate::terminal_integrations::TerminalEmulator::Warp, + "Kitty" => crate::terminal_integrations::TerminalEmulator::Kitty, + "WezTerm" => crate::terminal_integrations::TerminalEmulator::WezTerm, + "Ghostty" => crate::terminal_integrations::TerminalEmulator::Ghostty, + _ => terminal_integrations_manager.get_default_terminal().await, + } + } else { + terminal_integrations_manager.get_default_terminal().await + }; + + // Build launch options + let mut launch_options = crate::terminal_integrations::TerminalLaunchOptions { + command: request.command, + working_directory: request + .working_directory + .map(|s| std::path::PathBuf::from(s)), + args: vec![], + env_vars: request.environment.unwrap_or_default(), + title: Some(format!("VibeTunnel Session {}", request.session_id)), + profile: None, + tab: false, + split: None, + window_size: None, + }; + + // If no command specified, create a VibeTunnel session command + if launch_options.command.is_none() { + // Get server status to build the correct URL + let port = 4020; // Default port, should get from settings + launch_options.command = Some(format!( + "vt connect localhost:{}/{}", + port, request.session_id + )); + } + + // Launch the terminal + match terminal_integrations_manager + .launch_terminal(Some(terminal_type), launch_options) + .await + { + Ok(_) => Ok(TerminalSpawnResponse { + success: true, + error: None, + terminal_pid: None, // We don't track PIDs in the current implementation + }), + Err(e) => Ok(TerminalSpawnResponse { + success: false, + error: Some(e), + terminal_pid: None, + }), + } + } + + /// Spawn terminal for a specific session + pub async fn spawn_terminal_for_session( + &self, + session_id: String, + terminal_type: Option, + ) -> Result<(), String> { + let request = TerminalSpawnRequest { + session_id, + terminal_type, + command: None, + working_directory: None, + environment: None, + }; + + self.spawn_terminal(request).await + } + + /// Spawn terminal with custom command + pub async fn spawn_terminal_with_command( + &self, + command: String, + working_directory: Option, + terminal_type: Option, + ) -> Result<(), String> { + let request = TerminalSpawnRequest { + session_id: uuid::Uuid::new_v4().to_string(), + terminal_type, + command: Some(command), + working_directory, + environment: None, + }; + + self.spawn_terminal(request).await + } +} + +// Commands for Tauri +#[tauri::command] +pub async fn spawn_terminal_for_session( + session_id: String, + terminal_type: Option, + state: tauri::State<'_, crate::state::AppState>, +) -> Result<(), String> { + let spawn_service = &state.terminal_spawn_service; + spawn_service + .spawn_terminal_for_session(session_id, terminal_type) + .await +} + +#[tauri::command] +pub async fn spawn_terminal_with_command( + command: String, + working_directory: Option, + terminal_type: Option, + state: tauri::State<'_, crate::state::AppState>, +) -> Result<(), String> { + let spawn_service = &state.terminal_spawn_service; + spawn_service + .spawn_terminal_with_command(command, working_directory, terminal_type) + .await +} + +#[tauri::command] +pub async fn spawn_custom_terminal( + request: TerminalSpawnRequest, + state: tauri::State<'_, crate::state::AppState>, +) -> Result<(), String> { + let spawn_service = &state.terminal_spawn_service; + spawn_service.spawn_terminal(request).await +} diff --git a/tauri/src-tauri/src/tray_menu.rs b/tauri/src-tauri/src/tray_menu.rs new file mode 100644 index 00000000..1e00fd09 --- /dev/null +++ b/tauri/src-tauri/src/tray_menu.rs @@ -0,0 +1,271 @@ +use tauri::menu::{Menu, MenuBuilder, MenuItemBuilder, SubmenuBuilder}; +use tauri::{AppHandle, Manager}; + +use crate::session_monitor::SessionInfo; + +pub struct TrayMenuManager; + +impl TrayMenuManager { + pub fn create_menu(app: &AppHandle) -> Result, tauri::Error> { + Self::create_menu_with_state(app, false, 4020, 0, None) + } + + pub fn create_menu_with_state( + app: &AppHandle, + server_running: bool, + port: u16, + session_count: usize, + access_mode: Option, + ) -> Result, tauri::Error> { + Self::create_menu_with_sessions(app, server_running, port, session_count, access_mode, None) + } + + pub fn create_menu_with_sessions( + app: &AppHandle, + server_running: bool, + port: u16, + session_count: usize, + access_mode: Option, + sessions: Option>, + ) -> Result, tauri::Error> { + // Server status + let status_text = if server_running { + format!("Server running on port {}", port) + } else { + "Server stopped".to_string() + }; + let server_status = MenuItemBuilder::new(&status_text) + .id("server_status") + .enabled(false) + .build(app)?; + + // Network info (if in network mode) + let network_info = if server_running && access_mode.as_deref() == Some("network") { + if let Some(ip) = crate::network_utils::NetworkUtils::get_local_ip_address() { + Some( + MenuItemBuilder::new(&format!("Local IP: {}", ip)) + .id("network_info") + .enabled(false) + .build(app)?, + ) + } else { + None + } + } else { + None + }; + + // Dashboard access + let dashboard = MenuItemBuilder::new("Open Dashboard") + .id("dashboard") + .build(app)?; + + // Session info header + let session_text = match session_count { + 0 => "0 active sessions".to_string(), + 1 => "1 active session".to_string(), + _ => format!("{} active sessions", session_count), + }; + let sessions_info = MenuItemBuilder::new(&session_text) + .id("sessions_info") + .enabled(false) + .build(app)?; + + // Individual session items (if provided) + let mut session_items = Vec::new(); + if let Some(sessions_list) = sessions { + // Show up to 5 most recent active sessions + let active_sessions: Vec<_> = sessions_list + .iter() + .filter(|s| s.is_active) + .take(5) + .collect(); + + for session in active_sessions { + // Use session name for display + let dir_name = &session.name; + + // Truncate long names + let display_name = if dir_name.len() > 30 { + format!("{}...{}", &dir_name[..15], &dir_name[dir_name.len()-10..]) + } else { + dir_name.to_string() + }; + + let session_text = format!(" • {} (PID: {})", display_name, session.pid); + let session_item = MenuItemBuilder::new(&session_text) + .id(&format!("session_{}", session.id)) + .build(app)?; + + session_items.push(session_item); + } + + // Add ellipsis if there are more active sessions + if sessions_list.iter().filter(|s| s.is_active).count() > 5 { + let more_item = MenuItemBuilder::new(" • ...") + .id("sessions_more") + .enabled(false) + .build(app)?; + session_items.push(more_item); + } + } + + // Help submenu + let show_tutorial = MenuItemBuilder::new("Show Tutorial") + .id("show_tutorial") + .build(app)?; + + let website = MenuItemBuilder::new("Website").id("website").build(app)?; + + let report_issue = MenuItemBuilder::new("Report Issue") + .id("report_issue") + .build(app)?; + + let check_updates = MenuItemBuilder::new("Check for Updates...") + .id("check_updates") + .build(app)?; + + // Version info (disabled menu item) - read from Cargo.toml + let version = env!("CARGO_PKG_VERSION"); + let version_text = format!("Version {}", version); + let version_info = MenuItemBuilder::new(&version_text) + .id("version_info") + .enabled(false) + .build(app)?; + + let about = MenuItemBuilder::new("About VibeTunnel") + .id("about") + .build(app)?; + + let help_menu = SubmenuBuilder::new(app, "Help") + .item(&show_tutorial) + .separator() + .item(&website) + .item(&report_issue) + .separator() + .item(&check_updates) + .separator() + .item(&version_info) + .separator() + .item(&about) + .build()?; + + // Settings + let settings = MenuItemBuilder::new("Settings...") + .id("settings") + .build(app)?; + + // Quit + let quit = MenuItemBuilder::new("Quit").id("quit").build(app)?; + + // Build the complete menu - matching Mac app exactly + let mut menu_builder = MenuBuilder::new(app).item(&server_status); + + // Add network info if available + if let Some(network_item) = network_info { + menu_builder = menu_builder.item(&network_item); + } + + // Build menu with sessions + let mut menu_builder = menu_builder + .item(&dashboard) + .separator() + .item(&sessions_info); + + // Add individual session items + for session_item in session_items { + menu_builder = menu_builder.item(&session_item); + } + + let menu = menu_builder + .separator() + .item(&help_menu) + .item(&settings) + .separator() + .item(&quit) + .build()?; + + Ok(menu) + } + + pub async fn update_server_status(app: &AppHandle, port: u16, running: bool) { + if let Some(tray) = app.tray_by_id("main") { + // Get current session count and list from state + let state = app.state::(); + let terminals = state.terminal_manager.list_sessions().await; + let session_count = terminals.len(); + + // Get monitored sessions for detailed info + let sessions = state.session_monitor.get_sessions().await; + + // Get access mode from settings + let access_mode = if running { + if let Ok(settings) = crate::settings::Settings::load() { + Some(settings.dashboard.access_mode) + } else { + None + } + } else { + None + }; + + // Rebuild menu with new state and sessions + if let Ok(menu) = + Self::create_menu_with_sessions(app, running, port, session_count, access_mode, Some(sessions)) + { + if let Err(e) = tray.set_menu(Some(menu)) { + tracing::error!("Failed to update tray menu: {}", e); + } + } + } + } + + pub async fn update_session_count(app: &AppHandle, count: usize) { + if let Some(tray) = app.tray_by_id("main") { + // Get current server status from state + let state = app.state::(); + let server_guard = state.http_server.read().await; + let (running, port) = if let Some(server) = server_guard.as_ref() { + (true, server.port()) + } else { + (false, 4020) + }; + drop(server_guard); + + // Get monitored sessions for detailed info + let sessions = state.session_monitor.get_sessions().await; + + // Get access mode from settings + let access_mode = if running { + if let Ok(settings) = crate::settings::Settings::load() { + Some(settings.dashboard.access_mode) + } else { + None + } + } else { + None + }; + + // Rebuild menu with new state and sessions + if let Ok(menu) = Self::create_menu_with_sessions(app, running, port, count, access_mode, Some(sessions)) { + if let Err(e) = tray.set_menu(Some(menu)) { + tracing::error!("Failed to update tray menu: {}", e); + } + } + } + } + + pub async fn update_access_mode(_app: &AppHandle, mode: &str) { + // Update checkmarks in access mode menu + let _modes = vec![ + ("access_localhost", mode == "localhost"), + ("access_network", mode == "network"), + ("access_ngrok", mode == "ngrok"), + ]; + + // Note: In Tauri v2, we need to rebuild the menu to update checkmarks + tracing::debug!("Access mode updated to: {}", mode); + + // TODO: Implement menu rebuilding for dynamic updates + } +} diff --git a/tauri/src-tauri/src/tty_forward.rs b/tauri/src-tauri/src/tty_forward.rs new file mode 100644 index 00000000..faef1f99 --- /dev/null +++ b/tauri/src-tauri/src/tty_forward.rs @@ -0,0 +1,381 @@ +use bytes::Bytes; +use portable_pty::{native_pty_system, CommandBuilder, PtySize}; +use std::collections::HashMap; +use std::io::{Read, Write}; +use std::sync::Arc; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::{mpsc, oneshot, RwLock}; +use tracing::{error, info}; +use uuid::Uuid; + +/// Represents a forwarded TTY session +pub struct ForwardedSession { + pub id: String, + pub local_port: u16, + pub remote_host: String, + pub remote_port: u16, + pub connected: bool, + pub client_count: usize, +} + +/// Manages TTY forwarding sessions +pub struct TTYForwardManager { + sessions: Arc>>, + listeners: Arc>>>, +} + +impl TTYForwardManager { + pub fn new() -> Self { + Self { + sessions: Arc::new(RwLock::new(HashMap::new())), + listeners: Arc::new(RwLock::new(HashMap::new())), + } + } + + /// Start a TTY forwarding session + pub async fn start_forward( + &self, + local_port: u16, + remote_host: String, + remote_port: u16, + shell: Option, + ) -> Result { + let id = Uuid::new_v4().to_string(); + + // Create TCP listener + let listener = TcpListener::bind(format!("127.0.0.1:{}", local_port)) + .await + .map_err(|e| format!("Failed to bind to port {}: {}", local_port, e))?; + + let actual_port = listener + .local_addr() + .map_err(|e| format!("Failed to get local address: {}", e))? + .port(); + + // Create session + let session = ForwardedSession { + id: id.clone(), + local_port: actual_port, + remote_host: remote_host.clone(), + remote_port, + connected: false, + client_count: 0, + }; + + // Store session + self.sessions.write().await.insert(id.clone(), session); + + // Create shutdown channel + let (shutdown_tx, shutdown_rx) = oneshot::channel(); + self.listeners.write().await.insert(id.clone(), shutdown_tx); + + // Start listening for connections + let sessions = self.sessions.clone(); + let session_id = id.clone(); + let shell = shell.unwrap_or_else(|| { + std::env::var("SHELL").unwrap_or_else(|_| { + if cfg!(target_os = "windows") { + "cmd.exe".to_string() + } else { + "/bin/bash".to_string() + } + }) + }); + + tokio::spawn(async move { + Self::accept_connections( + listener, + sessions, + session_id, + remote_host, + remote_port, + shell, + shutdown_rx, + ) + .await; + }); + + info!("Started TTY forward on port {} (ID: {})", actual_port, id); + Ok(id) + } + + /// Accept incoming connections and forward them + async fn accept_connections( + listener: TcpListener, + sessions: Arc>>, + session_id: String, + _remote_host: String, + _remote_port: u16, + shell: String, + mut shutdown_rx: oneshot::Receiver<()>, + ) { + loop { + tokio::select! { + accept_result = listener.accept() => { + match accept_result { + Ok((stream, addr)) => { + info!("New TTY forward connection from {}", addr); + + // Update client count + if let Some(session) = sessions.write().await.get_mut(&session_id) { + session.client_count += 1; + session.connected = true; + } + + // Handle the connection + let sessions_clone = sessions.clone(); + let session_id_clone = session_id.clone(); + let shell_clone = shell.clone(); + + tokio::spawn(async move { + if let Err(e) = Self::handle_client( + stream, + sessions_clone.clone(), + session_id_clone.clone(), + shell_clone, + ).await { + error!("Error handling TTY forward client: {}", e); + } + + // Decrease client count + if let Some(session) = sessions_clone.write().await.get_mut(&session_id_clone) { + session.client_count = session.client_count.saturating_sub(1); + if session.client_count == 0 { + session.connected = false; + } + } + }); + } + Err(e) => { + error!("Failed to accept connection: {}", e); + } + } + } + _ = &mut shutdown_rx => { + info!("Shutting down TTY forward listener for session {}", session_id); + break; + } + } + } + } + + /// Handle a single client connection + async fn handle_client( + stream: TcpStream, + _sessions: Arc>>, + _session_id: String, + shell: String, + ) -> Result<(), String> { + // Set up PTY + let pty_system = native_pty_system(); + let pty_pair = pty_system + .openpty(PtySize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }) + .map_err(|e| format!("Failed to open PTY: {}", e))?; + + // Spawn shell + let cmd = CommandBuilder::new(&shell); + let child = pty_pair + .slave + .spawn_command(cmd) + .map_err(|e| format!("Failed to spawn shell: {}", e))?; + + // Get reader and writer + let mut reader = pty_pair + .master + .try_clone_reader() + .map_err(|e| format!("Failed to clone reader: {}", e))?; + + let mut writer = pty_pair + .master + .take_writer() + .map_err(|e| format!("Failed to take writer: {}", e))?; + + // Create channels for bidirectional communication + let (tx_to_pty, mut rx_from_tcp) = mpsc::unbounded_channel::(); + let (tx_to_tcp, mut rx_from_pty) = mpsc::unbounded_channel::(); + + // Split the TCP stream + let (mut tcp_reader, mut tcp_writer) = stream.into_split(); + + // Task 1: Read from TCP and write to PTY + let tcp_to_pty = tokio::spawn(async move { + let mut tcp_buf = [0u8; 4096]; + loop { + match tcp_reader.read(&mut tcp_buf).await { + Ok(0) => break, // Connection closed + Ok(n) => { + let data = Bytes::copy_from_slice(&tcp_buf[..n]); + if tx_to_pty.send(data).is_err() { + break; + } + } + Err(e) => { + error!("Error reading from TCP: {}", e); + break; + } + } + } + }); + + // Task 2: Read from PTY and write to TCP + let pty_to_tcp = tokio::spawn(async move { + while let Some(data) = rx_from_pty.recv().await { + if tcp_writer.write_all(&data).await.is_err() { + break; + } + if tcp_writer.flush().await.is_err() { + break; + } + } + }); + + // Task 3: PTY reader thread + let reader_handle = std::thread::spawn(move || { + let mut buffer = [0u8; 4096]; + loop { + match reader.read(&mut buffer) { + Ok(0) => break, + Ok(n) => { + let data = Bytes::copy_from_slice(&buffer[..n]); + // Since we're in a thread, we can't use blocking_send on unbounded channel + // We'll use a different approach + if tx_to_tcp.send(data).is_err() { + break; + } + } + Err(e) => { + error!("Error reading from PTY: {}", e); + break; + } + } + } + }); + + // Task 4: PTY writer thread + let writer_handle = std::thread::spawn(move || { + // Create a blocking runtime for the thread + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + rt.block_on(async { + while let Some(data) = rx_from_tcp.recv().await { + if writer.write_all(&data).is_err() { + break; + } + if writer.flush().is_err() { + break; + } + } + }); + }); + + // Wait for any task to complete + tokio::select! { + _ = tcp_to_pty => {}, + _ = pty_to_tcp => {}, + } + + // Clean up + drop(child); + let _ = reader_handle.join(); + let _ = writer_handle.join(); + + Ok(()) + } + + /// Stop a TTY forwarding session + pub async fn stop_forward(&self, id: &str) -> Result<(), String> { + // Remove session + self.sessions.write().await.remove(id); + + // Send shutdown signal + if let Some(shutdown_tx) = self.listeners.write().await.remove(id) { + let _ = shutdown_tx.send(()); + } + + info!("Stopped TTY forward session: {}", id); + Ok(()) + } + + /// List all active forwarding sessions + pub async fn list_forwards(&self) -> Vec { + self.sessions + .read() + .await + .values() + .map(|s| ForwardedSession { + id: s.id.clone(), + local_port: s.local_port, + remote_host: s.remote_host.clone(), + remote_port: s.remote_port, + connected: s.connected, + client_count: s.client_count, + }) + .collect() + } + + /// Get a specific forwarding session + pub async fn get_forward(&self, id: &str) -> Option { + self.sessions + .read() + .await + .get(id) + .map(|s| ForwardedSession { + id: s.id.clone(), + local_port: s.local_port, + remote_host: s.remote_host.clone(), + remote_port: s.remote_port, + connected: s.connected, + client_count: s.client_count, + }) + } +} + +/// HTTP endpoint handler for terminal spawn requests +pub async fn handle_terminal_spawn(port: u16, _shell: Option) -> Result<(), String> { + // Listen for HTTP requests on the specified port + let listener = TcpListener::bind(format!("127.0.0.1:{}", port)) + .await + .map_err(|e| format!("Failed to bind spawn listener: {}", e))?; + + info!("Terminal spawn service listening on port {}", port); + + loop { + let (stream, addr) = listener + .accept() + .await + .map_err(|e| format!("Failed to accept spawn connection: {}", e))?; + + info!("Terminal spawn request from {}", addr); + + // Handle the spawn request + tokio::spawn(async move { + if let Err(e) = handle_spawn_request(stream, None).await { + error!("Error handling spawn request: {}", e); + } + }); + } +} + +/// Handle a single terminal spawn request +async fn handle_spawn_request(mut stream: TcpStream, _shell: Option) -> Result<(), String> { + // Simple HTTP response + let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nTerminal spawned\r\n"; + stream + .write_all(response) + .await + .map_err(|e| format!("Failed to write response: {}", e))?; + + // TODO: Implement actual terminal spawning logic + // This would integrate with the system's terminal emulator + + Ok(()) +} diff --git a/tauri/src-tauri/src/unix_socket_server.rs b/tauri/src-tauri/src/unix_socket_server.rs new file mode 100644 index 00000000..6191dfb2 --- /dev/null +++ b/tauri/src-tauri/src/unix_socket_server.rs @@ -0,0 +1,157 @@ +use serde::{Deserialize, Serialize}; +use std::io::Read; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc; +use tracing::{error, info}; + +const SOCKET_PATH: &str = "/tmp/vibetunnel-terminal.sock"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SpawnRequest { + pub tty_fwd_path: Option, + pub working_dir: String, + pub session_id: String, + pub command: String, + pub terminal: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SpawnResponse { + pub success: bool, + pub error: Option, + pub session_id: Option, +} + +pub struct UnixSocketServer { + request_tx: mpsc::Sender, + request_rx: Arc>>>, + terminal_spawn_service: Arc, +} + +impl UnixSocketServer { + pub fn new( + terminal_spawn_service: Arc, + ) -> Self { + let (tx, rx) = mpsc::channel::(100); + + Self { + request_tx: tx, + request_rx: Arc::new(Mutex::new(Some(rx))), + terminal_spawn_service, + } + } + + pub fn start(&self) -> Result<(), Box> { + // Spawn the handler for requests now that runtime is available + let rx = self.request_rx.lock().unwrap().take(); + if let Some(mut rx) = rx { + let spawn_service = self.terminal_spawn_service.clone(); + tokio::spawn(async move { + while let Some(request) = rx.recv().await { + let service = spawn_service.clone(); + tokio::spawn(async move { + if let Err(e) = handle_spawn_request(request, service).await { + error!("Failed to handle spawn request: {}", e); + } + }); + } + }); + } + + // Remove existing socket if it exists + if Path::new(SOCKET_PATH).exists() { + std::fs::remove_file(SOCKET_PATH)?; + } + + let listener = UnixListener::bind(SOCKET_PATH)?; + info!("Terminal spawn service listening on {}", SOCKET_PATH); + + let tx = self.request_tx.clone(); + + // Spawn thread to handle Unix socket connections + std::thread::spawn(move || { + for stream in listener.incoming() { + match stream { + Ok(stream) => { + let tx = tx.clone(); + std::thread::spawn(move || { + if let Err(e) = handle_connection(stream, tx) { + error!("Failed to handle connection: {}", e); + } + }); + } + Err(e) => { + error!("Failed to accept connection: {}", e); + } + } + } + }); + + Ok(()) + } + + pub fn stop(&self) -> Result<(), Box> { + info!("Stopping terminal spawn service"); + if Path::new(SOCKET_PATH).exists() { + std::fs::remove_file(SOCKET_PATH)?; + } + Ok(()) + } +} + +fn handle_connection( + mut stream: UnixStream, + tx: mpsc::Sender, +) -> Result<(), Box> { + let mut buffer = Vec::new(); + stream.read_to_end(&mut buffer)?; + + let request: SpawnRequest = serde_json::from_slice(&buffer)?; + info!("Received spawn request for session {}", request.session_id); + + // Send request to async handler + if let Err(e) = tx.blocking_send(request.clone()) { + let response = SpawnResponse { + success: false, + error: Some(format!("Failed to queue request: {}", e)), + session_id: None, + }; + let response_data = serde_json::to_vec(&response)?; + std::io::Write::write_all(&mut stream, &response_data)?; + return Ok(()); + } + + // Send success response + let response = SpawnResponse { + success: true, + error: None, + session_id: Some(request.session_id), + }; + let response_data = serde_json::to_vec(&response)?; + std::io::Write::write_all(&mut stream, &response_data)?; + + Ok(()) +} + +async fn handle_spawn_request( + request: SpawnRequest, + terminal_spawn_service: Arc, +) -> Result<(), String> { + let spawn_request = crate::terminal_spawn_service::TerminalSpawnRequest { + session_id: request.session_id, + terminal_type: request.terminal, + command: Some(request.command), + working_directory: Some(request.working_dir), + environment: None, + }; + + terminal_spawn_service.spawn_terminal(spawn_request).await +} + +impl Drop for UnixSocketServer { + fn drop(&mut self) { + let _ = self.stop(); + } +} diff --git a/tauri/src-tauri/src/updater.rs b/tauri/src-tauri/src/updater.rs new file mode 100644 index 00000000..e3b48399 --- /dev/null +++ b/tauri/src-tauri/src/updater.rs @@ -0,0 +1,542 @@ +use chrono::{DateTime, TimeZone, Utc}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tauri::{AppHandle, Emitter}; +use tauri_plugin_updater::UpdaterExt; +use tokio::sync::RwLock; + +/// Update channel type +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum UpdateChannel { + Stable, + Beta, + Nightly, + Custom, +} + +impl UpdateChannel { + pub fn as_str(&self) -> &str { + match self { + UpdateChannel::Stable => "stable", + UpdateChannel::Beta => "beta", + UpdateChannel::Nightly => "nightly", + UpdateChannel::Custom => "custom", + } + } + + pub fn from_str(s: &str) -> Self { + match s.to_lowercase().as_str() { + "stable" => UpdateChannel::Stable, + "beta" => UpdateChannel::Beta, + "nightly" => UpdateChannel::Nightly, + _ => UpdateChannel::Custom, + } + } +} + +/// Update status +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum UpdateStatus { + Idle, + Checking, + Available, + Downloading, + Ready, + Installing, + Error, + NoUpdate, +} + +/// Update information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateInfo { + pub version: String, + pub notes: String, + pub pub_date: Option>, + pub download_size: Option, + pub signature: Option, + pub download_url: String, + pub channel: UpdateChannel, +} + +/// Update progress +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateProgress { + pub downloaded: u64, + pub total: u64, + pub percentage: f32, + pub bytes_per_second: Option, + pub eta_seconds: Option, +} + +/// Update settings +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdaterSettings { + pub channel: UpdateChannel, + pub check_on_startup: bool, + pub check_interval_hours: u32, + pub auto_download: bool, + pub auto_install: bool, + pub show_release_notes: bool, + pub include_pre_releases: bool, + pub custom_endpoint: Option, + pub proxy: Option, +} + +impl Default for UpdaterSettings { + fn default() -> Self { + Self { + channel: UpdateChannel::Stable, + check_on_startup: true, + check_interval_hours: 24, + auto_download: false, + auto_install: false, + show_release_notes: true, + include_pre_releases: false, + custom_endpoint: None, + proxy: None, + } + } +} + +/// Update manager state +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateState { + pub status: UpdateStatus, + pub current_version: String, + pub available_update: Option, + pub progress: Option, + pub last_check: Option>, + pub last_error: Option, + pub update_history: Vec, +} + +/// Update history entry +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateHistoryEntry { + pub version: String, + pub from_version: String, + pub channel: UpdateChannel, + pub installed_at: DateTime, + pub success: bool, + pub notes: Option, +} + +/// Update manager +pub struct UpdateManager { + app_handle: Arc>>, + settings: Arc>, + state: Arc>, + notification_manager: Option>, +} + +impl UpdateManager { + /// Create a new update manager + pub fn new(current_version: String) -> Self { + Self { + app_handle: Arc::new(RwLock::new(None)), + settings: Arc::new(RwLock::new(UpdaterSettings::default())), + state: Arc::new(RwLock::new(UpdateState { + status: UpdateStatus::Idle, + current_version, + available_update: None, + progress: None, + last_check: None, + last_error: None, + update_history: Vec::new(), + })), + notification_manager: None, + } + } + + /// Set the app handle + pub async fn set_app_handle(&self, app_handle: AppHandle) { + *self.app_handle.write().await = Some(app_handle); + } + + /// Set the notification manager + pub fn set_notification_manager( + &mut self, + notification_manager: Arc, + ) { + self.notification_manager = Some(notification_manager); + } + + /// Load settings from configuration + pub async fn load_settings(&self) -> Result<(), String> { + if let Ok(settings) = crate::settings::Settings::load() { + if let Some(update_settings) = settings.updates { + let mut updater_settings = self.settings.write().await; + updater_settings.channel = UpdateChannel::from_str(&update_settings.channel); + updater_settings.check_on_startup = true; + updater_settings.check_interval_hours = + match update_settings.check_frequency.as_str() { + "daily" => 24, + "weekly" => 168, + "monthly" => 720, + _ => 24, + }; + updater_settings.auto_download = update_settings.auto_download; + updater_settings.auto_install = update_settings.auto_install; + updater_settings.show_release_notes = update_settings.show_release_notes; + updater_settings.include_pre_releases = update_settings.include_pre_releases; + } + } + Ok(()) + } + + /// Get update settings + pub async fn get_settings(&self) -> UpdaterSettings { + self.settings.read().await.clone() + } + + /// Update settings + pub async fn update_settings(&self, settings: UpdaterSettings) -> Result<(), String> { + *self.settings.write().await = settings.clone(); + + // Save to persistent settings + if let Ok(mut app_settings) = crate::settings::Settings::load() { + app_settings.updates = Some(crate::settings::UpdateSettings { + channel: settings.channel.as_str().to_string(), + check_frequency: match settings.check_interval_hours { + 1..=23 => "daily".to_string(), + 24..=167 => "daily".to_string(), + 168..=719 => "weekly".to_string(), + _ => "monthly".to_string(), + }, + auto_download: settings.auto_download, + auto_install: settings.auto_install, + show_release_notes: settings.show_release_notes, + include_pre_releases: settings.include_pre_releases, + }); + app_settings.save()?; + } + + Ok(()) + } + + /// Get current update state + pub async fn get_state(&self) -> UpdateState { + self.state.read().await.clone() + } + + /// Check for updates + pub async fn check_for_updates(&self) -> Result, String> { + // Update status + { + let mut state = self.state.write().await; + state.status = UpdateStatus::Checking; + state.last_error = None; + } + + // Emit checking event + self.emit_update_event("checking", None).await; + + let app_handle_guard = self.app_handle.read().await; + let app_handle = app_handle_guard + .as_ref() + .ok_or_else(|| "App handle not set".to_string())?; + + // Get the updater instance + let updater = app_handle.updater_builder(); + + // Configure updater based on settings + let settings = self.settings.read().await; + + // Build updater with channel-specific endpoint + let updater_result = match settings.channel { + UpdateChannel::Stable => updater.endpoints(vec![ + "https://releases.vibetunnel.com/stable/{{target}}/{{arch}}/{{current_version}}" + .parse() + .unwrap(), + ]), + UpdateChannel::Beta => updater.endpoints(vec![ + "https://releases.vibetunnel.com/beta/{{target}}/{{arch}}/{{current_version}}" + .parse() + .unwrap(), + ]), + UpdateChannel::Nightly => updater.endpoints(vec![ + "https://releases.vibetunnel.com/nightly/{{target}}/{{arch}}/{{current_version}}" + .parse() + .unwrap(), + ]), + UpdateChannel::Custom => { + if let Some(endpoint) = &settings.custom_endpoint { + match endpoint.parse() { + Ok(url) => updater.endpoints(vec![url]), + Err(_) => return Err("Invalid custom endpoint URL".to_string()), + } + } else { + return Err("Custom endpoint not configured".to_string()); + } + } + }; + + // Build and check + match updater_result { + Ok(updater_builder) => match updater_builder.build() { + Ok(updater) => { + match updater.check().await { + Ok(Some(update)) => { + let update_info = UpdateInfo { + version: update.version.clone(), + notes: update.body.clone().unwrap_or_default(), + pub_date: update.date.map(|d| { + Utc.timestamp_opt(d.unix_timestamp(), 0) + .single() + .unwrap_or(Utc::now()) + }), + download_size: None, // TODO: Get from update + signature: None, + download_url: String::new(), // Will be set by updater + channel: settings.channel, + }; + + // Update state + { + let mut state = self.state.write().await; + state.status = UpdateStatus::Available; + state.available_update = Some(update_info.clone()); + state.last_check = Some(Utc::now()); + } + + // Emit available event + self.emit_update_event("available", Some(&update_info)) + .await; + + // Show notification + if let Some(notification_manager) = &self.notification_manager { + let _ = notification_manager + .notify_update_available( + &update_info.version, + &update_info.download_url, + ) + .await; + } + + // Auto-download if enabled + if settings.auto_download { + let _ = self.download_update().await; + } + + Ok(Some(update_info)) + } + Ok(None) => { + // No update available + let mut state = self.state.write().await; + state.status = UpdateStatus::NoUpdate; + state.last_check = Some(Utc::now()); + + self.emit_update_event("no-update", None).await; + + Ok(None) + } + Err(e) => { + let error_msg = format!("Failed to check for updates: {}", e); + + let mut state = self.state.write().await; + state.status = UpdateStatus::Error; + state.last_error = Some(error_msg.clone()); + state.last_check = Some(Utc::now()); + + self.emit_update_event("error", None).await; + + Err(error_msg) + } + } + } + Err(e) => { + let error_msg = format!("Failed to build updater: {}", e); + + let mut state = self.state.write().await; + state.status = UpdateStatus::Error; + state.last_error = Some(error_msg.clone()); + + Err(error_msg) + } + }, + Err(e) => { + let error_msg = format!("Failed to configure updater endpoints: {}", e); + + let mut state = self.state.write().await; + state.status = UpdateStatus::Error; + state.last_error = Some(error_msg.clone()); + + Err(error_msg) + } + } + } + + /// Download update + pub async fn download_update(&self) -> Result<(), String> { + let update_available = { + let state = self.state.read().await; + state.available_update.is_some() + }; + + if !update_available { + return Err("No update available to download".to_string()); + } + + // Update status + { + let mut state = self.state.write().await; + state.status = UpdateStatus::Downloading; + state.progress = Some(UpdateProgress { + downloaded: 0, + total: 0, + percentage: 0.0, + bytes_per_second: None, + eta_seconds: None, + }); + } + + self.emit_update_event("downloading", None).await; + + // TODO: Implement actual download with progress tracking + // For now, simulate download completion + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + + // Update status to ready + { + let mut state = self.state.write().await; + state.status = UpdateStatus::Ready; + state.progress = None; + } + + self.emit_update_event("ready", None).await; + + // Auto-install if enabled + let settings = self.settings.read().await; + if settings.auto_install { + let _ = self.install_update().await; + } + + Ok(()) + } + + /// Install update + pub async fn install_update(&self) -> Result<(), String> { + let update_info = { + let state = self.state.read().await; + if state.status != UpdateStatus::Ready { + return Err("Update not ready for installation".to_string()); + } + state.available_update.clone() + }; + + let update_info = update_info.ok_or_else(|| "No update available".to_string())?; + + // Update status + { + let mut state = self.state.write().await; + state.status = UpdateStatus::Installing; + } + + self.emit_update_event("installing", None).await; + + // Add to history + { + let mut state = self.state.write().await; + let from_version = state.current_version.clone(); + state.update_history.push(UpdateHistoryEntry { + version: update_info.version.clone(), + from_version, + channel: update_info.channel, + installed_at: Utc::now(), + success: true, + notes: Some(update_info.notes.clone()), + }); + } + + // TODO: Implement actual installation + // For now, return success + + self.emit_update_event("installed", None).await; + + Ok(()) + } + + /// Cancel update + pub async fn cancel_update(&self) -> Result<(), String> { + let mut state = self.state.write().await; + + match state.status { + UpdateStatus::Downloading => { + // TODO: Cancel download + state.status = UpdateStatus::Available; + state.progress = None; + Ok(()) + } + _ => Err("No update in progress to cancel".to_string()), + } + } + + /// Switch update channel + pub async fn switch_channel(&self, channel: UpdateChannel) -> Result<(), String> { + let mut settings = self.settings.write().await; + settings.channel = channel; + drop(settings); + + // Save settings + self.update_settings(self.get_settings().await).await?; + + // Clear current update info when switching channels + let mut state = self.state.write().await; + state.available_update = None; + state.status = UpdateStatus::Idle; + + Ok(()) + } + + /// Get update history + pub async fn get_update_history(&self, limit: Option) -> Vec { + let state = self.state.read().await; + match limit { + Some(l) => state.update_history.iter().rev().take(l).cloned().collect(), + None => state.update_history.clone(), + } + } + + /// Start automatic update checking + pub async fn start_auto_check(self: Arc) { + let settings = self.settings.read().await; + if !settings.check_on_startup { + return; + } + + let check_interval = + std::time::Duration::from_secs(settings.check_interval_hours as u64 * 3600); + drop(settings); + + tokio::spawn(async move { + loop { + let _ = self.check_for_updates().await; + tokio::time::sleep(check_interval).await; + } + }); + } + + /// Emit update event + async fn emit_update_event(&self, event_type: &str, update_info: Option<&UpdateInfo>) { + if let Some(app_handle) = self.app_handle.read().await.as_ref() { + let event_data = serde_json::json!({ + "type": event_type, + "update": update_info, + "state": self.get_state().await, + }); + + let _ = app_handle.emit("updater:event", event_data); + } + } +} + +/// Update check result +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateCheckResult { + pub available: bool, + pub current_version: String, + pub latest_version: Option, + pub channel: UpdateChannel, + pub checked_at: DateTime, +} diff --git a/tauri/src-tauri/src/welcome.rs b/tauri/src-tauri/src/welcome.rs new file mode 100644 index 00000000..0cbe9972 --- /dev/null +++ b/tauri/src-tauri/src/welcome.rs @@ -0,0 +1,452 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use tauri::{AppHandle, Emitter, Manager}; +use tokio::sync::RwLock; + +/// Tutorial step structure +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TutorialStep { + pub id: String, + pub title: String, + pub description: String, + pub content: String, + pub action: Option, + pub completed: bool, + pub order: u32, +} + +/// Tutorial action that can be triggered +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TutorialAction { + pub action_type: String, + pub payload: HashMap, +} + +/// Welcome state tracking +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WelcomeState { + pub first_launch: bool, + pub tutorial_completed: bool, + pub tutorial_skipped: bool, + pub completed_steps: Vec, + pub last_seen_version: Option, + pub onboarding_date: Option>, +} + +impl Default for WelcomeState { + fn default() -> Self { + Self { + first_launch: true, + tutorial_completed: false, + tutorial_skipped: false, + completed_steps: Vec::new(), + last_seen_version: None, + onboarding_date: None, + } + } +} + +/// Tutorial category +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TutorialCategory { + pub id: String, + pub name: String, + pub description: String, + pub icon: String, + pub steps: Vec, +} + +/// Welcome manager +pub struct WelcomeManager { + state: Arc>, + tutorials: Arc>>, + app_handle: Arc>>, +} + +impl WelcomeManager { + /// Create a new welcome manager + pub fn new() -> Self { + Self { + state: Arc::new(RwLock::new(WelcomeState::default())), + tutorials: Arc::new(RwLock::new(Self::create_default_tutorials())), + app_handle: Arc::new(RwLock::new(None)), + } + } + + /// Set the app handle + pub async fn set_app_handle(&self, app_handle: AppHandle) { + *self.app_handle.write().await = Some(app_handle); + } + + /// Load welcome state from storage + pub async fn load_state(&self) -> Result<(), String> { + // Try to load from settings or local storage + if let Ok(settings) = crate::settings::Settings::load() { + // Check if this is first launch based on settings + let mut state = self.state.write().await; + state.first_launch = settings.general.show_welcome_on_startup.unwrap_or(true); + + // Mark first launch as false for next time + if state.first_launch { + state.onboarding_date = Some(Utc::now()); + } + } + Ok(()) + } + + /// Save welcome state + pub async fn save_state(&self) -> Result<(), String> { + let state = self.state.read().await; + + // Update settings to reflect welcome state + if let Ok(mut settings) = crate::settings::Settings::load() { + settings.general.show_welcome_on_startup = + Some(!state.tutorial_completed && !state.tutorial_skipped); + settings.save().map_err(|e| e.to_string())?; + } + + Ok(()) + } + + /// Check if should show welcome screen + pub async fn should_show_welcome(&self) -> bool { + let state = self.state.read().await; + state.first_launch && !state.tutorial_completed && !state.tutorial_skipped + } + + /// Get current welcome state + pub async fn get_state(&self) -> WelcomeState { + self.state.read().await.clone() + } + + /// Get all tutorial categories + pub async fn get_tutorials(&self) -> Vec { + self.tutorials.read().await.clone() + } + + /// Get specific tutorial category + pub async fn get_tutorial_category(&self, category_id: &str) -> Option { + self.tutorials + .read() + .await + .iter() + .find(|c| c.id == category_id) + .cloned() + } + + /// Complete a tutorial step + pub async fn complete_step(&self, step_id: &str) -> Result<(), String> { + let mut state = self.state.write().await; + + if !state.completed_steps.contains(&step_id.to_string()) { + state.completed_steps.push(step_id.to_string()); + + // Check if all steps are completed + let tutorials = self.tutorials.read().await; + let total_steps: usize = tutorials.iter().map(|c| c.steps.len()).sum(); + + if state.completed_steps.len() >= total_steps { + state.tutorial_completed = true; + } + + // Save state + drop(state); + drop(tutorials); + self.save_state().await?; + + // Emit progress event + if let Some(app_handle) = self.app_handle.read().await.as_ref() { + let _ = app_handle.emit("tutorial:step_completed", step_id); + } + } + + Ok(()) + } + + /// Skip tutorial + pub async fn skip_tutorial(&self) -> Result<(), String> { + let mut state = self.state.write().await; + state.tutorial_skipped = true; + state.first_launch = false; + drop(state); + + self.save_state().await?; + + Ok(()) + } + + /// Reset tutorial progress + pub async fn reset_tutorial(&self) -> Result<(), String> { + let mut state = self.state.write().await; + state.completed_steps.clear(); + state.tutorial_completed = false; + state.tutorial_skipped = false; + drop(state); + + self.save_state().await?; + + Ok(()) + } + + /// Show welcome window + pub async fn show_welcome_window(&self) -> Result<(), String> { + if let Some(app_handle) = self.app_handle.read().await.as_ref() { + // Check if welcome window already exists + if let Some(window) = app_handle.get_webview_window("welcome") { + window.show().map_err(|e| e.to_string())?; + window.set_focus().map_err(|e| e.to_string())?; + } else { + // Create new welcome window + tauri::WebviewWindowBuilder::new( + app_handle, + "welcome", + tauri::WebviewUrl::App("welcome.html".into()), + ) + .title("Welcome to VibeTunnel") + .inner_size(800.0, 600.0) + .center() + .resizable(false) + .build() + .map_err(|e| e.to_string())?; + } + } else { + return Err("App handle not set".to_string()); + } + + Ok(()) + } + + /// Create default tutorial content + fn create_default_tutorials() -> Vec { + vec![ + TutorialCategory { + id: "getting_started".to_string(), + name: "Getting Started".to_string(), + description: "Learn the basics of VibeTunnel".to_string(), + icon: "🚀".to_string(), + steps: vec![ + TutorialStep { + id: "welcome".to_string(), + title: "Welcome to VibeTunnel".to_string(), + description: "Your powerful terminal session manager".to_string(), + content: r"VibeTunnel lets you create, manage, and share terminal sessions with ease. + +Key features: +• Create multiple terminal sessions +• Share sessions via web interface +• Record terminal sessions +• Secure remote access with ngrok +• Cross-platform support".to_string(), + action: None, + completed: false, + order: 1, + }, + TutorialStep { + id: "create_session".to_string(), + title: "Creating Your First Session".to_string(), + description: "Learn how to create a terminal session".to_string(), + content: r#"To create a new terminal session: + +1. Click the "New Terminal" button +2. Choose your preferred shell +3. Set the session name (optional) +4. Click "Create" + +Your session will appear in the sidebar."#.to_string(), + action: Some(TutorialAction { + action_type: "create_terminal".to_string(), + payload: HashMap::new(), + }), + completed: false, + order: 2, + }, + TutorialStep { + id: "start_server".to_string(), + title: "Starting the Web Server".to_string(), + description: "Share your sessions via web interface".to_string(), + content: r#"The web server lets you access your terminals from any browser: + +1. Click "Start Server" in the toolbar +2. Choose your access mode: + • Localhost - Access only from this machine + • Network - Access from your local network + • Ngrok - Access from anywhere (requires auth token) +3. Share the URL with others or access it yourself"#.to_string(), + action: Some(TutorialAction { + action_type: "start_server".to_string(), + payload: HashMap::new(), + }), + completed: false, + order: 3, + }, + ], + }, + TutorialCategory { + id: "advanced_features".to_string(), + name: "Advanced Features".to_string(), + description: "Discover powerful features".to_string(), + icon: "⚡".to_string(), + steps: vec![ + TutorialStep { + id: "recording".to_string(), + title: "Recording Sessions".to_string(), + description: "Record and replay terminal sessions".to_string(), + content: r#"Record your terminal sessions in Asciinema format: + +1. Right-click on a session +2. Select "Start Recording" +3. Perform your terminal tasks +4. Stop recording when done +5. Save or share the recording + +Recordings can be played back later or shared with others."#.to_string(), + action: None, + completed: false, + order: 1, + }, + TutorialStep { + id: "port_forwarding".to_string(), + title: "TTY Forwarding".to_string(), + description: "Forward terminal sessions over TCP".to_string(), + content: r"TTY forwarding allows remote terminal access: + +1. Go to Settings > Advanced +2. Enable TTY Forwarding +3. Configure the local port +4. Connect using: telnet localhost + +This is useful for accessing terminals from other applications.".to_string(), + action: None, + completed: false, + order: 2, + }, + TutorialStep { + id: "cli_tool".to_string(), + title: "Command Line Interface".to_string(), + description: "Use VibeTunnel from the terminal".to_string(), + content: r#"Install the CLI tool for quick access: + +1. Go to Settings > Advanced +2. Click "Install CLI Tool" +3. Open a new terminal +4. Run: vt --help + +Common commands: +• vt new - Create new session +• vt list - List sessions +• vt attach - Attach to session"#.to_string(), + action: Some(TutorialAction { + action_type: "install_cli".to_string(), + payload: HashMap::new(), + }), + completed: false, + order: 3, + }, + ], + }, + TutorialCategory { + id: "security".to_string(), + name: "Security & Settings".to_string(), + description: "Configure security and preferences".to_string(), + icon: "🔒".to_string(), + steps: vec![ + TutorialStep { + id: "password_protection".to_string(), + title: "Password Protection".to_string(), + description: "Secure your web interface".to_string(), + content: r#"Protect your sessions with a password: + +1. Go to Settings > Dashboard +2. Enable "Password Protection" +3. Set a strong password +4. Save settings + +Anyone accessing the web interface will need this password."#.to_string(), + action: Some(TutorialAction { + action_type: "open_settings".to_string(), + payload: HashMap::new(), + }), + completed: false, + order: 1, + }, + TutorialStep { + id: "auto_launch".to_string(), + title: "Auto Launch".to_string(), + description: "Start VibeTunnel with your system".to_string(), + content: r#"Configure VibeTunnel to start automatically: + +1. Go to Settings > General +2. Enable "Launch at startup" +3. Choose startup behavior: + • Start minimized + • Show dock icon + • Auto-start server + +VibeTunnel will be ready whenever you need it."#.to_string(), + action: None, + completed: false, + order: 2, + }, + ], + }, + ] + } + + /// Get tutorial progress + pub async fn get_progress(&self) -> TutorialProgress { + let state = self.state.read().await; + let tutorials = self.tutorials.read().await; + + let total_steps: usize = tutorials.iter().map(|c| c.steps.len()).sum(); + + let completed_steps = state.completed_steps.len(); + let percentage = if total_steps > 0 { + (completed_steps as f32 / total_steps as f32 * 100.0) as u32 + } else { + 0 + }; + + TutorialProgress { + total_steps, + completed_steps, + percentage, + categories: tutorials + .iter() + .map(|category| { + let category_completed = category + .steps + .iter() + .filter(|s| state.completed_steps.contains(&s.id)) + .count(); + + CategoryProgress { + category_id: category.id.clone(), + category_name: category.name.clone(), + total_steps: category.steps.len(), + completed_steps: category_completed, + } + }) + .collect(), + } + } +} + +/// Tutorial progress tracking +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TutorialProgress { + pub total_steps: usize, + pub completed_steps: usize, + pub percentage: u32, + pub categories: Vec, +} + +/// Category progress +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CategoryProgress { + pub category_id: String, + pub category_name: String, + pub total_steps: usize, + pub completed_steps: usize, +} diff --git a/tauri/src-tauri/tauri.conf.json b/tauri/src-tauri/tauri.conf.json new file mode 100644 index 00000000..1c1f9bb9 --- /dev/null +++ b/tauri/src-tauri/tauri.conf.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://schema.tauri.app/config/2.0.0", + "productName": "VibeTunnel", + "identifier": "com.vibetunnel.app", + "build": { + "beforeDevCommand": "cd ../web && npm run build", + "beforeBuildCommand": "cd ../web && npm run build", + "frontendDist": "../public" + }, + "app": { + "windows": [], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "resources": [ + "icons/menu-bar-icon.png", + "icons/menu-bar-icon@2x.png", + "icons/tray-icon.png", + "icons/tray-icon@2x.png", + "../web/native/vibetunnel*", + "../web/native/*.node", + "../web/native/spawn-helper", + "../web/public/**/*" + ], + "macOS": { + "frameworks": [], + "minimumSystemVersion": "10.15", + "exceptionDomain": "localhost", + "signingIdentity": null, + "providerShortName": null, + "entitlements": "entitlements.plist" + }, + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + }, + "linux": { + "deb": { + "depends": [] + }, + "appimage": { + "bundleMediaFramework": true + } + } + } +} \ No newline at end of file diff --git a/web/package.json b/web/package.json index e8c5fdd3..45bcfdd5 100644 --- a/web/package.json +++ b/web/package.json @@ -18,6 +18,16 @@ "format:check": "prettier --check 'src/**/*.{ts,tsx,js,jsx,json,css,md}'" }, "dependencies": { + "@codemirror/commands": "^6.6.2", + "@codemirror/lang-css": "^6.2.1", + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-markdown": "^6.2.5", + "@codemirror/lang-python": "^6.1.6", + "@codemirror/state": "^6.4.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.28.0", "@homebridge/node-pty-prebuilt-multiarch": "^0.12.0", "@xterm/headless": "^5.5.0", "chalk": "^4.1.2", @@ -26,16 +36,6 @@ "mime-types": "^3.0.1", "postject": "^1.0.0-alpha.6", "signal-exit": "^4.1.0", - "@codemirror/view": "^6.28.0", - "@codemirror/state": "^6.4.1", - "@codemirror/theme-one-dark": "^6.1.2", - "@codemirror/lang-javascript": "^6.2.2", - "@codemirror/lang-html": "^6.4.9", - "@codemirror/lang-css": "^6.2.1", - "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-markdown": "^6.2.5", - "@codemirror/lang-python": "^6.1.6", - "@codemirror/commands": "^6.6.2", "ws": "^8.18.2", "monaco-editor": "^0.52.2" }, diff --git a/web/src/client/app.ts b/web/src/client/app.ts index 1ed063b0..9bcf2325 100644 --- a/web/src/client/app.ts +++ b/web/src/client/app.ts @@ -492,7 +492,7 @@ export class VibeTunnelApp extends LitElement { ` ) : html` -
+
+ + diff --git a/web/src/client/components/app-header.ts b/web/src/client/components/app-header.ts index 96357715..4b4f732a 100644 --- a/web/src/client/components/app-header.ts +++ b/web/src/client/components/app-header.ts @@ -145,7 +145,14 @@ export class AppHeader extends LitElement { @click=${this.handleOpenFileBrowser} title="Browse Files" > - Browse + + + + + Browse +
-
+
${this.killing ? html`
diff --git a/web/src/client/components/session-list.ts b/web/src/client/components/session-list.ts index f4acdd24..fe446df8 100644 --- a/web/src/client/components/session-list.ts +++ b/web/src/client/components/session-list.ts @@ -62,32 +62,9 @@ export class SessionList extends LitElement { } private async handleSessionKilled(e: CustomEvent) { - const { sessionId, session } = e.detail; + const { sessionId } = e.detail; logger.debug(`session ${sessionId} killed, updating session list`); - // Check if this is a cleanup action (exited session) vs kill action (running session) - const isCleanup = session?.status === 'exited'; - - if (isCleanup) { - // Find the session card element for cleanup animation - const sessionCards = this.querySelectorAll('session-card'); - let targetCard: HTMLElement | null = null; - - sessionCards.forEach((card) => { - const sessionCard = card as HTMLElement & { session?: { id: string } }; - if (sessionCard.session?.id === sessionId) { - targetCard = sessionCard; - } - }); - - // Apply black hole animation to the card - if (targetCard) { - (targetCard as HTMLElement).classList.add('black-hole-collapsing'); - // Wait for animation to complete - await new Promise((resolve) => setTimeout(resolve, 300)); - } - } - // Remove the session from the local state this.sessions = this.sessions.filter((session) => session.id !== sessionId); @@ -242,7 +219,7 @@ export class SessionList extends LitElement {
` : html` -
+
${repeat( filteredSessions, (session) => session.id, diff --git a/web/src/client/components/session-view.ts b/web/src/client/components/session-view.ts index f80bc314..41cead51 100644 --- a/web/src/client/components/session-view.ts +++ b/web/src/client/components/session-view.ts @@ -17,7 +17,6 @@ import { LitElement, PropertyValues, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import type { Session } from './session-list.js'; import './terminal.js'; -import './file-browser-fab.js'; import './file-browser.js'; import type { Terminal } from './terminal.js'; import { CastConverter } from '../utils/cast-converter.js'; @@ -1080,6 +1079,17 @@ export class SessionView extends LitElement {
+