diff --git a/OPUS-tauri-app/.gitignore b/OPUS-tauri-app/.gitignore new file mode 100644 index 00000000..66149586 --- /dev/null +++ b/OPUS-tauri-app/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +src-tauri/target/ +.env +*.log diff --git a/OPUS-tauri-app/index.html b/OPUS-tauri-app/index.html new file mode 100644 index 00000000..affba87d --- /dev/null +++ b/OPUS-tauri-app/index.html @@ -0,0 +1,12 @@ + + + + + + Workset Desktop + + +
+ + + diff --git a/OPUS-tauri-app/package-lock.json b/OPUS-tauri-app/package-lock.json new file mode 100644 index 00000000..f92c51ab --- /dev/null +++ b/OPUS-tauri-app/package-lock.json @@ -0,0 +1,2162 @@ +{ + "name": "workset-desktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "workset-desktop", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-opener": "^2", + "@xterm/addon-clipboard": "^0.1.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/addon-unicode11": "^0.8.0", + "@xterm/addon-web-links": "^0.11.0", + "@xterm/addon-webgl": "^0.18.0", + "@xterm/xterm": "^5.5.0", + "lucide-react": "^0.460.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zustand": "^5.0.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "typescript": "~5.9.3", + "vite": "^7.2.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz", + "integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.0", + "@tauri-apps/cli-darwin-x64": "2.10.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", + "@tauri-apps/cli-linux-arm64-musl": "2.10.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-musl": "2.10.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", + "@tauri-apps/cli-win32-x64-msvc": "2.10.0" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz", + "integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", + "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", + "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", + "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", + "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", + "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", + "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", + "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", + "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", + "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", + "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz", + "integrity": "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", + "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.2", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xterm/addon-clipboard": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.1.0.tgz", + "integrity": "sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==", + "license": "MIT", + "dependencies": { + "js-base64": "^3.7.5" + }, + "peerDependencies": { + "@xterm/xterm": "^5.4.0" + } + }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/addon-unicode11": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.8.0.tgz", + "integrity": "sha512-LxinXu8SC4OmVa6FhgwsVCBZbr8WoSGzBl2+vqe8WcQ6hb1r6Gj9P99qTNdPiFPh4Ceiu2pC8xukZ6+2nnh49Q==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/addon-web-links": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.11.0.tgz", + "integrity": "sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/addon-webgl": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.18.0.tgz", + "integrity": "sha512-xCnfMBTI+/HKPdRnSOHaJDRqEpq2Ugy8LEj9GiY4J3zJObo3joylIFaMvzBwbYRg8zLtkO0KQaStCeSfoaI2/w==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.460.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz", + "integrity": "sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/OPUS-tauri-app/package.json b/OPUS-tauri-app/package.json new file mode 100644 index 00000000..599c2049 --- /dev/null +++ b/OPUS-tauri-app/package.json @@ -0,0 +1,35 @@ +{ + "name": "workset-desktop", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "check": "tsc --noEmit", + "tauri": "tauri" + }, + "dependencies": { + "react": "^19.2.0", + "react-dom": "^19.2.0", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-opener": "^2", + "zustand": "^5.0.0", + "@xterm/xterm": "^5.5.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/addon-webgl": "^0.18.0", + "@xterm/addon-web-links": "^0.11.0", + "@xterm/addon-clipboard": "^0.1.0", + "@xterm/addon-unicode11": "^0.8.0", + "lucide-react": "^0.460.0" + }, + "devDependencies": { + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "@tauri-apps/cli": "^2" + } +} diff --git a/OPUS-tauri-app/src-tauri/Cargo.lock b/OPUS-tauri-app/src-tauri/Cargo.lock new file mode 100644 index 00000000..7bd69f3f --- /dev/null +++ b/OPUS-tauri-app/src-tauri/Cargo.lock @@ -0,0 +1,5549 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.10.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.11+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.114", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.10.0", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.11+spec-1.1.0", + "vswhom", + "winreg 0.55.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset 0.9.1", + "rustc_version", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ioctl-rs" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jiff" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.10.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.13.0", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.7.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", + "pin-utils", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.10.0", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-javascript-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" +dependencies = [ + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-javascript-core", + "objc2-security", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.13.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "portable-pty" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix", + "serial", + "shared_library", + "shell-words", + "winapi", + "winreg 0.10.1", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.10+spec-1.0.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.114", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serial" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" +dependencies = [ + "serial-core", + "serial-unix", + "serial-windows", +] + +[[package]] +name = "serial-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" +dependencies = [ + "libc", +] + +[[package]] +name = "serial-unix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" +dependencies = [ + "ioctl-rs", + "libc", + "serial-core", + "termios", +] + +[[package]] +name = "serial-windows" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" +dependencies = [ + "libc", + "serial-core", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall 0.5.18", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" +dependencies = [ + "bitflags 2.10.0", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.11+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.114", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.114", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.11+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 0.9.11+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +dependencies = [ + "dunce", + "embed-resource", + "toml 0.9.11+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termios" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" +dependencies = [ + "libc", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow 0.7.14", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow 0.7.14", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "workset-desktop" +version = "0.1.0" +dependencies = [ + "chrono", + "dirs", + "env_logger", + "log", + "notify", + "portable-pty", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-opener", + "tokio", + "uuid", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wry" +version = "0.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ed1a195b0375491dd15a7066a10251be217ce743cf4bbbbdcf5391d6473bee0" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.14", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.114", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +dependencies = [ + "serde", + "winnow 0.7.14", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zmij" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" + +[[package]] +name = "zvariant" +version = "5.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.14", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.114", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.114", + "winnow 0.7.14", +] diff --git a/OPUS-tauri-app/src-tauri/Cargo.toml b/OPUS-tauri-app/src-tauri/Cargo.toml new file mode 100644 index 00000000..8e91a093 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "workset-desktop" +version = "0.1.0" +edition = "2021" + +[lib] +name = "workset_desktop_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["full"] } +uuid = { version = "1", features = ["v4"] } +chrono = { version = "0.4", features = ["serde"] } +notify = "7" +dirs = "6" +portable-pty = "0.8" +log = "0.4" +env_logger = "0.11" diff --git a/OPUS-tauri-app/src-tauri/build.rs b/OPUS-tauri-app/src-tauri/build.rs new file mode 100644 index 00000000..d860e1e6 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/OPUS-tauri-app/src-tauri/capabilities/default.json b/OPUS-tauri-app/src-tauri/capabilities/default.json new file mode 100644 index 00000000..1164e3c6 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/capabilities/default.json @@ -0,0 +1,14 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Default capabilities for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "core:event:default", + "core:event:allow-listen", + "core:event:allow-emit", + "core:window:allow-start-dragging" + ] +} diff --git a/OPUS-tauri-app/src-tauri/gen/schemas/acl-manifests.json b/OPUS-tauri-app/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 00000000..931df681 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/gen/schemas/acl-manifests.json @@ -0,0 +1 @@ +{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"opener":{"default_permission":{"identifier":"default","description":"This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer","permissions":["allow-open-url","allow-reveal-item-in-dir","allow-default-urls"]},"permissions":{"allow-default-urls":{"identifier":"allow-default-urls","description":"This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"url":"mailto:*"},{"url":"tel:*"},{"url":"http://*"},{"url":"https://*"}]}},"allow-open-path":{"identifier":"allow-open-path","description":"Enables the open_path command without any pre-configured scope.","commands":{"allow":["open_path"],"deny":[]}},"allow-open-url":{"identifier":"allow-open-url","description":"Enables the open_url command without any pre-configured scope.","commands":{"allow":["open_url"],"deny":[]}},"allow-reveal-item-in-dir":{"identifier":"allow-reveal-item-in-dir","description":"Enables the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":["reveal_item_in_dir"],"deny":[]}},"deny-open-path":{"identifier":"deny-open-path","description":"Denies the open_path command without any pre-configured scope.","commands":{"allow":[],"deny":["open_path"]}},"deny-open-url":{"identifier":"deny-open-url","description":"Denies the open_url command without any pre-configured scope.","commands":{"allow":[],"deny":["open_url"]}},"deny-reveal-item-in-dir":{"identifier":"deny-reveal-item-in-dir","description":"Denies the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["reveal_item_in_dir"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this url with, for example: firefox."},"url":{"description":"A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\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"},{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this path with, for example: xdg-open."},"path":{"description":"A path that can be opened by the webview when using the Opener 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"}],"definitions":{"Application":{"anyOf":[{"description":"Open in default application.","type":"null"},{"description":"If true, allow open with any application.","type":"boolean"},{"description":"Allow specific application to open with.","type":"string"}],"description":"Opener scope application."}},"description":"Opener scope entry.","title":"OpenerScopeEntry"}}} \ No newline at end of file diff --git a/OPUS-tauri-app/src-tauri/gen/schemas/capabilities.json b/OPUS-tauri-app/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 00000000..26f48a97 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{"default":{"identifier":"default","description":"Default capabilities for the main window","local":true,"windows":["main"],"permissions":["core:default","opener:default","core:event:default","core:event:allow-listen","core:event:allow-emit","core:window:allow-start-dragging"]}} \ No newline at end of file diff --git a/OPUS-tauri-app/src-tauri/gen/schemas/desktop-schema.json b/OPUS-tauri-app/src-tauri/gen/schemas/desktop-schema.json new file mode 100644 index 00000000..49176251 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,2477 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\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": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener 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": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\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": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener 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" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir 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" + ] + } + ] + }, + "Application": { + "description": "Opener scope application.", + "anyOf": [ + { + "description": "Open in default application.", + "type": "null" + }, + { + "description": "If true, allow open with any application.", + "type": "boolean" + }, + { + "description": "Allow specific application to open with.", + "type": "string" + } + ] + } + } +} \ No newline at end of file diff --git a/OPUS-tauri-app/src-tauri/gen/schemas/macOS-schema.json b/OPUS-tauri-app/src-tauri/gen/schemas/macOS-schema.json new file mode 100644 index 00000000..49176251 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/gen/schemas/macOS-schema.json @@ -0,0 +1,2477 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\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": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener 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": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\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": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener 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" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir 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" + ] + } + ] + }, + "Application": { + "description": "Opener scope application.", + "anyOf": [ + { + "description": "Open in default application.", + "type": "null" + }, + { + "description": "If true, allow open with any application.", + "type": "boolean" + }, + { + "description": "Allow specific application to open with.", + "type": "string" + } + ] + } + } +} \ No newline at end of file diff --git a/OPUS-tauri-app/src-tauri/icons/128x128.png b/OPUS-tauri-app/src-tauri/icons/128x128.png new file mode 100644 index 00000000..16016d0f Binary files /dev/null and b/OPUS-tauri-app/src-tauri/icons/128x128.png differ diff --git a/OPUS-tauri-app/src-tauri/icons/128x128@2x.png b/OPUS-tauri-app/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..5b9fa9a9 Binary files /dev/null and b/OPUS-tauri-app/src-tauri/icons/128x128@2x.png differ diff --git a/OPUS-tauri-app/src-tauri/icons/32x32.png b/OPUS-tauri-app/src-tauri/icons/32x32.png new file mode 100644 index 00000000..89b5df54 Binary files /dev/null and b/OPUS-tauri-app/src-tauri/icons/32x32.png differ diff --git a/OPUS-tauri-app/src-tauri/icons/icon.icns b/OPUS-tauri-app/src-tauri/icons/icon.icns new file mode 100644 index 00000000..f0ab7744 Binary files /dev/null and b/OPUS-tauri-app/src-tauri/icons/icon.icns differ diff --git a/OPUS-tauri-app/src-tauri/src/cli/env.rs b/OPUS-tauri-app/src-tauri/src/cli/env.rs new file mode 100644 index 00000000..5586f947 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/cli/env.rs @@ -0,0 +1,50 @@ +use crate::types::diagnostics::EnvSnapshot; +use crate::types::error::ErrorEnvelope; +use std::collections::HashMap; +use std::process::Command; + +pub fn capture_env_snapshot() -> EnvSnapshot { + EnvSnapshot { + path: std::env::var("PATH").unwrap_or_default(), + shell: std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".into()), + home: std::env::var("HOME").unwrap_or_default(), + ssh_auth_sock: std::env::var("SSH_AUTH_SOCK").ok(), + git_ssh_command: std::env::var("GIT_SSH_COMMAND").ok(), + git_askpass: std::env::var("GIT_ASKPASS").ok(), + gh_config_dir: std::env::var("GH_CONFIG_DIR").ok(), + gh_auth_summary: None, + } +} + +pub fn reload_login_env() -> Result, ErrorEnvelope> { + let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".into()); + let output = Command::new(&shell) + .args(["-l", "-c", "env"]) + .output() + .map_err(|e| { + ErrorEnvelope::runtime("reload_login_env", format!("Failed to run login shell: {e}")) + })?; + + if !output.status.success() { + return Err(ErrorEnvelope::runtime( + "reload_login_env", + "Login shell exited with non-zero status", + )); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let mut env_map = HashMap::new(); + for line in stdout.lines() { + if let Some((key, value)) = line.split_once('=') { + match key { + "PATH" | "SSH_AUTH_SOCK" | "GIT_SSH_COMMAND" | "GIT_ASKPASS" + | "GH_CONFIG_DIR" | "HOME" => { + std::env::set_var(key, value); + env_map.insert(key.to_string(), value.to_string()); + } + _ => {} + } + } + } + Ok(env_map) +} diff --git a/OPUS-tauri-app/src-tauri/src/cli/mod.rs b/OPUS-tauri-app/src-tauri/src/cli/mod.rs new file mode 100644 index 00000000..e1f92bf5 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/cli/mod.rs @@ -0,0 +1,3 @@ +pub mod env; +pub mod paths; +pub mod runner; diff --git a/OPUS-tauri-app/src-tauri/src/cli/paths.rs b/OPUS-tauri-app/src-tauri/src/cli/paths.rs new file mode 100644 index 00000000..d2fc0446 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/cli/paths.rs @@ -0,0 +1,46 @@ +use std::path::PathBuf; + +pub fn resolve_workset_cli(override_path: Option<&str>) -> Option { + if let Some(p) = override_path { + let path = PathBuf::from(p); + if path.exists() { + return Some(path); + } + } + if let Some(found) = which("workset") { + return Some(found); + } + // macOS GUI apps inherit a minimal PATH; check common install locations + for dir in &["go/bin", ".local/bin", "bin"] { + if let Some(home) = std::env::var_os("HOME") { + let candidate = PathBuf::from(home).join(dir).join("workset"); + if candidate.is_file() { + return Some(candidate); + } + } + } + None +} + +pub fn resolve_sessiond(override_path: Option<&str>) -> Option { + if let Some(p) = override_path { + let path = PathBuf::from(p); + if path.exists() { + return Some(path); + } + } + which("workset-sessiond") +} + +fn which(binary: &str) -> Option { + std::env::var_os("PATH").and_then(|paths| { + std::env::split_paths(&paths).find_map(|dir| { + let full = dir.join(binary); + if full.is_file() { + Some(full) + } else { + None + } + }) + }) +} diff --git a/OPUS-tauri-app/src-tauri/src/cli/runner.rs b/OPUS-tauri-app/src-tauri/src/cli/runner.rs new file mode 100644 index 00000000..8222fb10 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/cli/runner.rs @@ -0,0 +1,34 @@ +use crate::types::error::ErrorEnvelope; +use std::process::Command; + +pub fn run_workset_command(cli_path: &str, args: &[&str]) -> Result { + let output = Command::new(cli_path) + .args(args) + .output() + .map_err(|e| { + ErrorEnvelope::runtime("cli.run", format!("Failed to execute workset CLI: {e}")) + .with_details(format!("Path: {cli_path}, Args: {args:?}")) + })?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + return Err( + ErrorEnvelope::new("git", "cli.run", format!("CLI command failed: {stderr}")) + .with_details(format!("Exit code: {:?}", output.status.code())) + .retryable(), + ); + } + + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} + +pub fn run_workset_json( + cli_path: &str, + args: &[&str], +) -> Result { + let stdout = run_workset_command(cli_path, args)?; + serde_json::from_str(&stdout).map_err(|e| { + ErrorEnvelope::runtime("cli.parse", format!("Failed to parse CLI output: {e}")) + .with_details(format!("Raw output: {}", &stdout[..stdout.len().min(500)])) + }) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/context.rs b/OPUS-tauri-app/src-tauri/src/commands/context.rs new file mode 100644 index 00000000..e66e6ff3 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/context.rs @@ -0,0 +1,51 @@ +use tauri::State; +use crate::state::AppState; +use crate::types::context::ActiveContext; +use crate::types::error::ErrorEnvelope; + +#[tauri::command] +pub fn context_get(state: State<'_, AppState>) -> Result { + let ctx = state.ui_context.lock().map_err(|e| { + ErrorEnvelope::runtime("context.get", format!("Lock error: {e}")) + })?; + let active_workset_id = ctx.active_workset_id().map(|s| s.to_string()); + let active_workspace = active_workset_id + .as_deref() + .and_then(|wid| ctx.last_workspace_for(wid)) + .map(|s| s.to_string()); + Ok(ActiveContext { + active_workset_id, + active_workspace, + }) +} + +#[tauri::command] +pub fn context_set_active_workset( + state: State<'_, AppState>, + workset_id: String, +) -> Result<(), ErrorEnvelope> { + let mut ctx = state.ui_context.lock().map_err(|e| { + ErrorEnvelope::runtime("context.set_active_workset", format!("Lock error: {e}")) + })?; + ctx.set_active_workset(&workset_id) +} + +#[tauri::command] +pub fn context_set_active_workspace( + state: State<'_, AppState>, + workspace_name: String, +) -> Result<(), ErrorEnvelope> { + let ctx_lock = state.ui_context.lock().map_err(|e| { + ErrorEnvelope::runtime("context.set_active_workspace", format!("Lock error: {e}")) + })?; + let workset_id = ctx_lock + .active_workset_id() + .ok_or_else(|| ErrorEnvelope::config("context.set_active_workspace", "No active workset"))? + .to_string(); + drop(ctx_lock); + + let mut ctx = state.ui_context.lock().map_err(|e| { + ErrorEnvelope::runtime("context.set_active_workspace", format!("Lock error: {e}")) + })?; + ctx.set_active_workspace(&workset_id, &workspace_name) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/diagnostics.rs b/OPUS-tauri-app/src-tauri/src/commands/diagnostics.rs new file mode 100644 index 00000000..144bf18e --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/diagnostics.rs @@ -0,0 +1,118 @@ +use std::process::Command; + +use tauri::State; + +use crate::cli::env::{capture_env_snapshot, reload_login_env}; +use crate::cli::paths::{resolve_sessiond, resolve_workset_cli}; +use crate::sessiond::client::SessiondClient; +use crate::sessiond::lifecycle::is_sessiond_running; +use crate::state::AppState; +use crate::types::diagnostics::{EnvSnapshot, SessiondStatus}; +use crate::types::error::ErrorEnvelope; + +#[tauri::command] +pub fn diagnostics_env_snapshot( + _state: State<'_, AppState>, +) -> Result { + Ok(capture_env_snapshot()) +} + +#[tauri::command] +pub fn diagnostics_reload_login_env( + state: State<'_, AppState>, +) -> Result { + // reload_login_env updates env vars and returns a HashMap + let _env_map = reload_login_env()?; + + // Update CLI path if it changed + if let Some(cli) = resolve_workset_cli(None) { + let mut cli_path = state.cli_path.lock().unwrap(); + *cli_path = Some(cli.to_string_lossy().to_string()); + } + + if let Some(sessiond) = resolve_sessiond(None) { + let mut sessiond_path = state.sessiond_path.lock().unwrap(); + *sessiond_path = Some(sessiond.to_string_lossy().to_string()); + } + + // Re-capture snapshot after reload + Ok(capture_env_snapshot()) +} + +#[tauri::command] +pub fn diagnostics_sessiond_status( + _state: State<'_, AppState>, +) -> Result { + let socket_path = SessiondClient::default_socket_path(); + let running = is_sessiond_running(&socket_path); + + Ok(SessiondStatus { + running, + version: None, + socket_path: Some(socket_path), + last_error: None, + last_restart: None, + }) +} + +#[tauri::command] +pub fn diagnostics_sessiond_restart( + state: State<'_, AppState>, +) -> Result { + let sessiond_path = { + state + .sessiond_path + .lock() + .unwrap() + .clone() + }; + + if let Some(path) = sessiond_path { + crate::sessiond::lifecycle::start_sessiond(&path).map_err(|e| { + ErrorEnvelope::new("diagnostics", "sessiond_restart", &e) + })?; + } + + let socket_path = SessiondClient::default_socket_path(); + let running = is_sessiond_running(&socket_path); + + Ok(SessiondStatus { + running, + version: None, + socket_path: Some(socket_path), + last_error: None, + last_restart: Some(chrono::Utc::now().to_rfc3339()), + }) +} + +#[tauri::command] +pub fn diagnostics_cli_status( + state: State<'_, AppState>, +) -> Result { + match resolve_workset_cli(None) { + Some(path) => { + let path_str = path.to_string_lossy().to_string(); + + // Get version + let version = Command::new(&path) + .arg("version") + .output() + .ok() + .and_then(|out| String::from_utf8(out.stdout).ok()) + .map(|s| s.trim().to_string()); + + let mut cli_path = state.cli_path.lock().unwrap(); + *cli_path = Some(path_str.clone()); + + Ok(serde_json::json!({ + "available": true, + "path": path_str, + "version": version, + })) + } + None => Ok(serde_json::json!({ + "available": false, + "error": "workset CLI not found in PATH", + })), + } +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/diff.rs b/OPUS-tauri-app/src-tauri/src/commands/diff.rs new file mode 100644 index 00000000..a6706a41 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/diff.rs @@ -0,0 +1,70 @@ +use tauri::{AppHandle, State}; + +use crate::diff_engine::patch::compute_file_patch; +use crate::diff_engine::summary::compute_diff_summary; +use crate::diff_engine::watcher::start_watcher; +use crate::state::AppState; +use crate::types::diff::{DiffSummary, FilePatch}; +use crate::types::error::ErrorEnvelope; + +#[tauri::command] +pub fn diff_summary( + _state: State<'_, AppState>, + _workspace_name: String, + _repo: String, + repo_path: String, +) -> Result { + compute_diff_summary(&repo_path).map_err(|e| { + ErrorEnvelope::new("diff", "diff.summary", &e) + }) +} + +#[tauri::command] +pub fn diff_file_patch( + _state: State<'_, AppState>, + repo_path: String, + path: String, + prev_path: Option, + status: String, +) -> Result { + compute_file_patch(&repo_path, &path, prev_path.as_deref(), &status).map_err(|e| { + ErrorEnvelope::new("diff", "diff.file_patch", &e) + }) +} + +#[tauri::command] +pub fn diff_watch_start( + app: AppHandle, + state: State<'_, AppState>, + workspace_name: String, + repo: String, + repo_path: String, +) -> Result<(), ErrorEnvelope> { + let key = format!("{}:{}", workspace_name, repo); + + let mut watchers = state.diff_watchers.lock().unwrap(); + if watchers.contains_key(&key) { + return Ok(()); // Already watching + } + + let handle = start_watcher(app, workspace_name, repo, repo_path); + watchers.insert(key, handle); + Ok(()) +} + +#[tauri::command] +pub fn diff_watch_stop( + state: State<'_, AppState>, + workspace_name: String, + repo: String, +) -> Result<(), ErrorEnvelope> { + let key = format!("{}:{}", workspace_name, repo); + + let mut watchers = state.diff_watchers.lock().unwrap(); + if let Some(handle) = watchers.remove(&key) { + handle + .cancel + .store(true, std::sync::atomic::Ordering::Relaxed); + } + Ok(()) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/github.rs b/OPUS-tauri-app/src-tauri/src/commands/github.rs new file mode 100644 index 00000000..fd9201a6 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/github.rs @@ -0,0 +1,216 @@ +use std::process::Command; + +use crate::types::error::ErrorEnvelope; + +#[derive(serde::Serialize)] +pub struct GitHubRepo { + pub full_name: String, + pub description: Option, + pub private: bool, +} + +/// Fetch all repos the authenticated user has access to (personal, org member, +/// collaborator) sorted by most recently pushed. Results are cached and +/// filtered client-side for instant autocomplete. +#[tauri::command] +pub async fn github_list_repos() -> Result, ErrorEnvelope> { + tokio::task::spawn_blocking(|| { + let output = Command::new("gh") + .args([ + "api", + "--method", + "GET", + "/user/repos", + "--paginate", + "--jq", + ".[] | {full_name, description, private}", + "-f", + "per_page=100", + "-f", + "sort=pushed", + "-f", + "type=all", + ]) + .output() + .map_err(|e| { + ErrorEnvelope::new( + "auth", + "github.list_repos", + format!("gh CLI not found: {e}"), + ) + })?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + if stderr.contains("not logged") || stderr.contains("auth login") { + return Err(ErrorEnvelope::new( + "auth", + "github.list_repos", + "Not authenticated with GitHub. Run `gh auth login` in a terminal.", + )); + } + return Err(ErrorEnvelope::runtime( + "github.list_repos", + format!("gh api failed: {stderr}"), + )); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + + let mut repos = Vec::new(); + for line in stdout.lines() { + let line = line.trim(); + if line.is_empty() { + continue; + } + #[derive(serde::Deserialize)] + struct Row { + full_name: String, + description: Option, + private: bool, + } + if let Ok(row) = serde_json::from_str::(line) { + repos.push(GitHubRepo { + full_name: row.full_name, + description: row.description, + private: row.private, + }); + } + } + + Ok(repos) + }) + .await + .map_err(|e| ErrorEnvelope::runtime("github.list_repos", format!("Task failed: {e}")))? +} + +#[tauri::command] +pub async fn github_auth_status() -> Result { + tokio::task::spawn_blocking(|| { + let output = Command::new("gh") + .args(["auth", "status", "--hostname", "github.com"]) + .output(); + + match output { + Ok(out) => { + let authenticated = out.status.success(); + let message = if authenticated { + // Configure git to use gh as HTTPS credential helper (idempotent) + let _ = Command::new("gh") + .args(["auth", "setup-git"]) + .output(); + String::from_utf8_lossy(&out.stdout).trim().to_string() + } else { + String::from_utf8_lossy(&out.stderr).trim().to_string() + }; + + Ok(serde_json::json!({ + "available": true, + "authenticated": authenticated, + "message": message, + })) + } + Err(_) => Ok(serde_json::json!({ + "available": false, + "authenticated": false, + "message": "gh CLI not installed", + })), + } + }) + .await + .map_err(|e| ErrorEnvelope::runtime("github.auth_status", format!("Task failed: {e}")))? +} + +#[derive(serde::Serialize)] +pub struct GitHubAccount { + pub login: String, + pub active: bool, +} + +#[tauri::command] +pub async fn github_list_accounts() -> Result, ErrorEnvelope> { + tokio::task::spawn_blocking(|| { + let output = Command::new("gh") + .args(["auth", "status", "--json", "hosts"]) + .output() + .map_err(|e| { + ErrorEnvelope::new( + "auth", + "github.list_accounts", + format!("gh CLI not found: {e}"), + ) + })?; + + if !output.status.success() { + return Ok(Vec::new()); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + + #[derive(serde::Deserialize)] + struct AccountEntry { + login: String, + active: bool, + } + + #[derive(serde::Deserialize)] + struct AuthStatus { + hosts: std::collections::HashMap>, + } + + let status: AuthStatus = serde_json::from_str(&stdout).map_err(|e| { + ErrorEnvelope::runtime( + "github.list_accounts", + format!("Failed to parse gh output: {e}"), + ) + })?; + + let mut accounts = Vec::new(); + for (_host, entries) in status.hosts { + for entry in entries { + accounts.push(GitHubAccount { + login: entry.login, + active: entry.active, + }); + } + } + + accounts.sort_by(|a, b| b.active.cmp(&a.active)); + + Ok(accounts) + }) + .await + .map_err(|e| { + ErrorEnvelope::runtime("github.list_accounts", format!("Task failed: {e}")) + })? +} + +#[tauri::command] +pub async fn github_switch_account(user: String) -> Result<(), ErrorEnvelope> { + tokio::task::spawn_blocking(move || { + let output = Command::new("gh") + .args(["auth", "switch", "--user", &user]) + .output() + .map_err(|e| { + ErrorEnvelope::new( + "auth", + "github.switch_account", + format!("gh CLI not found: {e}"), + ) + })?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(ErrorEnvelope::runtime( + "github.switch_account", + format!("Failed to switch account: {stderr}"), + )); + } + + Ok(()) + }) + .await + .map_err(|e| { + ErrorEnvelope::runtime("github.switch_account", format!("Task failed: {e}")) + })? +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/migrations.rs b/OPUS-tauri-app/src-tauri/src/commands/migrations.rs new file mode 100644 index 00000000..8a9d3aab --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/migrations.rs @@ -0,0 +1,74 @@ +use tauri::{AppHandle, State}; + +use crate::jobs::migration::{run_migration, RemoveOptions}; +use crate::state::AppState; +use crate::types::error::ErrorEnvelope; +use crate::types::job::MigrationJobRef; + +#[tauri::command] +pub fn migration_start( + app: AppHandle, + state: State<'_, AppState>, + workset_id: String, + repo_url: String, + action: String, + workspace_names: Vec, + delete_worktrees: Option, + delete_local: Option, +) -> Result { + let cli_path = resolve_cli(&state).map_err(|e| { + ErrorEnvelope::new("cli", "migration.start", &e) + })?; + + let remove_opts = if action == "remove" { + Some(RemoveOptions { + delete_worktrees: delete_worktrees.unwrap_or(true), + delete_local: delete_local.unwrap_or(false), + }) + } else { + None + }; + + let job_id = uuid::Uuid::new_v4().to_string(); + + let handle = run_migration( + app, + cli_path, + job_id.clone(), + workset_id, + repo_url, + action, + workspace_names, + remove_opts, + ); + + let mut migrations = state.migration_store.lock().unwrap(); + migrations.active_jobs.insert(job_id.clone(), handle); + + Ok(MigrationJobRef { job_id }) +} + +#[tauri::command] +pub fn migration_cancel( + state: State<'_, AppState>, + job_id: String, +) -> Result<(), ErrorEnvelope> { + let mut migrations = state.migration_store.lock().unwrap(); + if let Some(handle) = migrations.active_jobs.remove(&job_id) { + handle + .cancel + .store(true, std::sync::atomic::Ordering::Relaxed); + } + Ok(()) +} + +fn resolve_cli(state: &AppState) -> Result { + let cli = state.cli_path.lock().unwrap(); + if let Some(ref path) = *cli { + return Ok(path.clone()); + } + if let Some(found) = crate::cli::paths::resolve_workset_cli(None) { + return Ok(found.to_string_lossy().to_string()); + } + Err("workset CLI not found".to_string()) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/mod.rs b/OPUS-tauri-app/src-tauri/src/commands/mod.rs new file mode 100644 index 00000000..a973e87d --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/mod.rs @@ -0,0 +1,10 @@ +pub mod context; +pub mod diagnostics; +pub mod diff; +pub mod github; +pub mod migrations; +pub mod pty; +pub mod repos; +pub mod terminal; +pub mod worksets; +pub mod workspaces; diff --git a/OPUS-tauri-app/src-tauri/src/commands/pty.rs b/OPUS-tauri-app/src-tauri/src/commands/pty.rs new file mode 100644 index 00000000..ee0db972 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/pty.rs @@ -0,0 +1,332 @@ +use tauri::{AppHandle, Emitter, State}; + +use crate::sessiond::client::{read_stream_message, SessiondClient}; +use crate::state::AppState; +use crate::types::error::ErrorEnvelope; +use crate::types::layout::TerminalLayout; +use crate::types::pty::{BootstrapPayload, PtyCreateResult}; + +/// Active PTY session handle +pub struct PtySession { + pub session_id: String, + pub workspace_name: String, + pub terminal_id: String, + pub stream_id: String, + pub cancel: std::sync::Arc, +} + +fn make_session_id(workspace_name: &str, terminal_id: &str) -> String { + format!("{}:{}", workspace_name, terminal_id) +} + +fn get_socket_path(state: &AppState) -> String { + state + .sessiond_path + .lock() + .ok() + .and_then(|p| p.clone()) + .unwrap_or_else(SessiondClient::default_socket_path) +} + +#[tauri::command] +pub fn pty_create( + _state: State<'_, AppState>, +) -> Result { + let terminal_id = uuid::Uuid::new_v4().to_string(); + Ok(PtyCreateResult { terminal_id }) +} + +#[tauri::command] +pub fn pty_start( + app: AppHandle, + state: State<'_, AppState>, + workspace_name: String, + terminal_id: String, + _kind: String, + cwd: String, +) -> Result<(), ErrorEnvelope> { + let session_id = make_session_id(&workspace_name, &terminal_id); + let socket_path = get_socket_path(&state); + let client = SessiondClient::new(&socket_path); + + // Create session + let _resp = client.create(&session_id, &cwd).map_err(|e| { + ErrorEnvelope::new("sessiond", "pty.start", &e) + })?; + + let stream_id = uuid::Uuid::new_v4().to_string(); + let cancel = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let cancel_clone = cancel.clone(); + + // Store session, cancelling any existing streaming thread first + { + let mut sessions = state.pty_sessions.lock().unwrap(); + if let Some(old) = sessions.remove(&session_id) { + old.cancel + .store(true, std::sync::atomic::Ordering::Relaxed); + } + sessions.insert( + session_id.clone(), + PtySession { + session_id: session_id.clone(), + workspace_name: workspace_name.clone(), + terminal_id: terminal_id.clone(), + stream_id: stream_id.clone(), + cancel, + }, + ); + } + + // Spawn streaming thread + let ws_name = workspace_name.clone(); + let term_id = terminal_id.clone(); + let sid = session_id.clone(); + let strid = stream_id.clone(); + + std::thread::spawn(move || { + let client = SessiondClient::new(&socket_path); + let attach_result = client.attach(&sid, &strid, 0, false); + + let (mut reader, first) = match attach_result { + Ok(r) => r, + Err(e) => { + app.emit("pty:lifecycle", serde_json::json!({ + "workspace_name": ws_name, + "terminal_id": term_id, + "status": "error", + "message": e, + })).ok(); + return; + } + }; + + // Handle first bootstrap message + emit_stream_message(&app, &ws_name, &term_id, &first); + + // Emit lifecycle started + app.emit("pty:lifecycle", serde_json::json!({ + "workspace_name": ws_name, + "terminal_id": term_id, + "status": "started", + })).ok(); + + // Stream loop + loop { + if cancel_clone.load(std::sync::atomic::Ordering::Relaxed) { + break; + } + match read_stream_message(&mut reader) { + Ok(msg) => { + // Re-check cancel after blocking read to avoid emitting + // stale data from cancelled threads that were blocked on read + if cancel_clone.load(std::sync::atomic::Ordering::Relaxed) { + break; + } + emit_stream_message(&app, &ws_name, &term_id, &msg); + } + Err(_) => { + break; + } + } + } + + // Emit lifecycle closed + app.emit("pty:lifecycle", serde_json::json!({ + "workspace_name": ws_name, + "terminal_id": term_id, + "status": "closed", + })).ok(); + }); + + Ok(()) +} + +fn emit_stream_message( + app: &AppHandle, + workspace_name: &str, + terminal_id: &str, + msg: &crate::sessiond::protocol::StreamMessage, +) { + match msg.msg_type.as_str() { + "bootstrap" => { + app.emit("pty:bootstrap", serde_json::json!({ + "workspace_name": workspace_name, + "terminal_id": terminal_id, + "snapshot": msg.data, + "alt_screen": msg.alt_screen, + "mouse": msg.mouse, + "mouse_sgr": msg.mouse_sgr, + "safe_to_replay": msg.safe_to_replay, + "initial_credit": msg.initial_credit, + "next_offset": msg.next_offset, + })).ok(); + } + "data" => { + let bytes = msg.data.as_ref().map(|d| d.len() as i64).unwrap_or(0); + app.emit("pty:data", serde_json::json!({ + "workspace_name": workspace_name, + "terminal_id": terminal_id, + "data": msg.data, + "bytes": bytes, + })).ok(); + } + "modes" => { + app.emit("pty:modes", serde_json::json!({ + "workspace_name": workspace_name, + "terminal_id": terminal_id, + "alt_screen": msg.alt_screen, + "mouse": msg.mouse, + "mouse_sgr": msg.mouse_sgr, + "mouse_encoding": msg.mouse_encoding, + })).ok(); + } + "error" => { + app.emit("pty:lifecycle", serde_json::json!({ + "workspace_name": workspace_name, + "terminal_id": terminal_id, + "status": "error", + "message": msg.error, + })).ok(); + } + _ => {} + } +} + +#[tauri::command] +pub fn pty_write( + state: State<'_, AppState>, + workspace_name: String, + terminal_id: String, + data: String, +) -> Result<(), ErrorEnvelope> { + let session_id = make_session_id(&workspace_name, &terminal_id); + let socket_path = get_socket_path(&state); + let client = SessiondClient::new(&socket_path); + + client.send_input(&session_id, &data).map_err(|e| { + ErrorEnvelope::new("sessiond", "pty.write", &e) + }) +} + +#[tauri::command] +pub fn pty_resize( + state: State<'_, AppState>, + workspace_name: String, + terminal_id: String, + cols: u32, + rows: u32, +) -> Result<(), ErrorEnvelope> { + let session_id = make_session_id(&workspace_name, &terminal_id); + let socket_path = get_socket_path(&state); + let client = SessiondClient::new(&socket_path); + + client.resize(&session_id, cols, rows).map_err(|e| { + ErrorEnvelope::new("sessiond", "pty.resize", &e) + }) +} + +#[tauri::command] +pub fn pty_ack( + state: State<'_, AppState>, + workspace_name: String, + terminal_id: String, + bytes: i64, +) -> Result<(), ErrorEnvelope> { + let session_id = make_session_id(&workspace_name, &terminal_id); + let socket_path = get_socket_path(&state); + + // Look up stream_id from active sessions + let stream_id = { + let sessions = state.pty_sessions.lock().unwrap(); + sessions + .get(&session_id) + .map(|s| s.stream_id.clone()) + .unwrap_or_default() + }; + + if stream_id.is_empty() { + return Err(ErrorEnvelope::new("sessiond", "pty.ack", "No active session")); + } + + let client = SessiondClient::new(&socket_path); + client.ack(&session_id, &stream_id, bytes).map_err(|e| { + ErrorEnvelope::new("sessiond", "pty.ack", &e) + }) +} + +#[tauri::command] +pub fn pty_bootstrap( + state: State<'_, AppState>, + workspace_name: String, + terminal_id: String, +) -> Result { + let session_id = make_session_id(&workspace_name, &terminal_id); + let socket_path = get_socket_path(&state); + let client = SessiondClient::new(&socket_path); + + let resp = client.bootstrap(&session_id).map_err(|e| { + ErrorEnvelope::new("sessiond", "pty.bootstrap", &e) + })?; + + Ok(BootstrapPayload { + workspace_name, + terminal_id, + snapshot: resp.snapshot, + backlog: resp.backlog, + backlog_truncated: Some(resp.backlog_truncated), + next_offset: resp.next_offset.map(|n| n as u64), + alt_screen: Some(resp.alt_screen), + mouse: Some(resp.mouse), + mouse_sgr: Some(resp.mouse_sgr), + safe_to_replay: Some(resp.safe_to_replay), + initial_credit: resp.initial_credit.map(|n| n as u64), + }) +} + +#[tauri::command] +pub fn pty_stop( + state: State<'_, AppState>, + workspace_name: String, + terminal_id: String, +) -> Result<(), ErrorEnvelope> { + let session_id = make_session_id(&workspace_name, &terminal_id); + + // Cancel the streaming thread + { + let mut sessions = state.pty_sessions.lock().unwrap(); + if let Some(session) = sessions.remove(&session_id) { + session + .cancel + .store(true, std::sync::atomic::Ordering::Relaxed); + } + } + + let socket_path = get_socket_path(&state); + let client = SessiondClient::new(&socket_path); + client.stop(&session_id).map_err(|e| { + ErrorEnvelope::new("sessiond", "pty.stop", &e) + }) +} + +// ---------- Layout persistence commands ---------- + +#[tauri::command] +pub fn layout_get( + state: State<'_, AppState>, + workspace_name: String, +) -> Result, ErrorEnvelope> { + let mut store = state.layout_store.lock().unwrap(); + Ok(store.get(&workspace_name)) +} + +#[tauri::command] +pub fn layout_save( + state: State<'_, AppState>, + workspace_name: String, + layout: TerminalLayout, +) -> Result<(), ErrorEnvelope> { + let mut store = state.layout_store.lock().unwrap(); + store.save(&workspace_name, &layout).map_err(|e| { + ErrorEnvelope::new("persistence", "layout.save", &e) + }) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/repos.rs b/OPUS-tauri-app/src-tauri/src/commands/repos.rs new file mode 100644 index 00000000..f824fe5d --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/repos.rs @@ -0,0 +1,75 @@ +use tauri::State; +use crate::cli::{paths, runner}; +use crate::state::AppState; +use crate::types::error::ErrorEnvelope; +use crate::types::repo::{RepoInstance, RepoListEntry}; +use crate::types::workspace::WorkspaceSummary; + +#[tauri::command] +pub fn workspace_repos_list( + state: State<'_, AppState>, + workspace_name: String, +) -> Result, ErrorEnvelope> { + let cli_path = resolve_cli(&state)?; + + // Get workspace path from `workset ls --json` + let all_ws: Vec = runner::run_workset_json( + &cli_path, + &["ls", "--json"], + ) + .unwrap_or_default(); + let ws_path = all_ws + .iter() + .find(|w| w.name == workspace_name) + .map(|w| w.path.clone()) + .unwrap_or_default(); + + // Get repo list from `workset repo ls -w --json` + let entries: Vec = runner::run_workset_json( + &cli_path, + &["repo", "ls", "-w", &workspace_name, "--json"], + ) + .unwrap_or_default(); + + // Convert to RepoInstance with computed worktree paths + let repos = entries + .into_iter() + .map(|e| { + let worktree_path = if ws_path.is_empty() { + e.local_path.clone() + } else { + format!("{}/{}", ws_path, e.repo_dir) + }; + let missing = !std::path::Path::new(&worktree_path).exists(); + RepoInstance { + name: e.name, + worktree_path, + repo_dir: e.repo_dir, + missing, + default_branch: e.default_branch, + default_remote: e.remote, + } + }) + .collect(); + + Ok(repos) +} + +fn resolve_cli(state: &State<'_, AppState>) -> Result { + let cli = state.cli_path.lock().map_err(|e| { + ErrorEnvelope::runtime("resolve_cli", format!("Lock error: {e}")) + })?; + + if let Some(ref path) = *cli { + return Ok(path.clone()); + } + + if let Some(found) = paths::resolve_workset_cli(None) { + return Ok(found.to_string_lossy().to_string()); + } + + Err(ErrorEnvelope::config( + "resolve_cli", + "workset CLI not found. Install it with: go install ./cmd/workset", + )) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/terminal.rs b/OPUS-tauri-app/src-tauri/src/commands/terminal.rs new file mode 100644 index 00000000..636737c8 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/terminal.rs @@ -0,0 +1,73 @@ +use tauri::ipc::Channel; +use tauri::State; + +use crate::state::AppState; +use crate::terminal_manager::PtyEvent; +use crate::types::error::ErrorEnvelope; + +#[tauri::command] +pub fn terminal_spawn( + state: State<'_, AppState>, + terminal_id: String, + cwd: String, + channel: Channel, +) -> Result<(), ErrorEnvelope> { + let manager_ref = state.terminal_manager.clone(); + let mut mgr = state.terminal_manager.lock().unwrap(); + mgr.spawn(&terminal_id, &cwd, channel, manager_ref) + .map_err(|e| ErrorEnvelope::new("terminal", "terminal.spawn", &e)) +} + +#[tauri::command] +pub fn terminal_attach( + state: State<'_, AppState>, + terminal_id: String, + channel: Channel, +) -> Result<(), ErrorEnvelope> { + let mut mgr = state.terminal_manager.lock().unwrap(); + mgr.attach(&terminal_id, channel) + .map_err(|e| ErrorEnvelope::new("terminal", "terminal.attach", &e)) +} + +#[tauri::command] +pub fn terminal_detach( + state: State<'_, AppState>, + terminal_id: String, +) -> Result<(), ErrorEnvelope> { + let mut mgr = state.terminal_manager.lock().unwrap(); + mgr.detach(&terminal_id) + .map_err(|e| ErrorEnvelope::new("terminal", "terminal.detach", &e)) +} + +#[tauri::command] +pub fn terminal_write( + state: State<'_, AppState>, + terminal_id: String, + data: String, +) -> Result<(), ErrorEnvelope> { + let mut mgr = state.terminal_manager.lock().unwrap(); + mgr.write(&terminal_id, &data) + .map_err(|e| ErrorEnvelope::new("terminal", "terminal.write", &e)) +} + +#[tauri::command] +pub fn terminal_resize( + state: State<'_, AppState>, + terminal_id: String, + cols: u32, + rows: u32, +) -> Result<(), ErrorEnvelope> { + let mut mgr = state.terminal_manager.lock().unwrap(); + mgr.resize(&terminal_id, cols, rows) + .map_err(|e| ErrorEnvelope::new("terminal", "terminal.resize", &e)) +} + +#[tauri::command] +pub fn terminal_kill( + state: State<'_, AppState>, + terminal_id: String, +) -> Result<(), ErrorEnvelope> { + let mut mgr = state.terminal_manager.lock().unwrap(); + mgr.kill(&terminal_id) + .map_err(|e| ErrorEnvelope::new("terminal", "terminal.kill", &e)) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/worksets.rs b/OPUS-tauri-app/src-tauri/src/commands/worksets.rs new file mode 100644 index 00000000..36313982 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/worksets.rs @@ -0,0 +1,69 @@ +use tauri::State; +use crate::state::AppState; +use crate::types::error::ErrorEnvelope; +use crate::types::workset::{WorksetDefaults, WorksetProfile}; + +#[tauri::command] +pub fn worksets_list(state: State<'_, AppState>) -> Result, ErrorEnvelope> { + let store = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("worksets.list", format!("Lock error: {e}")) + })?; + Ok(store.list()) +} + +#[tauri::command] +pub fn worksets_create( + state: State<'_, AppState>, + name: String, + defaults: Option, +) -> Result { + let mut store = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("worksets.create", format!("Lock error: {e}")) + })?; + store.create(&name, defaults) +} + +#[tauri::command] +pub fn worksets_update( + state: State<'_, AppState>, + id: String, + name: Option, + defaults: Option, +) -> Result { + let mut store = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("worksets.update", format!("Lock error: {e}")) + })?; + store.update(&id, name.as_deref(), defaults) +} + +#[tauri::command] +pub fn worksets_delete(state: State<'_, AppState>, id: String) -> Result<(), ErrorEnvelope> { + let mut store = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("worksets.delete", format!("Lock error: {e}")) + })?; + store.delete(&id) +} + +#[tauri::command] +pub fn worksets_repos_add( + state: State<'_, AppState>, + workset_id: String, + source: String, +) -> Result { + let mut store = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("worksets.repos.add", format!("Lock error: {e}")) + })?; + store.add_repo(&workset_id, &source) +} + +#[tauri::command] +pub fn worksets_repos_remove( + state: State<'_, AppState>, + workset_id: String, + source: String, +) -> Result { + let mut store = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("worksets.repos.remove", format!("Lock error: {e}")) + })?; + store.remove_repo(&workset_id, &source) +} diff --git a/OPUS-tauri-app/src-tauri/src/commands/workspaces.rs b/OPUS-tauri-app/src-tauri/src/commands/workspaces.rs new file mode 100644 index 00000000..aa43ae67 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/commands/workspaces.rs @@ -0,0 +1,131 @@ +use tauri::State; +use crate::cli::paths; +use crate::cli::runner; +use crate::state::AppState; +use crate::types::error::ErrorEnvelope; +use crate::types::workspace::{WorkspaceCreateJobRef, WorkspaceCreateProgress, WorkspaceSummary}; + +#[tauri::command] +pub fn workspaces_list( + state: State<'_, AppState>, + workset_id: String, +) -> Result, ErrorEnvelope> { + let cli_path = resolve_cli(&state)?; + + let all: Vec = runner::run_workset_json(&cli_path, &["ls", "--json"]) + .unwrap_or_default(); + + // Filter to workspaces belonging to this workset + let profiles = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("workspaces.list", format!("Lock error: {e}")) + })?; + let workset = profiles.list().into_iter().find(|w| w.id == workset_id); + let workspace_ids: Vec = workset + .map(|w| w.workspace_ids) + .unwrap_or_default(); + + let filtered: Vec = all + .into_iter() + .filter(|ws| workspace_ids.contains(&ws.name)) + .collect(); + Ok(filtered) +} + +#[tauri::command] +pub fn workspaces_create( + state: State<'_, AppState>, + workset_id: String, + name: String, + _path: Option, +) -> Result { + let cli_path = resolve_cli(&state)?; + + // Get repos from the workset profile + let profiles = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("workspaces.create", format!("Lock error: {e}")) + })?; + let workset = profiles + .list() + .into_iter() + .find(|w| w.id == workset_id) + .ok_or_else(|| { + ErrorEnvelope::config("workspaces.create", format!("Workset '{workset_id}' not found")) + })?; + let repos = workset.repos.clone(); + drop(profiles); + + // Build CLI args: workset new --repo ... + let mut args: Vec = vec!["new".into(), name.clone()]; + for repo in &repos { + args.push("--repo".into()); + args.push(repo.clone()); + } + let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + + runner::run_workset_command(&cli_path, &arg_refs)?; + + // Register the workspace in the workset profile + let mut profiles = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("workspaces.create", format!("Lock error: {e}")) + })?; + profiles.add_workspace(&workset_id, &name)?; + + let job_id = uuid::Uuid::new_v4().to_string(); + Ok(WorkspaceCreateJobRef { job_id }) +} + +#[tauri::command] +pub fn workspaces_create_status( + _state: State<'_, AppState>, + job_id: String, +) -> Result { + Ok(WorkspaceCreateProgress { + job_id, + state: "succeeded".into(), + repos: Vec::new(), + workspace_name: None, + workspace_path: None, + }) +} + +#[tauri::command] +pub fn workspaces_delete( + state: State<'_, AppState>, + workset_id: String, + workspace_name: String, + delete: Option, +) -> Result<(), ErrorEnvelope> { + let cli_path = resolve_cli(&state)?; + let mut args = vec!["rm", &workspace_name]; + if delete.unwrap_or(false) { + args.push("--delete"); + } + runner::run_workset_command(&cli_path, &args)?; + + // Remove the workspace from the workset profile + let mut profiles = state.profiles.lock().map_err(|e| { + ErrorEnvelope::runtime("workspaces.delete", format!("Lock error: {e}")) + })?; + profiles.remove_workspace(&workset_id, &workspace_name)?; + + Ok(()) +} + +fn resolve_cli(state: &State<'_, AppState>) -> Result { + let cli = state.cli_path.lock().map_err(|e| { + ErrorEnvelope::runtime("resolve_cli", format!("Lock error: {e}")) + })?; + + if let Some(ref path) = *cli { + return Ok(path.clone()); + } + + if let Some(found) = paths::resolve_workset_cli(None) { + return Ok(found.to_string_lossy().to_string()); + } + + Err(ErrorEnvelope::config( + "resolve_cli", + "workset CLI not found. Install it with: go install ./cmd/workset", + )) +} diff --git a/OPUS-tauri-app/src-tauri/src/diff_engine/mod.rs b/OPUS-tauri-app/src-tauri/src/diff_engine/mod.rs new file mode 100644 index 00000000..1d7b7d58 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/diff_engine/mod.rs @@ -0,0 +1,3 @@ +pub mod patch; +pub mod summary; +pub mod watcher; diff --git a/OPUS-tauri-app/src-tauri/src/diff_engine/patch.rs b/OPUS-tauri-app/src-tauri/src/diff_engine/patch.rs new file mode 100644 index 00000000..fe9877d0 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/diff_engine/patch.rs @@ -0,0 +1,75 @@ +use std::process::Command; + +use crate::types::diff::FilePatch; + +const MAX_PATCH_BYTES: u64 = 2 * 1024 * 1024; // 2MB +const MAX_PATCH_LINES: u32 = 20_000; + +/// Extract a unified diff patch for a single file. +pub fn compute_file_patch( + repo_path: &str, + path: &str, + _prev_path: Option<&str>, + status: &str, +) -> Result { + let mut patch = String::new(); + + if status == "A" || status == "untracked" { + // Untracked file: use no-index diff + let output = Command::new("git") + .current_dir(repo_path) + .args(["diff", "--no-index", "--unified=3", "--", "/dev/null", path]) + .output() + .map_err(|e| format!("git diff --no-index failed: {}", e))?; + // git diff --no-index returns exit code 1 for differences, which is expected + patch = String::from_utf8_lossy(&output.stdout).to_string(); + } else { + // Staged changes + let staged = Command::new("git") + .current_dir(repo_path) + .args(["diff", "--cached", "--unified=3", "--", path]) + .output() + .map_err(|e| format!("git diff --cached failed: {}", e))?; + let staged_out = String::from_utf8_lossy(&staged.stdout); + + // Unstaged changes + let unstaged = Command::new("git") + .current_dir(repo_path) + .args(["diff", "--unified=3", "--", path]) + .output() + .map_err(|e| format!("git diff failed: {}", e))?; + let unstaged_out = String::from_utf8_lossy(&unstaged.stdout); + + if !staged_out.is_empty() { + patch.push_str(&staged_out); + } + if !unstaged_out.is_empty() { + if !patch.is_empty() { + patch.push('\n'); + } + patch.push_str(&unstaged_out); + } + } + + let total_bytes = patch.len() as u64; + let total_lines = patch.lines().count() as u32; + + // Check for binary + let is_binary = patch.contains("Binary files") || patch.contains("GIT binary patch"); + + let truncated = total_bytes > MAX_PATCH_BYTES || total_lines > MAX_PATCH_LINES; + let final_patch = if truncated { + let limit = MAX_PATCH_BYTES.min(total_bytes) as usize; + patch[..limit].to_string() + } else { + patch + }; + + Ok(FilePatch { + patch: final_patch, + truncated, + total_bytes, + total_lines, + binary: if is_binary { Some(true) } else { None }, + }) +} diff --git a/OPUS-tauri-app/src-tauri/src/diff_engine/summary.rs b/OPUS-tauri-app/src-tauri/src/diff_engine/summary.rs new file mode 100644 index 00000000..6203ba80 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/diff_engine/summary.rs @@ -0,0 +1,201 @@ +use std::collections::HashMap; +use std::process::Command; + +use crate::types::diff::{DiffFileSummary, DiffSummary}; + +/// Compute a diff summary for a repo worktree, combining staged + unstaged + untracked. +pub fn compute_diff_summary(repo_path: &str) -> Result { + let mut file_map: HashMap = HashMap::new(); + + // 1. Staged name-status + parse_name_status(repo_path, true, &mut file_map)?; + // 2. Unstaged name-status + parse_name_status(repo_path, false, &mut file_map)?; + // 3. Staged numstat + parse_numstat(repo_path, true, &mut file_map)?; + // 4. Unstaged numstat + parse_numstat(repo_path, false, &mut file_map)?; + // 5. Untracked files + add_untracked(repo_path, &mut file_map)?; + + let mut files: Vec = file_map.into_values().collect(); + files.sort_by(|a, b| a.path.cmp(&b.path)); + + let total_added: u32 = files.iter().map(|f| f.added).sum(); + let total_removed: u32 = files.iter().map(|f| f.removed).sum(); + + Ok(DiffSummary { + files, + total_added, + total_removed, + }) +} + +fn parse_name_status( + repo_path: &str, + cached: bool, + map: &mut HashMap, +) -> Result<(), String> { + let mut cmd = Command::new("git"); + cmd.current_dir(repo_path) + .args(["diff", "--name-status", "--find-renames", "-z"]); + if cached { + cmd.arg("--cached"); + } + + let output = cmd.output().map_err(|e| format!("git diff failed: {}", e))?; + let stdout = String::from_utf8_lossy(&output.stdout); + + let parts: Vec<&str> = stdout.split('\0').collect(); + let mut i = 0; + while i < parts.len() { + let status_str = parts[i].trim(); + if status_str.is_empty() { + i += 1; + continue; + } + let status_char = status_str.chars().next().unwrap_or('M'); + let status = match status_char { + 'A' => "A", + 'D' => "D", + 'R' | 'C' => "R", + _ => "M", + }; + + if status_char == 'R' || status_char == 'C' { + // Rename/copy: next two entries are old_path and new_path + if i + 2 < parts.len() { + let prev_path = parts[i + 1].to_string(); + let path = parts[i + 2].to_string(); + map.entry(path.clone()).or_insert(DiffFileSummary { + path, + prev_path: Some(prev_path), + added: 0, + removed: 0, + status: status.to_string(), + binary: None, + }); + i += 3; + } else { + break; + } + } else { + if i + 1 < parts.len() { + let path = parts[i + 1].to_string(); + map.entry(path.clone()).or_insert(DiffFileSummary { + path, + prev_path: None, + added: 0, + removed: 0, + status: status.to_string(), + binary: None, + }); + i += 2; + } else { + break; + } + } + } + Ok(()) +} + +fn parse_numstat( + repo_path: &str, + cached: bool, + map: &mut HashMap, +) -> Result<(), String> { + let mut cmd = Command::new("git"); + cmd.current_dir(repo_path) + .args(["diff", "--numstat", "--find-renames", "-z"]); + if cached { + cmd.arg("--cached"); + } + + let output = cmd.output().map_err(|e| format!("git numstat failed: {}", e))?; + let stdout = String::from_utf8_lossy(&output.stdout); + + for line in stdout.lines() { + let parts: Vec<&str> = line.split('\t').collect(); + if parts.len() >= 3 { + let added: u32 = parts[0].parse().unwrap_or(0); + let removed: u32 = parts[1].parse().unwrap_or(0); + let path = parts[2].trim_matches('\0').to_string(); + + if parts[0] == "-" && parts[1] == "-" { + // Binary file + if let Some(entry) = map.get_mut(&path) { + entry.binary = Some(true); + } + } else if let Some(entry) = map.get_mut(&path) { + entry.added += added; + entry.removed += removed; + } + } + } + Ok(()) +} + +fn add_untracked( + repo_path: &str, + map: &mut HashMap, +) -> Result<(), String> { + let output = Command::new("git") + .current_dir(repo_path) + .args(["ls-files", "--others", "--exclude-standard", "-z"]) + .output() + .map_err(|e| format!("git ls-files failed: {}", e))?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let untracked: Vec<&str> = stdout.split('\0').filter(|s| !s.is_empty()).collect(); + + // Batch numstat for untracked files + if !untracked.is_empty() { + for chunk in untracked.chunks(200) { + let mut cmd = Command::new("git"); + cmd.current_dir(repo_path) + .args(["diff", "--no-index", "--numstat", "-z", "--"]); + for path in chunk { + cmd.arg("/dev/null").arg(path); + } + let result = cmd.output(); + match result { + Ok(out) => { + let s = String::from_utf8_lossy(&out.stdout); + for line in s.lines() { + let parts: Vec<&str> = line.split('\t').collect(); + if parts.len() >= 3 { + let added: u32 = parts[0].parse().unwrap_or(0); + let path = parts[2] + .trim_matches('\0') + .trim_start_matches("./") + .to_string(); + map.entry(path.clone()).or_insert(DiffFileSummary { + path, + prev_path: None, + added, + removed: 0, + status: "A".to_string(), + binary: None, + }); + } + } + } + Err(_) => { + // Fallback: just list them without stats + for path in chunk { + let p = path.to_string(); + map.entry(p.clone()).or_insert(DiffFileSummary { + path: p, + prev_path: None, + added: 0, + removed: 0, + status: "A".to_string(), + binary: None, + }); + } + } + } + } + } + Ok(()) +} diff --git a/OPUS-tauri-app/src-tauri/src/diff_engine/watcher.rs b/OPUS-tauri-app/src-tauri/src/diff_engine/watcher.rs new file mode 100644 index 00000000..3c214c12 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/diff_engine/watcher.rs @@ -0,0 +1,74 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use tauri::{AppHandle, Emitter}; + +use crate::diff_engine::summary::compute_diff_summary; + +const POLL_INTERVAL_SECS: u64 = 5; + +/// Handle for a running diff watcher. +pub struct DiffWatcherHandle { + pub cancel: Arc, +} + +/// Start a background diff watcher for a repo worktree. Emits `diff:summary` events. +pub fn start_watcher( + app: AppHandle, + workspace_name: String, + repo_name: String, + repo_path: String, +) -> DiffWatcherHandle { + let cancel = Arc::new(AtomicBool::new(false)); + let cancel_clone = cancel.clone(); + + std::thread::spawn(move || { + let mut last_hash: Option = None; + + loop { + if cancel_clone.load(Ordering::Relaxed) { + break; + } + + match compute_diff_summary(&repo_path) { + Ok(summary) => { + // Simple hash to deduplicate + let hash = { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + format!("{:?}", summary.files).hash(&mut hasher); + hasher.finish() + }; + + if last_hash != Some(hash) { + last_hash = Some(hash); + app.emit("diff:summary", serde_json::json!({ + "workspace_name": workspace_name, + "repo": repo_name, + "summary": summary, + })).ok(); + } + } + Err(e) => { + app.emit("diff:status", serde_json::json!({ + "workspace_name": workspace_name, + "repo": repo_name, + "status": "error", + "message": e, + })).ok(); + } + } + + // Sleep with cancellation check + for _ in 0..(POLL_INTERVAL_SECS * 1000 / 500) { + if cancel_clone.load(Ordering::Relaxed) { + return; + } + std::thread::sleep(Duration::from_millis(500)); + } + } + }); + + DiffWatcherHandle { cancel } +} diff --git a/OPUS-tauri-app/src-tauri/src/jobs/migration.rs b/OPUS-tauri-app/src-tauri/src/jobs/migration.rs new file mode 100644 index 00000000..11f39889 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/jobs/migration.rs @@ -0,0 +1,159 @@ +use std::process::Command; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use tauri::{AppHandle, Emitter}; + +use crate::types::job::{MigrationProgress, WorkspaceMigrationStatus}; + +#[derive(Debug, Clone, Default)] +pub struct RemoveOptions { + pub delete_worktrees: bool, + pub delete_local: bool, +} + +pub struct MigrationJobHandle { + pub job_id: String, + pub cancel: Arc, +} + +/// Run a migration job: add or remove a repo from the given workspaces. +pub fn run_migration( + app: AppHandle, + cli_path: String, + job_id: String, + _workset_id: String, + repo_url: String, + action: String, // "add" or "remove" + workspace_names: Vec, + remove_opts: Option, +) -> MigrationJobHandle { + let cancel = Arc::new(AtomicBool::new(false)); + let cancel_clone = cancel.clone(); + + let handle = MigrationJobHandle { + job_id: job_id.clone(), + cancel: cancel.clone(), + }; + + std::thread::spawn(move || { + let mut statuses: Vec = workspace_names + .iter() + .map(|name| WorkspaceMigrationStatus { + workspace_name: name.clone(), + state: "pending".to_string(), + error: None, + }) + .collect(); + + emit_progress(&app, &job_id, "running", &statuses); + + for (i, ws_name) in workspace_names.iter().enumerate() { + if cancel_clone.load(Ordering::Relaxed) { + for status in statuses.iter_mut().skip(i) { + status.state = "failed".to_string(); + } + emit_progress(&app, &job_id, "canceled", &statuses); + return; + } + + statuses[i].state = "running".to_string(); + emit_progress(&app, &job_id, "running", &statuses); + + let result = if action == "add" { + run_add_repo(&cli_path, ws_name, &repo_url) + } else { + run_remove_repo(&cli_path, ws_name, &repo_url, remove_opts.as_ref()) + }; + + match result { + Ok(_) => { + statuses[i].state = "success".to_string(); + } + Err(e) => { + statuses[i].state = "failed".to_string(); + statuses[i].error = Some( + crate::types::error::ErrorEnvelope::new("migration", &action, &e), + ); + } + } + emit_progress(&app, &job_id, "running", &statuses); + } + + let has_failures = statuses.iter().any(|s| s.state == "failed"); + let final_state = if has_failures { "failed" } else { "done" }; + emit_progress(&app, &job_id, final_state, &statuses); + }); + + handle +} + +fn run_add_repo(cli_path: &str, workspace_name: &str, repo_url: &str) -> Result<(), String> { + let output = Command::new(cli_path) + .args(["repo", "add", "-w", workspace_name, repo_url]) + .output() + .map_err(|e| format!("Failed to run workset repo add: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("repo add failed: {}", stderr)); + } + Ok(()) +} + +fn run_remove_repo( + cli_path: &str, + workspace_name: &str, + repo_url: &str, + opts: Option<&RemoveOptions>, +) -> Result<(), String> { + let repo_name = derive_repo_name(repo_url); + let mut args = vec!["repo", "remove", "-w", workspace_name, &repo_name, "--yes"]; + if let Some(o) = opts { + if o.delete_worktrees { + args.push("--delete-worktrees"); + } + if o.delete_local { + args.push("--delete-local"); + } + } + + let output = Command::new(cli_path) + .args(&args) + .output() + .map_err(|e| format!("Failed to run workset repo remove: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("repo remove failed: {}", stderr)); + } + Ok(()) +} + +/// Derive the repo name from a URL/source string. +/// Mirrors Go's DeriveRepoNameFromURL: strips .git suffix, trailing slashes, +/// then returns the last path segment. +fn derive_repo_name(url: &str) -> String { + let trimmed = url.trim_end_matches(".git").trim_end_matches('/'); + if let Some(idx) = trimmed.rfind('/') { + return trimmed[idx + 1..].to_string(); + } + if let Some(idx) = trimmed.rfind(':') { + return trimmed[idx + 1..].to_string(); + } + trimmed.to_string() +} + +fn emit_progress( + app: &AppHandle, + job_id: &str, + state: &str, + workspaces: &[WorkspaceMigrationStatus], +) { + let progress = MigrationProgress { + job_id: job_id.to_string(), + state: state.to_string(), + workspaces: workspaces.to_vec(), + }; + app.emit("migration:progress", &progress).ok(); +} diff --git a/OPUS-tauri-app/src-tauri/src/jobs/mod.rs b/OPUS-tauri-app/src-tauri/src/jobs/mod.rs new file mode 100644 index 00000000..a571eaa2 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/jobs/mod.rs @@ -0,0 +1 @@ +pub mod migration; diff --git a/OPUS-tauri-app/src-tauri/src/lib.rs b/OPUS-tauri-app/src-tauri/src/lib.rs new file mode 100644 index 00000000..efac2754 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/lib.rs @@ -0,0 +1,74 @@ +mod cli; +mod commands; +mod diff_engine; +mod jobs; +mod sessiond; +mod state; +mod store; +mod terminal_manager; +mod types; + +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .manage(state::AppState::new()) + .invoke_handler(tauri::generate_handler![ + // Group 1: Worksets + commands::worksets::worksets_list, + commands::worksets::worksets_create, + commands::worksets::worksets_update, + commands::worksets::worksets_delete, + commands::worksets::worksets_repos_add, + commands::worksets::worksets_repos_remove, + // Group 2: Context + commands::context::context_get, + commands::context::context_set_active_workset, + commands::context::context_set_active_workspace, + // Group 3: Workspaces + commands::workspaces::workspaces_list, + commands::workspaces::workspaces_create, + commands::workspaces::workspaces_create_status, + commands::workspaces::workspaces_delete, + // Group 5: Repos + commands::repos::workspace_repos_list, + // Group 8: PTY + commands::pty::pty_create, + commands::pty::pty_start, + commands::pty::pty_write, + commands::pty::pty_resize, + commands::pty::pty_ack, + commands::pty::pty_bootstrap, + commands::pty::pty_stop, + // Layout persistence + commands::pty::layout_get, + commands::pty::layout_save, + // Terminal (new portable-pty based) + commands::terminal::terminal_spawn, + commands::terminal::terminal_attach, + commands::terminal::terminal_detach, + commands::terminal::terminal_write, + commands::terminal::terminal_resize, + commands::terminal::terminal_kill, + // Group 6-7: Diff + commands::diff::diff_summary, + commands::diff::diff_file_patch, + commands::diff::diff_watch_start, + commands::diff::diff_watch_stop, + // Group 4: Migrations + commands::migrations::migration_start, + commands::migrations::migration_cancel, + // Group 9: Diagnostics + commands::diagnostics::diagnostics_env_snapshot, + commands::diagnostics::diagnostics_reload_login_env, + commands::diagnostics::diagnostics_sessiond_status, + commands::diagnostics::diagnostics_sessiond_restart, + commands::diagnostics::diagnostics_cli_status, + // Group 10: GitHub + commands::github::github_list_repos, + commands::github::github_auth_status, + commands::github::github_list_accounts, + commands::github::github_switch_account, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/OPUS-tauri-app/src-tauri/src/main.rs b/OPUS-tauri-app/src-tauri/src/main.rs new file mode 100644 index 00000000..b1febf22 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/main.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + workset_desktop_lib::run() +} diff --git a/OPUS-tauri-app/src-tauri/src/sessiond/client.rs b/OPUS-tauri-app/src-tauri/src/sessiond/client.rs new file mode 100644 index 00000000..2f17c01a --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/sessiond/client.rs @@ -0,0 +1,220 @@ +use std::io::{BufRead, BufReader, Write}; +use std::os::unix::net::UnixStream; +use std::path::Path; +use std::time::Duration; + +use crate::sessiond::protocol::*; + +pub struct SessiondClient { + socket_path: String, +} + +impl SessiondClient { + pub fn new(socket_path: &str) -> Self { + Self { + socket_path: socket_path.to_string(), + } + } + + pub fn default_socket_path() -> String { + let home = dirs::home_dir().unwrap_or_default(); + let base = home.join(".workset"); + // Prefer dev socket if it exists (used during development) + let dev = base.join("sessiond-dev.sock"); + if dev.exists() { + return dev.to_string_lossy().to_string(); + } + base.join("sessiond.sock") + .to_string_lossy() + .to_string() + } + + fn connect(&self) -> Result { + let path = Path::new(&self.socket_path); + if !path.exists() { + return Err(format!("sessiond socket not found at {}", self.socket_path)); + } + let stream = UnixStream::connect(path) + .map_err(|e| format!("Failed to connect to sessiond: {}", e))?; + stream + .set_read_timeout(Some(Duration::from_secs(10))) + .ok(); + stream + .set_write_timeout(Some(Duration::from_secs(5))) + .ok(); + Ok(stream) + } + + fn control_call( + &self, + method: &str, + params: Option, + ) -> Result { + let mut stream = self.connect()?; + + let req = ControlRequest { + protocol_version: PROTOCOL_VERSION, + method: method.to_string(), + params, + }; + let mut payload = serde_json::to_string(&req) + .map_err(|e| format!("Failed to serialize request: {}", e))?; + payload.push('\n'); + + stream + .write_all(payload.as_bytes()) + .map_err(|e| format!("Failed to send request: {}", e))?; + stream + .flush() + .map_err(|e| format!("Failed to flush: {}", e))?; + + let mut reader = BufReader::new(stream); + let mut line = String::new(); + reader + .read_line(&mut line) + .map_err(|e| format!("Failed to read response: {}", e))?; + + let resp: ControlResponse = serde_json::from_str(line.trim()) + .map_err(|e| format!("Failed to parse response: {}", e))?; + + if !resp.ok { + return Err(resp.error.unwrap_or_else(|| "Unknown error".to_string())); + } + Ok(resp.result) + } + + pub fn create(&self, session_id: &str, cwd: &str) -> Result { + let params = serde_json::to_value(CreateRequest { + session_id: session_id.to_string(), + cwd: cwd.to_string(), + }) + .map_err(|e| e.to_string())?; + + let result = self.control_call("create", Some(params))?; + serde_json::from_value(result).map_err(|e| format!("Failed to parse create response: {}", e)) + } + + pub fn send_input(&self, session_id: &str, data: &str) -> Result<(), String> { + let params = serde_json::to_value(SendRequest { + session_id: session_id.to_string(), + data: data.to_string(), + }) + .map_err(|e| e.to_string())?; + + self.control_call("send", Some(params))?; + Ok(()) + } + + pub fn resize(&self, session_id: &str, cols: u32, rows: u32) -> Result<(), String> { + let params = serde_json::to_value(ResizeRequest { + session_id: session_id.to_string(), + cols, + rows, + }) + .map_err(|e| e.to_string())?; + + self.control_call("resize", Some(params))?; + Ok(()) + } + + pub fn ack( + &self, + session_id: &str, + stream_id: &str, + bytes: i64, + ) -> Result<(), String> { + let params = serde_json::to_value(AckRequest { + session_id: session_id.to_string(), + stream_id: stream_id.to_string(), + bytes, + }) + .map_err(|e| e.to_string())?; + + self.control_call("ack", Some(params))?; + Ok(()) + } + + pub fn bootstrap(&self, session_id: &str) -> Result { + let params = serde_json::to_value(BootstrapRequest { + session_id: session_id.to_string(), + }) + .map_err(|e| e.to_string())?; + + let result = self.control_call("bootstrap", Some(params))?; + serde_json::from_value(result) + .map_err(|e| format!("Failed to parse bootstrap response: {}", e)) + } + + pub fn stop(&self, session_id: &str) -> Result<(), String> { + let params = serde_json::to_value(StopRequest { + session_id: session_id.to_string(), + }) + .map_err(|e| e.to_string())?; + + self.control_call("stop", Some(params))?; + Ok(()) + } + + /// Opens a long-lived attach stream. Returns the stream and the first bootstrap message. + /// The caller should spawn a task to read from the stream in a loop. + pub fn attach( + &self, + session_id: &str, + stream_id: &str, + since: i64, + with_buffer: bool, + ) -> Result<(BufReader, StreamMessage), String> { + let mut stream = self.connect()?; + + // Attach uses a longer read timeout since it's a long-lived stream + stream + .set_read_timeout(Some(Duration::from_secs(300))) + .ok(); + + let req = AttachRequest { + protocol_version: PROTOCOL_VERSION, + msg_type: "attach".to_string(), + session_id: session_id.to_string(), + stream_id: Some(stream_id.to_string()), + since, + with_buffer, + }; + + let mut payload = serde_json::to_string(&req) + .map_err(|e| format!("Failed to serialize attach request: {}", e))?; + payload.push('\n'); + + stream + .write_all(payload.as_bytes()) + .map_err(|e| format!("Failed to send attach request: {}", e))?; + stream + .flush() + .map_err(|e| format!("Failed to flush: {}", e))?; + + let mut reader = BufReader::new(stream); + let mut line = String::new(); + reader + .read_line(&mut line) + .map_err(|e| format!("Failed to read initial message: {}", e))?; + + let first: StreamMessage = serde_json::from_str(line.trim()) + .map_err(|e| format!("Failed to parse initial stream message: {}", e))?; + + Ok((reader, first)) + } +} + +/// Read the next stream message from an attached stream. +pub fn read_stream_message( + reader: &mut BufReader, +) -> Result { + let mut line = String::new(); + reader + .read_line(&mut line) + .map_err(|e| format!("Stream read error: {}", e))?; + if line.is_empty() { + return Err("Stream closed".to_string()); + } + serde_json::from_str(line.trim()) + .map_err(|e| format!("Failed to parse stream message: {}", e)) +} diff --git a/OPUS-tauri-app/src-tauri/src/sessiond/lifecycle.rs b/OPUS-tauri-app/src-tauri/src/sessiond/lifecycle.rs new file mode 100644 index 00000000..6f9f13c2 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/sessiond/lifecycle.rs @@ -0,0 +1,31 @@ +use std::path::Path; +use std::process::Command; + +use crate::sessiond::client::SessiondClient; + +/// Check if sessiond is running by attempting a connection. +pub fn is_sessiond_running(socket_path: &str) -> bool { + Path::new(socket_path).exists() +} + +/// Attempt to start the sessiond binary. +pub fn start_sessiond(binary_path: &str) -> Result<(), String> { + Command::new(binary_path) + .arg("start") + .spawn() + .map_err(|e| format!("Failed to start sessiond: {}", e))?; + + // Give it a moment to start + std::thread::sleep(std::time::Duration::from_millis(500)); + Ok(()) +} + +/// Check sessiond health by connecting and verifying the socket is responsive. +pub fn check_health(socket_path: &str) -> Result { + if !is_sessiond_running(socket_path) { + return Ok(false); + } + // Try creating a client and doing a minimal operation + let _client = SessiondClient::new(socket_path); + Ok(true) +} diff --git a/OPUS-tauri-app/src-tauri/src/sessiond/mod.rs b/OPUS-tauri-app/src-tauri/src/sessiond/mod.rs new file mode 100644 index 00000000..edcaa6f4 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/sessiond/mod.rs @@ -0,0 +1,3 @@ +pub mod client; +pub mod lifecycle; +pub mod protocol; diff --git a/OPUS-tauri-app/src-tauri/src/sessiond/protocol.rs b/OPUS-tauri-app/src-tauri/src/sessiond/protocol.rs new file mode 100644 index 00000000..a420f447 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/sessiond/protocol.rs @@ -0,0 +1,163 @@ +use serde::{Deserialize, Serialize}; + +pub const PROTOCOL_VERSION: u32 = 2; + +// ---------- Control request/response ---------- + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ControlRequest { + pub protocol_version: u32, + pub method: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub params: Option, +} + +#[derive(Debug, Deserialize)] +pub struct ControlResponse { + pub ok: bool, + #[serde(default)] + pub result: serde_json::Value, + #[serde(default)] + pub error: Option, +} + +// ---------- Create ---------- + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRequest { + pub session_id: String, + pub cwd: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateResponse { + pub session_id: String, + #[serde(default)] + pub existing: bool, +} + +// ---------- Attach ---------- + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AttachRequest { + pub protocol_version: u32, + #[serde(rename = "type")] + pub msg_type: String, + pub session_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub stream_id: Option, + pub since: i64, + pub with_buffer: bool, +} + +// ---------- Stream message (received from sessiond) ---------- + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StreamMessage { + #[serde(rename = "type")] + pub msg_type: String, + #[serde(default)] + pub session_id: String, + #[serde(default)] + pub stream_id: String, + #[serde(default)] + pub data: Option, + #[serde(default)] + pub len: Option, + #[serde(default)] + pub next_offset: Option, + #[serde(default)] + pub truncated: bool, + #[serde(default)] + pub source: Option, + #[serde(default)] + pub snapshot_source: Option, + #[serde(default)] + pub backlog_source: Option, + #[serde(default)] + pub backlog_truncated: bool, + #[serde(default)] + pub alt_screen: bool, + #[serde(default)] + pub mouse_mask: u8, + #[serde(default)] + pub mouse: bool, + #[serde(default)] + pub mouse_sgr: bool, + #[serde(default)] + pub mouse_encoding: Option, + #[serde(default)] + pub safe_to_replay: bool, + #[serde(default)] + pub initial_credit: Option, + #[serde(default)] + pub error: Option, +} + +// ---------- Send / Resize / Stop / ACK ---------- + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SendRequest { + pub session_id: String, + pub data: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResizeRequest { + pub session_id: String, + pub cols: u32, + pub rows: u32, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StopRequest { + pub session_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AckRequest { + pub session_id: String, + pub stream_id: String, + pub bytes: i64, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BootstrapRequest { + pub session_id: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BootstrapResponse { + pub session_id: String, + #[serde(default)] + pub snapshot: Option, + #[serde(default)] + pub backlog: Option, + #[serde(default)] + pub next_offset: Option, + #[serde(default)] + pub backlog_truncated: bool, + #[serde(default)] + pub alt_screen: bool, + #[serde(default)] + pub mouse: bool, + #[serde(default)] + pub mouse_sgr: bool, + #[serde(default)] + pub mouse_encoding: Option, + #[serde(default)] + pub safe_to_replay: bool, + #[serde(default)] + pub initial_credit: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/state.rs b/OPUS-tauri-app/src-tauri/src/state.rs new file mode 100644 index 00000000..0f904900 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/state.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use crate::commands::pty::PtySession; +use crate::diff_engine::watcher::DiffWatcherHandle; +use crate::store::layout::LayoutStore; +use crate::store::migrations::MigrationStore; +use crate::store::ui_context::UiContextStore; +use crate::store::workset_profiles::WorksetProfileStore; +use crate::terminal_manager::TerminalManager; + +pub struct AppState { + pub profiles: Mutex, + pub ui_context: Mutex, + pub layout_store: Mutex, + pub migration_store: Mutex, + pub pty_sessions: Mutex>, + pub diff_watchers: Mutex>, + pub cli_path: Mutex>, + pub sessiond_path: Mutex>, + pub terminal_manager: Arc>, +} + +impl AppState { + pub fn new() -> Self { + let profiles = WorksetProfileStore::load().unwrap_or_default(); + let ui_context = UiContextStore::load().unwrap_or_default(); + Self { + profiles: Mutex::new(profiles), + ui_context: Mutex::new(ui_context), + layout_store: Mutex::new(LayoutStore::new()), + migration_store: Mutex::new(MigrationStore::new()), + pty_sessions: Mutex::new(HashMap::new()), + diff_watchers: Mutex::new(HashMap::new()), + cli_path: Mutex::new(None), + sessiond_path: Mutex::new(None), + terminal_manager: Arc::new(Mutex::new(TerminalManager::new())), + } + } +} diff --git a/OPUS-tauri-app/src-tauri/src/store/layout.rs b/OPUS-tauri-app/src-tauri/src/store/layout.rs new file mode 100644 index 00000000..99556107 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/store/layout.rs @@ -0,0 +1,67 @@ +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +use crate::types::layout::TerminalLayout; + +/// Persists terminal layout per workspace path. +pub struct LayoutStore { + dir: PathBuf, + cache: HashMap, +} + +impl LayoutStore { + pub fn new() -> Self { + let dir = dirs::home_dir() + .unwrap_or_default() + .join(".workset") + .join("layouts"); + fs::create_dir_all(&dir).ok(); + Self { + dir, + cache: HashMap::new(), + } + } + + fn path_for(&self, workspace_name: &str) -> PathBuf { + let safe_name = workspace_name.replace('/', "__"); + self.dir.join(format!("{}.json", safe_name)) + } + + pub fn get(&mut self, workspace_name: &str) -> Option { + if let Some(layout) = self.cache.get(workspace_name) { + return Some(layout.clone()); + } + let path = self.path_for(workspace_name); + if path.exists() { + if let Ok(data) = fs::read_to_string(&path) { + if let Ok(layout) = serde_json::from_str::(&data) { + self.cache.insert(workspace_name.to_string(), layout.clone()); + return Some(layout); + } + } + } + None + } + + pub fn save(&mut self, workspace_name: &str, layout: &TerminalLayout) -> Result<(), String> { + let path = self.path_for(workspace_name); + let data = serde_json::to_string_pretty(layout) + .map_err(|e| format!("Failed to serialize layout: {}", e))?; + fs::write(&path, data).map_err(|e| format!("Failed to write layout: {}", e))?; + self.cache.insert(workspace_name.to_string(), layout.clone()); + Ok(()) + } + + pub fn delete(&mut self, workspace_name: &str) { + let path = self.path_for(workspace_name); + fs::remove_file(path).ok(); + self.cache.remove(workspace_name); + } +} + +impl Default for LayoutStore { + fn default() -> Self { + Self::new() + } +} diff --git a/OPUS-tauri-app/src-tauri/src/store/migrations.rs b/OPUS-tauri-app/src-tauri/src/store/migrations.rs new file mode 100644 index 00000000..bd65ad63 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/store/migrations.rs @@ -0,0 +1,25 @@ +use std::collections::HashMap; + +use crate::jobs::migration::MigrationJobHandle; +use crate::types::job::MigrationProgress; + +/// Tracks active and completed migration jobs. +pub struct MigrationStore { + pub active_jobs: HashMap, + pub history: Vec, +} + +impl MigrationStore { + pub fn new() -> Self { + Self { + active_jobs: HashMap::new(), + history: Vec::new(), + } + } +} + +impl Default for MigrationStore { + fn default() -> Self { + Self::new() + } +} diff --git a/OPUS-tauri-app/src-tauri/src/store/mod.rs b/OPUS-tauri-app/src-tauri/src/store/mod.rs new file mode 100644 index 00000000..20501ff7 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/store/mod.rs @@ -0,0 +1,4 @@ +pub mod layout; +pub mod migrations; +pub mod ui_context; +pub mod workset_profiles; diff --git a/OPUS-tauri-app/src-tauri/src/store/ui_context.rs b/OPUS-tauri-app/src-tauri/src/store/ui_context.rs new file mode 100644 index 00000000..c5fea3de --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/store/ui_context.rs @@ -0,0 +1,77 @@ +use crate::types::error::ErrorEnvelope; +use std::collections::HashMap; +use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +struct UiContextData { + #[serde(skip_serializing_if = "Option::is_none")] + active_workset_id: Option, + #[serde(default)] + last_workspace_per_workset: HashMap, +} + +#[derive(Debug, Default)] +pub struct UiContextStore { + data: UiContextData, + path: PathBuf, +} + +impl UiContextStore { + pub fn load() -> Result { + let path = Self::store_path(); + let data = if path.exists() { + let raw = std::fs::read_to_string(&path).map_err(|e| { + ErrorEnvelope::config("ui_context.load", format!("Failed to read: {e}")) + })?; + serde_json::from_str(&raw).unwrap_or_default() + } else { + UiContextData::default() + }; + Ok(Self { data, path }) + } + + fn store_path() -> PathBuf { + let base = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + base.join(".workset").join("ui_context.json") + } + + fn save(&self) -> Result<(), ErrorEnvelope> { + if let Some(parent) = self.path.parent() { + std::fs::create_dir_all(parent).map_err(|e| { + ErrorEnvelope::config("ui_context.save", format!("Failed to create dir: {e}")) + })?; + } + let raw = serde_json::to_string_pretty(&self.data).map_err(|e| { + ErrorEnvelope::config("ui_context.save", format!("Failed to serialize: {e}")) + })?; + std::fs::write(&self.path, raw).map_err(|e| { + ErrorEnvelope::config("ui_context.save", format!("Failed to write: {e}")) + })?; + Ok(()) + } + + pub fn active_workset_id(&self) -> Option<&str> { + self.data.active_workset_id.as_deref() + } + + pub fn last_workspace_for(&self, workset_id: &str) -> Option<&str> { + self.data.last_workspace_per_workset.get(workset_id).map(|s| s.as_str()) + } + + pub fn set_active_workset(&mut self, workset_id: &str) -> Result<(), ErrorEnvelope> { + self.data.active_workset_id = Some(workset_id.to_string()); + self.save() + } + + pub fn set_active_workspace( + &mut self, + workset_id: &str, + workspace_name: &str, + ) -> Result<(), ErrorEnvelope> { + self.data + .last_workspace_per_workset + .insert(workset_id.to_string(), workspace_name.to_string()); + self.save() + } +} diff --git a/OPUS-tauri-app/src-tauri/src/store/workset_profiles.rs b/OPUS-tauri-app/src-tauri/src/store/workset_profiles.rs new file mode 100644 index 00000000..b7e0c609 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/store/workset_profiles.rs @@ -0,0 +1,172 @@ +use crate::types::error::ErrorEnvelope; +use crate::types::workset::{WorksetDefaults, WorksetProfile}; +use std::path::PathBuf; + +#[derive(Debug, Default)] +pub struct WorksetProfileStore { + profiles: Vec, + path: PathBuf, +} + +impl WorksetProfileStore { + pub fn load() -> Result { + let path = Self::store_path(); + let profiles = if path.exists() { + let data = std::fs::read_to_string(&path).map_err(|e| { + ErrorEnvelope::config("workset_profiles.load", format!("Failed to read store: {e}")) + })?; + serde_json::from_str(&data).unwrap_or_default() + } else { + Vec::new() + }; + Ok(Self { profiles, path }) + } + + fn store_path() -> PathBuf { + let base = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + base.join(".workset").join("ui_worksets.json") + } + + fn save(&self) -> Result<(), ErrorEnvelope> { + if let Some(parent) = self.path.parent() { + std::fs::create_dir_all(parent).map_err(|e| { + ErrorEnvelope::config("workset_profiles.save", format!("Failed to create dir: {e}")) + })?; + } + let data = serde_json::to_string_pretty(&self.profiles).map_err(|e| { + ErrorEnvelope::config("workset_profiles.save", format!("Failed to serialize: {e}")) + })?; + std::fs::write(&self.path, data).map_err(|e| { + ErrorEnvelope::config("workset_profiles.save", format!("Failed to write: {e}")) + })?; + Ok(()) + } + + pub fn list(&self) -> Vec { + self.profiles.clone() + } + + pub fn create( + &mut self, + name: &str, + defaults: Option, + ) -> Result { + let id = self.slugify_unique(name); + let now = chrono::Utc::now().to_rfc3339(); + let profile = WorksetProfile { + id, + name: name.to_string(), + repos: Vec::new(), + workspace_ids: Vec::new(), + defaults, + created_at: now.clone(), + updated_at: now, + }; + self.profiles.push(profile.clone()); + self.save()?; + Ok(profile) + } + + pub fn update( + &mut self, + id: &str, + name: Option<&str>, + defaults: Option, + ) -> Result { + let profile = self.profiles.iter_mut().find(|p| p.id == id).ok_or_else(|| { + ErrorEnvelope::config("workset_profiles.update", format!("Workset '{id}' not found")) + })?; + if let Some(n) = name { + profile.name = n.to_string(); + } + if let Some(d) = defaults { + profile.defaults = Some(d); + } + profile.updated_at = chrono::Utc::now().to_rfc3339(); + let result = profile.clone(); + self.save()?; + Ok(result) + } + + pub fn delete(&mut self, id: &str) -> Result<(), ErrorEnvelope> { + let before = self.profiles.len(); + self.profiles.retain(|p| p.id != id); + if self.profiles.len() == before { + return Err(ErrorEnvelope::config( + "workset_profiles.delete", + format!("Workset '{id}' not found"), + )); + } + self.save()?; + Ok(()) + } + + pub fn add_repo(&mut self, workset_id: &str, source: &str) -> Result { + let profile = self.profiles.iter_mut().find(|p| p.id == workset_id).ok_or_else(|| { + ErrorEnvelope::config("workset_profiles.add_repo", format!("Workset '{workset_id}' not found")) + })?; + if !profile.repos.contains(&source.to_string()) { + profile.repos.push(source.to_string()); + profile.updated_at = chrono::Utc::now().to_rfc3339(); + } + let result = profile.clone(); + self.save()?; + Ok(result) + } + + pub fn remove_repo(&mut self, workset_id: &str, source: &str) -> Result { + let profile = self.profiles.iter_mut().find(|p| p.id == workset_id).ok_or_else(|| { + ErrorEnvelope::config("workset_profiles.remove_repo", format!("Workset '{workset_id}' not found")) + })?; + profile.repos.retain(|r| r != source); + profile.updated_at = chrono::Utc::now().to_rfc3339(); + let result = profile.clone(); + self.save()?; + Ok(result) + } + + pub fn add_workspace(&mut self, workset_id: &str, workspace_name: &str) -> Result<(), ErrorEnvelope> { + let profile = self.profiles.iter_mut().find(|p| p.id == workset_id).ok_or_else(|| { + ErrorEnvelope::config("workset_profiles.add_workspace", format!("Workset '{workset_id}' not found")) + })?; + if !profile.workspace_ids.contains(&workspace_name.to_string()) { + profile.workspace_ids.push(workspace_name.to_string()); + profile.updated_at = chrono::Utc::now().to_rfc3339(); + } + self.save() + } + + pub fn remove_workspace(&mut self, workset_id: &str, workspace_name: &str) -> Result<(), ErrorEnvelope> { + let profile = self.profiles.iter_mut().find(|p| p.id == workset_id).ok_or_else(|| { + ErrorEnvelope::config("workset_profiles.remove_workspace", format!("Workset '{workset_id}' not found")) + })?; + profile.workspace_ids.retain(|w| w != workspace_name); + profile.updated_at = chrono::Utc::now().to_rfc3339(); + self.save() + } + + fn slugify_unique(&self, name: &str) -> String { + let base: String = name + .to_lowercase() + .chars() + .map(|c| if c.is_alphanumeric() { c } else { '-' }) + .collect::() + .trim_matches('-') + .to_string(); + let base = if base.is_empty() { + "workset".to_string() + } else { + base + }; + if !self.profiles.iter().any(|p| p.id == base) { + return base; + } + for i in 2.. { + let candidate = format!("{base}-{i}"); + if !self.profiles.iter().any(|p| p.id == candidate) { + return candidate; + } + } + unreachable!() + } +} diff --git a/OPUS-tauri-app/src-tauri/src/terminal_manager.rs b/OPUS-tauri-app/src-tauri/src/terminal_manager.rs new file mode 100644 index 00000000..d8418242 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/terminal_manager.rs @@ -0,0 +1,258 @@ +use std::collections::{HashMap, VecDeque}; +use std::io::{Read, Write}; +use std::sync::{Arc, Mutex}; + +use portable_pty::{native_pty_system, CommandBuilder, MasterPty, PtySize}; +use serde::Serialize; +use tauri::ipc::Channel; + +const RING_BUFFER_MAX_BYTES: usize = 64 * 1024; + +#[derive(Clone, Serialize)] +#[serde(tag = "type")] +pub enum PtyEvent { + Data { data: String }, + Closed { exit_code: Option }, + Error { message: String }, +} + +struct ManagedTerminal { + master: Box, + writer: Box, + subscribers: Vec>, + ring_buffer: VecDeque, + alive: bool, + cancel: Arc, +} + +pub struct TerminalManager { + sessions: HashMap, +} + +impl TerminalManager { + pub fn new() -> Self { + Self { + sessions: HashMap::new(), + } + } + + /// Spawn a new PTY process with the given terminal_id. + /// The `manager_ref` is needed so the reader thread can broadcast output. + pub fn spawn( + &mut self, + terminal_id: &str, + cwd: &str, + channel: Channel, + manager_ref: Arc>, + ) -> Result<(), String> { + let pty_system = native_pty_system(); + let pair = pty_system + .openpty(PtySize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }) + .map_err(|e| format!("Failed to open PTY: {}", e))?; + + let mut cmd = CommandBuilder::new_default_prog(); + cmd.cwd(cwd); + + let child = pair + .slave + .spawn_command(cmd) + .map_err(|e| format!("Failed to spawn shell: {}", e))?; + + drop(pair.slave); + + let writer = pair + .master + .take_writer() + .map_err(|e| format!("Failed to get PTY writer: {}", e))?; + + let reader = pair + .master + .try_clone_reader() + .map_err(|e| format!("Failed to get PTY reader: {}", e))?; + + let cancel = Arc::new(std::sync::atomic::AtomicBool::new(false)); + + let managed = ManagedTerminal { + master: pair.master, + writer, + subscribers: vec![channel], + ring_buffer: VecDeque::with_capacity(RING_BUFFER_MAX_BYTES), + alive: true, + cancel: cancel.clone(), + }; + + let tid = terminal_id.to_string(); + self.sessions.insert(tid.clone(), managed); + + std::thread::spawn(move || { + reader_loop(tid, reader, child, cancel, manager_ref); + }); + + Ok(()) + } + + /// Attach a new Channel subscriber to an existing terminal. + /// Replays the ring buffer so the UI gets immediate screen content. + pub fn attach( + &mut self, + terminal_id: &str, + channel: Channel, + ) -> Result<(), String> { + let session = self + .sessions + .get_mut(terminal_id) + .ok_or_else(|| format!("No terminal with id: {}", terminal_id))?; + + // Replay ring buffer + if !session.ring_buffer.is_empty() { + let (front, back) = session.ring_buffer.as_slices(); + let mut combined = Vec::with_capacity(front.len() + back.len()); + combined.extend_from_slice(front); + combined.extend_from_slice(back); + if let Ok(data) = String::from_utf8(combined) { + channel.send(PtyEvent::Data { data }).ok(); + } + } + + if !session.alive { + channel.send(PtyEvent::Closed { exit_code: None }).ok(); + } + + session.subscribers.push(channel); + Ok(()) + } + + /// Remove the most recently added subscriber (the one being detached). + /// The PTY keeps running. + pub fn detach(&mut self, terminal_id: &str) -> Result<(), String> { + let session = self + .sessions + .get_mut(terminal_id) + .ok_or_else(|| format!("No terminal with id: {}", terminal_id))?; + + if !session.subscribers.is_empty() { + session.subscribers.pop(); + } + Ok(()) + } + + /// Write input data to the PTY. + pub fn write(&mut self, terminal_id: &str, data: &str) -> Result<(), String> { + let session = self + .sessions + .get_mut(terminal_id) + .ok_or_else(|| format!("No terminal with id: {}", terminal_id))?; + + session + .writer + .write_all(data.as_bytes()) + .map_err(|e| format!("Write failed: {}", e))?; + session + .writer + .flush() + .map_err(|e| format!("Flush failed: {}", e))?; + Ok(()) + } + + /// Resize the PTY. + pub fn resize(&mut self, terminal_id: &str, cols: u32, rows: u32) -> Result<(), String> { + let session = self + .sessions + .get_mut(terminal_id) + .ok_or_else(|| format!("No terminal with id: {}", terminal_id))?; + + session + .master + .resize(PtySize { + rows: rows as u16, + cols: cols as u16, + pixel_width: 0, + pixel_height: 0, + }) + .map_err(|e| format!("Resize failed: {}", e))?; + Ok(()) + } + + /// Kill the PTY process and remove the session. + pub fn kill(&mut self, terminal_id: &str) -> Result<(), String> { + if let Some(session) = self.sessions.remove(terminal_id) { + session + .cancel + .store(true, std::sync::atomic::Ordering::Relaxed); + // Dropping master/writer closes the PTY fd, causing the child to exit + } + Ok(()) + } + + pub fn is_alive(&self, terminal_id: &str) -> bool { + self.sessions.get(terminal_id).map(|s| s.alive).unwrap_or(false) + } + + pub fn exists(&self, terminal_id: &str) -> bool { + self.sessions.contains_key(terminal_id) + } + + fn push_to_ring_buffer(session: &mut ManagedTerminal, data: &[u8]) { + for &byte in data { + if session.ring_buffer.len() >= RING_BUFFER_MAX_BYTES { + session.ring_buffer.pop_front(); + } + session.ring_buffer.push_back(byte); + } + } + + fn broadcast(session: &ManagedTerminal, event: &PtyEvent) { + for sub in &session.subscribers { + sub.send(event.clone()).ok(); + } + } +} + +/// Background thread that reads PTY output and broadcasts to subscribers. +fn reader_loop( + terminal_id: String, + mut reader: Box, + mut child: Box, + cancel: Arc, + manager: Arc>, +) { + let mut buf = [0u8; 4096]; + + loop { + if cancel.load(std::sync::atomic::Ordering::Relaxed) { + break; + } + + match reader.read(&mut buf) { + Ok(0) => break, + Ok(n) => { + let chunk = &buf[..n]; + let data = String::from_utf8_lossy(chunk).to_string(); + let event = PtyEvent::Data { data }; + + if let Ok(mut mgr) = manager.lock() { + if let Some(session) = mgr.sessions.get_mut(&terminal_id) { + TerminalManager::push_to_ring_buffer(session, chunk); + TerminalManager::broadcast(session, &event); + } + } + } + Err(_) => break, + } + } + + let exit_code = child.wait().ok().map(|status| if status.success() { 0 } else { 1 }); + let event = PtyEvent::Closed { exit_code }; + + if let Ok(mut mgr) = manager.lock() { + if let Some(session) = mgr.sessions.get_mut(&terminal_id) { + session.alive = false; + TerminalManager::broadcast(session, &event); + } + } +} diff --git a/OPUS-tauri-app/src-tauri/src/types/context.rs b/OPUS-tauri-app/src-tauri/src/types/context.rs new file mode 100644 index 00000000..c60a39f4 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/context.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct ActiveContext { + #[serde(skip_serializing_if = "Option::is_none")] + pub active_workset_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub active_workspace: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/diagnostics.rs b/OPUS-tauri-app/src-tauri/src/types/diagnostics.rs new file mode 100644 index 00000000..68debce5 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/diagnostics.rs @@ -0,0 +1,31 @@ +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +pub struct EnvSnapshot { + pub path: String, + pub shell: String, + pub home: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub ssh_auth_sock: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub git_ssh_command: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub git_askpass: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub gh_config_dir: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub gh_auth_summary: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SessiondStatus { + pub running: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub socket_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_restart: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/diff.rs b/OPUS-tauri-app/src-tauri/src/types/diff.rs new file mode 100644 index 00000000..384e1c8d --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/diff.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiffSummary { + pub files: Vec, + pub total_added: u32, + pub total_removed: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiffFileSummary { + pub path: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub prev_path: Option, + pub added: u32, + pub removed: u32, + pub status: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub binary: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct FilePatch { + pub patch: String, + pub truncated: bool, + pub total_bytes: u64, + pub total_lines: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub binary: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/error.rs b/OPUS-tauri-app/src-tauri/src/types/error.rs new file mode 100644 index 00000000..543d8921 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/error.rs @@ -0,0 +1,56 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ErrorEnvelope { + pub category: String, + pub operation: String, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub details: Option, + pub retryable: bool, + #[serde(default)] + pub suggested_actions: Vec, +} + +impl ErrorEnvelope { + pub fn new(category: &str, operation: &str, message: impl Into) -> Self { + Self { + category: category.to_string(), + operation: operation.to_string(), + message: message.into(), + details: None, + retryable: false, + suggested_actions: Vec::new(), + } + } + + pub fn with_details(mut self, details: impl Into) -> Self { + self.details = Some(details.into()); + self + } + + pub fn retryable(mut self) -> Self { + self.retryable = true; + self + } + + pub fn config(operation: &str, message: impl Into) -> Self { + Self::new("config", operation, message) + } + + pub fn runtime(operation: &str, message: impl Into) -> Self { + Self::new("runtime", operation, message) + } + + pub fn unknown(operation: &str, message: impl Into) -> Self { + Self::new("unknown", operation, message) + } +} + +impl std::fmt::Display for ErrorEnvelope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[{}] {}: {}", self.category, self.operation, self.message) + } +} + +impl std::error::Error for ErrorEnvelope {} diff --git a/OPUS-tauri-app/src-tauri/src/types/job.rs b/OPUS-tauri-app/src-tauri/src/types/job.rs new file mode 100644 index 00000000..7d5a612e --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/job.rs @@ -0,0 +1,22 @@ +use serde::Serialize; +use super::error::ErrorEnvelope; + +#[derive(Debug, Clone, Serialize)] +pub struct MigrationJobRef { + pub job_id: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct MigrationProgress { + pub job_id: String, + pub state: String, + pub workspaces: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct WorkspaceMigrationStatus { + pub workspace_name: String, + pub state: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/layout.rs b/OPUS-tauri-app/src-tauri/src/types/layout.rs new file mode 100644 index 00000000..675072c7 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/layout.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TerminalLayout { + pub version: u32, + pub root: LayoutNode, + #[serde(skip_serializing_if = "Option::is_none")] + pub focused_pane_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "kind")] +pub enum LayoutNode { + #[serde(rename = "pane")] + Pane { + id: String, + tabs: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + active_tab_id: Option, + }, + #[serde(rename = "split")] + Split { + id: String, + direction: String, + ratio: f64, + first: Box, + second: Box, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LayoutTab { + pub id: String, + pub terminal_id: String, + pub title: String, + pub kind: String, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/mod.rs b/OPUS-tauri-app/src-tauri/src/types/mod.rs new file mode 100644 index 00000000..27509b49 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/mod.rs @@ -0,0 +1,10 @@ +pub mod context; +pub mod diagnostics; +pub mod diff; +pub mod error; +pub mod job; +pub mod layout; +pub mod pty; +pub mod repo; +pub mod workset; +pub mod workspace; diff --git a/OPUS-tauri-app/src-tauri/src/types/pty.rs b/OPUS-tauri-app/src-tauri/src/types/pty.rs new file mode 100644 index 00000000..586a9a1d --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/pty.rs @@ -0,0 +1,30 @@ +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +pub struct PtyCreateResult { + pub terminal_id: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct BootstrapPayload { + pub workspace_name: String, + pub terminal_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub backlog: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub backlog_truncated: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub next_offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub alt_screen: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mouse: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mouse_sgr: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub safe_to_replay: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_credit: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/repo.rs b/OPUS-tauri-app/src-tauri/src/types/repo.rs new file mode 100644 index 00000000..369d1d9c --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/repo.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +/// Raw JSON from `workset repo ls --json` +#[derive(Debug, Clone, Deserialize)] +pub struct RepoListEntry { + pub name: String, + pub local_path: String, + #[serde(default)] + pub managed: bool, + pub repo_dir: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub remote: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_branch: Option, +} + +/// Enriched repo info sent to the frontend +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RepoInstance { + pub name: String, + pub worktree_path: String, + pub repo_dir: String, + #[serde(default)] + pub missing: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_branch: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_remote: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/workset.rs b/OPUS-tauri-app/src-tauri/src/types/workset.rs new file mode 100644 index 00000000..da210f46 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/workset.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WorksetProfile { + pub id: String, + pub name: String, + #[serde(default)] + pub repos: Vec, + #[serde(default)] + pub workspace_ids: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub defaults: Option, + pub created_at: String, + pub updated_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WorksetDefaults { + #[serde(skip_serializing_if = "Option::is_none")] + pub base_branch: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_remote: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub workspace_root: Option, +} diff --git a/OPUS-tauri-app/src-tauri/src/types/workspace.rs b/OPUS-tauri-app/src-tauri/src/types/workspace.rs new file mode 100644 index 00000000..80071143 --- /dev/null +++ b/OPUS-tauri-app/src-tauri/src/types/workspace.rs @@ -0,0 +1,46 @@ +use serde::{Deserialize, Serialize}; +use super::error::ErrorEnvelope; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WorkspaceSummary { + pub name: String, + pub path: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub created_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_used: Option, + #[serde(default)] + pub archived: bool, + #[serde(default)] + pub pinned: bool, + #[serde(default)] + pub pin_order: i32, + #[serde(default)] + pub expanded: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub struct WorkspaceCreateJobRef { + pub job_id: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct WorkspaceCreateProgress { + pub job_id: String, + pub state: String, + pub repos: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub workspace_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub workspace_path: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct RepoProvisionStatus { + pub name: String, + pub state: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub step: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} diff --git a/OPUS-tauri-app/src-tauri/tauri.conf.json b/OPUS-tauri-app/src-tauri/tauri.conf.json new file mode 100644 index 00000000..8236dd1c --- /dev/null +++ b/OPUS-tauri-app/src-tauri/tauri.conf.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Workset Desktop", + "version": "0.1.0", + "identifier": "com.workset.desktop", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Workset Desktop", + "width": 1440, + "height": 900, + "minWidth": 1024, + "minHeight": 640, + "decorations": true, + "transparent": false, + "titleBarStyle": "Overlay", + "hiddenTitle": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": ["app", "dmg"], + "icon": [ + "icons/icon.icns", + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png" + ] + } +} diff --git a/OPUS-tauri-app/src/App.tsx b/OPUS-tauri-app/src/App.tsx new file mode 100644 index 00000000..7249408e --- /dev/null +++ b/OPUS-tauri-app/src/App.tsx @@ -0,0 +1,66 @@ +import { useEffect } from 'react'; +import { useAppStore } from '@/state/store'; +import { AppShell } from '@/components/layout/AppShell'; +import { TopChrome } from '@/components/layout/TopChrome'; +import { IconRail } from '@/components/layout/IconRail'; +import { SecondarySidebar } from '@/components/layout/SecondarySidebar'; +import { RightPanel } from '@/components/layout/RightPanel'; +import { CommandCenterPage } from '@/components/pages/CommandCenter/CommandCenterPage'; +import { SpacesPage } from '@/components/pages/Spaces/SpacesPage'; +import { SettingsPage } from '@/components/pages/Settings/SettingsPage'; +import { WorksetCreateModal } from '@/components/modals/WorksetCreateModal'; +import { CreateWorkspaceModal } from '@/components/pages/Spaces/CreateWorkspaceModal'; +import { MigrationStatusModal } from '@/components/modals/MigrationStatusModal'; +import { RepoRemoveConfirmModal } from '@/components/modals/RepoRemoveConfirmModal'; +import { CommandPalette } from '@/components/modals/CommandPalette'; +import { ThemeProvider } from '@/styles/ThemeProvider'; +import { useGlobalShortcuts } from '@/hooks/useGlobalShortcuts'; +import '@/commands/appCommands'; + +function MainContent() { + const activePage = useAppStore((s) => s.activePage); + + return ( + <> +
+ +
+
+ +
+
+ +
+ + ); +} + +export default function App() { + const loadWorksets = useAppStore((s) => s.loadWorksets); + const activeModal = useAppStore((s) => s.activeModal); + const activePage = useAppStore((s) => s.activePage); + + useGlobalShortcuts(); + + useEffect(() => { + loadWorksets(); + }, [loadWorksets]); + + return ( + <> + + } + rail={} + sidebar={} + main={} + rightPanel={activePage === 'spaces' ? : undefined} + /> + {activeModal?.type === 'command-palette' && } + {activeModal?.type === 'create-workset' && } + {activeModal?.type === 'create-workspace' && } + {activeModal?.type === 'migration-status' && } + {activeModal?.type === 'repo-remove-confirm' && } + + ); +} diff --git a/OPUS-tauri-app/src/api/context.ts b/OPUS-tauri-app/src/api/context.ts new file mode 100644 index 00000000..7bd21540 --- /dev/null +++ b/OPUS-tauri-app/src/api/context.ts @@ -0,0 +1,14 @@ +import { invoke } from './invoke'; +import type { ActiveContext } from '@/types/context'; + +export function getContext(): Promise { + return invoke('context_get'); +} + +export function setActiveWorkset(worksetId: string): Promise { + return invoke('context_set_active_workset', { worksetId }); +} + +export function setActiveWorkspace(workspaceName: string): Promise { + return invoke('context_set_active_workspace', { workspaceName }); +} diff --git a/OPUS-tauri-app/src/api/diagnostics.ts b/OPUS-tauri-app/src/api/diagnostics.ts new file mode 100644 index 00000000..76a89f41 --- /dev/null +++ b/OPUS-tauri-app/src/api/diagnostics.ts @@ -0,0 +1,22 @@ +import { invoke } from './invoke'; +import type { EnvSnapshot, SessiondStatus, CliStatus } from '@/types/diagnostics'; + +export function envSnapshot(): Promise { + return invoke('diagnostics_env_snapshot', {}); +} + +export function reloadLoginEnv(): Promise { + return invoke('diagnostics_reload_login_env', {}); +} + +export function sessiondStatus(): Promise { + return invoke('diagnostics_sessiond_status', {}); +} + +export function sessiondRestart(): Promise { + return invoke('diagnostics_sessiond_restart', {}); +} + +export function cliStatus(): Promise { + return invoke('diagnostics_cli_status', {}); +} diff --git a/OPUS-tauri-app/src/api/diff.ts b/OPUS-tauri-app/src/api/diff.ts new file mode 100644 index 00000000..0d723c16 --- /dev/null +++ b/OPUS-tauri-app/src/api/diff.ts @@ -0,0 +1,31 @@ +import { invoke } from './invoke'; +import type { DiffSummary, FilePatch } from '@/types/diff'; + +export function diffSummary( + workspaceName: string, + repo: string, + repoPath: string, +): Promise { + return invoke('diff_summary', { workspaceName, repo, repoPath }); +} + +export function diffFilePatch( + repoPath: string, + path: string, + prevPath: string | undefined, + status: string, +): Promise { + return invoke('diff_file_patch', { repoPath, path, prevPath, status }); +} + +export function diffWatchStart( + workspaceName: string, + repo: string, + repoPath: string, +): Promise { + return invoke('diff_watch_start', { workspaceName, repo, repoPath }); +} + +export function diffWatchStop(workspaceName: string, repo: string): Promise { + return invoke('diff_watch_stop', { workspaceName, repo }); +} diff --git a/OPUS-tauri-app/src/api/events.ts b/OPUS-tauri-app/src/api/events.ts new file mode 100644 index 00000000..8d427e82 --- /dev/null +++ b/OPUS-tauri-app/src/api/events.ts @@ -0,0 +1,5 @@ +import { listen, type UnlistenFn } from '@tauri-apps/api/event'; + +export function onEvent(event: string, handler: (payload: T) => void): Promise { + return listen(event, (e) => handler(e.payload)); +} diff --git a/OPUS-tauri-app/src/api/github.ts b/OPUS-tauri-app/src/api/github.ts new file mode 100644 index 00000000..94ef7fee --- /dev/null +++ b/OPUS-tauri-app/src/api/github.ts @@ -0,0 +1,18 @@ +import { invoke } from './invoke'; +import type { GitHubRepo, GitHubAuthStatus, GitHubAccount } from '@/types/github'; + +export function listGitHubRepos(): Promise { + return invoke('github_list_repos'); +} + +export function githubAuthStatus(): Promise { + return invoke('github_auth_status'); +} + +export function listGitHubAccounts(): Promise { + return invoke('github_list_accounts'); +} + +export function switchGitHubAccount(user: string): Promise { + return invoke('github_switch_account', { user }); +} diff --git a/OPUS-tauri-app/src/api/invoke.ts b/OPUS-tauri-app/src/api/invoke.ts new file mode 100644 index 00000000..0124f8ff --- /dev/null +++ b/OPUS-tauri-app/src/api/invoke.ts @@ -0,0 +1,5 @@ +import { invoke as tauriInvoke } from '@tauri-apps/api/core'; + +export async function invoke(cmd: string, args?: Record): Promise { + return tauriInvoke(cmd, args); +} diff --git a/OPUS-tauri-app/src/api/migrations.ts b/OPUS-tauri-app/src/api/migrations.ts new file mode 100644 index 00000000..e4519177 --- /dev/null +++ b/OPUS-tauri-app/src/api/migrations.ts @@ -0,0 +1,26 @@ +import { invoke } from './invoke'; +import type { MigrationJobRef } from '@/types/jobs'; + +export type MigrationStartOptions = { + worksetId: string; + repoUrl: string; + action: 'add' | 'remove'; + workspaceNames: string[]; + deleteWorktrees?: boolean; + deleteLocal?: boolean; +}; + +export function migrationStart(opts: MigrationStartOptions): Promise { + return invoke('migration_start', { + worksetId: opts.worksetId, + repoUrl: opts.repoUrl, + action: opts.action, + workspaceNames: opts.workspaceNames, + deleteWorktrees: opts.deleteWorktrees, + deleteLocal: opts.deleteLocal, + }); +} + +export function migrationCancel(jobId: string): Promise { + return invoke('migration_cancel', { jobId }); +} diff --git a/OPUS-tauri-app/src/api/pty.ts b/OPUS-tauri-app/src/api/pty.ts new file mode 100644 index 00000000..4e3d8865 --- /dev/null +++ b/OPUS-tauri-app/src/api/pty.ts @@ -0,0 +1,55 @@ +import { invoke } from './invoke'; +import { Channel } from '@tauri-apps/api/core'; +import type { TerminalLayout } from '@/types/layout'; + +// --- New terminal API (portable-pty based) --- + +export type PtyEvent = + | { type: 'Data'; data: string } + | { type: 'Closed'; exit_code: number | null } + | { type: 'Error'; message: string }; + +export function terminalSpawn( + terminalId: string, + cwd: string, + onEvent: (e: PtyEvent) => void, +): Promise { + const channel = new Channel(); + channel.onmessage = onEvent; + return invoke('terminal_spawn', { terminalId, cwd, channel }); +} + +export function terminalAttach( + terminalId: string, + onEvent: (e: PtyEvent) => void, +): Promise { + const channel = new Channel(); + channel.onmessage = onEvent; + return invoke('terminal_attach', { terminalId, channel }); +} + +export function terminalDetach(terminalId: string): Promise { + return invoke('terminal_detach', { terminalId }); +} + +export function terminalWrite(terminalId: string, data: string): Promise { + return invoke('terminal_write', { terminalId, data }); +} + +export function terminalResize(terminalId: string, cols: number, rows: number): Promise { + return invoke('terminal_resize', { terminalId, cols, rows }); +} + +export function terminalKill(terminalId: string): Promise { + return invoke('terminal_kill', { terminalId }); +} + +// --- Layout persistence (unchanged) --- + +export function layoutGet(workspaceName: string): Promise { + return invoke('layout_get', { workspaceName }); +} + +export function layoutSave(workspaceName: string, layout: TerminalLayout): Promise { + return invoke('layout_save', { workspaceName, layout }); +} diff --git a/OPUS-tauri-app/src/api/repos.ts b/OPUS-tauri-app/src/api/repos.ts new file mode 100644 index 00000000..60c3ad6e --- /dev/null +++ b/OPUS-tauri-app/src/api/repos.ts @@ -0,0 +1,6 @@ +import { invoke } from './invoke'; +import type { RepoInstance } from '@/types/repo'; + +export function listWorkspaceRepos(workspaceName: string): Promise { + return invoke('workspace_repos_list', { workspaceName }); +} diff --git a/OPUS-tauri-app/src/api/worksets.ts b/OPUS-tauri-app/src/api/worksets.ts new file mode 100644 index 00000000..e7889b50 --- /dev/null +++ b/OPUS-tauri-app/src/api/worksets.ts @@ -0,0 +1,26 @@ +import { invoke } from './invoke'; +import type { WorksetProfile, WorksetDefaults } from '@/types/workset'; + +export function listWorksets(): Promise { + return invoke('worksets_list'); +} + +export function createWorkset(name: string, defaults?: WorksetDefaults): Promise { + return invoke('worksets_create', { name, defaults }); +} + +export function updateWorkset(id: string, name?: string, defaults?: WorksetDefaults): Promise { + return invoke('worksets_update', { id, name, defaults }); +} + +export function deleteWorkset(id: string): Promise { + return invoke('worksets_delete', { id }); +} + +export function addWorksetRepo(worksetId: string, source: string): Promise { + return invoke('worksets_repos_add', { worksetId, source }); +} + +export function removeWorksetRepo(worksetId: string, source: string): Promise { + return invoke('worksets_repos_remove', { worksetId, source }); +} diff --git a/OPUS-tauri-app/src/api/workspaces.ts b/OPUS-tauri-app/src/api/workspaces.ts new file mode 100644 index 00000000..13e9a8e5 --- /dev/null +++ b/OPUS-tauri-app/src/api/workspaces.ts @@ -0,0 +1,18 @@ +import { invoke } from './invoke'; +import type { WorkspaceSummary, WorkspaceCreateJobRef, WorkspaceCreateProgress } from '@/types/workspace'; + +export function listWorkspaces(worksetId: string): Promise { + return invoke('workspaces_list', { worksetId }); +} + +export function createWorkspace(worksetId: string, name: string, path?: string): Promise { + return invoke('workspaces_create', { worksetId, name, path }); +} + +export function getCreateStatus(jobId: string): Promise { + return invoke('workspaces_create_status', { jobId }); +} + +export function deleteWorkspace(worksetId: string, workspaceName: string, del?: boolean): Promise { + return invoke('workspaces_delete', { worksetId, workspaceName, delete: del }); +} diff --git a/OPUS-tauri-app/src/commands/appCommands.ts b/OPUS-tauri-app/src/commands/appCommands.ts new file mode 100644 index 00000000..3cac9498 --- /dev/null +++ b/OPUS-tauri-app/src/commands/appCommands.ts @@ -0,0 +1,399 @@ +import { + LayoutGrid, + Layers, + Settings, + Plus, + Terminal, + PanelRightClose, + Command, + ArrowUp, + ArrowDown, + ArrowLeft, + ArrowRight, + X, + GitBranch, + Sun, + Moon, + Palette, + Columns2, + Rows2, + PanelLeft, + Focus, +} from 'lucide-react'; +import { registerCommands } from './registry'; +import { findPane, collectPaneIds } from './layoutUtils'; +import { useAppStore } from '@/state/store'; +import { themes, getThemeById } from '@/styles/themes'; + +const store = () => useAppStore.getState(); + +function focusPaneTerminal(paneId: string) { + requestAnimationFrame(() => { + const el = document.querySelector(`[data-pane-id="${paneId}"]`); + const textarea = el?.querySelector('.xterm-helper-textarea') as HTMLElement | null; + textarea?.focus(); + }); +} + +registerCommands([ + // ── Navigation ────────────────────────────────────────────── + { + id: 'nav.command-center', + label: 'Go to Command Center', + category: 'navigation', + icon: LayoutGrid, + shortcut: { modifiers: ['meta'], key: '1' }, + execute: () => store().setActivePage('command-center'), + }, + { + id: 'nav.spaces', + label: 'Go to Spaces', + category: 'navigation', + icon: Layers, + shortcut: { modifiers: ['meta'], key: '2' }, + execute: () => store().setActivePage('spaces'), + }, + { + id: 'nav.settings', + label: 'Go to Settings', + category: 'navigation', + icon: Settings, + shortcut: { modifiers: ['meta'], key: '3' }, + execute: () => store().setActivePage('settings'), + }, + + // ── Workset ───────────────────────────────────────────────── + { + id: 'workset.create', + label: 'Create New Workset', + category: 'workset', + icon: Plus, + execute: () => store().openModal('create-workset'), + }, + + { + id: 'workset.add-repo', + label: 'Add Repository', + category: 'workset', + icon: GitBranch, + when: () => store().activeWorksetId !== null, + execute: () => { + const s = store(); + s.setActivePage('command-center'); + s.setCommandCenterSection('repositories'); + }, + }, + + // ── Workspace ─────────────────────────────────────────────── + { + id: 'workspace.create', + label: 'Create New Workspace', + category: 'workspace', + icon: Plus, + when: () => store().activeWorksetId !== null, + execute: () => store().openModal('create-workspace'), + }, + { + id: 'workspace.prev', + label: 'Previous Workspace', + category: 'workspace', + icon: ArrowUp, + shortcut: { modifiers: ['meta'], key: 'arrowup' }, + when: () => + store().activePage === 'spaces' && + store().activeWorkspaceName !== null && + store().workspaces.length > 1, + execute: () => { + const s = store(); + const idx = s.workspaces.findIndex((ws) => ws.name === s.activeWorkspaceName); + const next = (idx - 1 + s.workspaces.length) % s.workspaces.length; + s.setActiveWorkspace(s.workspaces[next].name); + }, + }, + { + id: 'workspace.next', + label: 'Next Workspace', + category: 'workspace', + icon: ArrowDown, + shortcut: { modifiers: ['meta'], key: 'arrowdown' }, + when: () => + store().activePage === 'spaces' && + store().activeWorkspaceName !== null && + store().workspaces.length > 1, + execute: () => { + const s = store(); + const idx = s.workspaces.findIndex((ws) => ws.name === s.activeWorkspaceName); + const next = (idx + 1) % s.workspaces.length; + s.setActiveWorkspace(s.workspaces[next].name); + }, + }, + + // ── Terminal ──────────────────────────────────────────────── + { + id: 'terminal.new', + label: 'New Terminal Tab', + category: 'terminal', + icon: Terminal, + shortcut: { modifiers: ['meta'], key: 't' }, + when: () => + store().activePage === 'spaces' && store().activeWorkspaceName !== null, + execute: () => { + const s = store(); + if (!s.activeWorkspaceName) return; + const paneId = s.focusedPaneId ?? 'main'; + const terminalId = s.allocatePtySession(s.activeWorkspaceName, 'terminal'); + s.addTab(paneId, { + id: `tab-${Date.now()}`, + terminal_id: terminalId, + title: 'Terminal', + kind: 'terminal', + }); + }, + }, + { + id: 'terminal.close-tab', + label: 'Close Active Tab', + category: 'terminal', + icon: X, + shortcut: { modifiers: ['meta'], key: 'w' }, + when: () => + store().activePage === 'spaces' && + store().activeWorkspaceName !== null && + store().layout !== null, + execute: () => { + const s = store(); + if (!s.layout || !s.activeWorkspaceName) return; + const paneId = s.focusedPaneId ?? 'main'; + const pane = findPane(s.layout.root, paneId); + if (!pane || pane.tabs.length === 0) return; + const tab = pane.tabs.find((t) => t.id === pane.active_tab_id); + if (!tab) return; + s.removeTab(paneId, tab.id); + if (tab.kind !== 'diff') { + s.closePtySession(tab.terminal_id); + } + }, + }, + { + id: 'terminal.prev-tab', + label: 'Previous Tab', + category: 'terminal', + icon: ArrowLeft, + shortcut: { modifiers: ['meta'], key: 'arrowleft' }, + when: () => { + const s = store(); + if (s.activePage !== 'spaces' || !s.layout) return false; + const paneId = s.focusedPaneId ?? 'main'; + const pane = findPane(s.layout.root, paneId); + return pane !== null && pane.tabs.length > 1; + }, + execute: () => { + const s = store(); + if (!s.layout) return; + const paneId = s.focusedPaneId ?? 'main'; + const pane = findPane(s.layout.root, paneId); + if (!pane || pane.tabs.length === 0) return; + const idx = pane.tabs.findIndex((t) => t.id === pane.active_tab_id); + const next = (idx - 1 + pane.tabs.length) % pane.tabs.length; + s.setActiveTab(paneId, pane.tabs[next].id); + }, + }, + { + id: 'terminal.next-tab', + label: 'Next Tab', + category: 'terminal', + icon: ArrowRight, + shortcut: { modifiers: ['meta'], key: 'arrowright' }, + when: () => { + const s = store(); + if (s.activePage !== 'spaces' || !s.layout) return false; + const paneId = s.focusedPaneId ?? 'main'; + const pane = findPane(s.layout.root, paneId); + return pane !== null && pane.tabs.length > 1; + }, + execute: () => { + const s = store(); + if (!s.layout) return; + const paneId = s.focusedPaneId ?? 'main'; + const pane = findPane(s.layout.root, paneId); + if (!pane || pane.tabs.length === 0) return; + const idx = pane.tabs.findIndex((t) => t.id === pane.active_tab_id); + const next = (idx + 1) % pane.tabs.length; + s.setActiveTab(paneId, pane.tabs[next].id); + }, + }, + + // ── Panes ───────────────────────────────────────────────────── + { + id: 'pane.split-right', + label: 'Split Pane Right', + category: 'terminal', + icon: Columns2, + shortcut: { modifiers: ['meta'], key: 'd' }, + when: () => + store().activePage === 'spaces' && store().activeWorkspaceName !== null, + execute: () => { + const s = store(); + if (!s.activeWorkspaceName) return; + const paneId = s.focusedPaneId ?? 'main'; + const newPaneId = s.splitPane(paneId, 'row'); + const terminalId = s.allocatePtySession(s.activeWorkspaceName, 'terminal'); + s.addTab(newPaneId, { + id: `tab-${Date.now()}`, + terminal_id: terminalId, + title: 'Terminal', + kind: 'terminal', + }); + }, + }, + { + id: 'pane.split-down', + label: 'Split Pane Down', + category: 'terminal', + icon: Rows2, + shortcut: { modifiers: ['meta', 'shift'], key: 'd' }, + when: () => + store().activePage === 'spaces' && store().activeWorkspaceName !== null, + execute: () => { + const s = store(); + if (!s.activeWorkspaceName) return; + const paneId = s.focusedPaneId ?? 'main'; + const newPaneId = s.splitPane(paneId, 'column'); + const terminalId = s.allocatePtySession(s.activeWorkspaceName, 'terminal'); + s.addTab(newPaneId, { + id: `tab-${Date.now()}`, + terminal_id: terminalId, + title: 'Terminal', + kind: 'terminal', + }); + }, + }, + { + id: 'pane.close', + label: 'Close Pane', + category: 'terminal', + icon: PanelLeft, + when: () => { + const s = store(); + if (s.activePage !== 'spaces' || !s.layout) return false; + // Can only close if there are splits (not a single root pane) + return s.layout.root.kind === 'split'; + }, + execute: () => { + const s = store(); + if (!s.layout) return; + const paneId = s.focusedPaneId ?? 'main'; + // Close all PTYs in this pane first + const pane = findPane(s.layout.root, paneId); + if (pane) { + for (const tab of pane.tabs) { + if (tab.kind !== 'diff') { + s.closePtySession(tab.terminal_id); + } + } + } + s.closePane(paneId); + }, + }, + + // ── Pane focus navigation ──────────────────────────────────── + { + id: 'pane.focus-next', + label: 'Focus Next Pane', + category: 'terminal', + icon: Focus, + shortcut: { modifiers: ['meta', 'alt'], key: 'arrowright' }, + when: () => store().activePage === 'spaces' && store().layout?.root.kind === 'split', + execute: () => { + const s = store(); + if (!s.layout) return; + const panes = collectPaneIds(s.layout.root); + const idx = panes.indexOf(s.focusedPaneId ?? 'main'); + const next = panes[(idx + 1) % panes.length]; + s.setFocusedPane(next); + focusPaneTerminal(next); + }, + }, + { + id: 'pane.focus-prev', + label: 'Focus Previous Pane', + category: 'terminal', + icon: Focus, + shortcut: { modifiers: ['meta', 'alt'], key: 'arrowleft' }, + when: () => store().activePage === 'spaces' && store().layout?.root.kind === 'split', + execute: () => { + const s = store(); + if (!s.layout) return; + const panes = collectPaneIds(s.layout.root); + const idx = panes.indexOf(s.focusedPaneId ?? 'main'); + const next = panes[(idx - 1 + panes.length) % panes.length]; + s.setFocusedPane(next); + focusPaneTerminal(next); + }, + }, + + // ── App ───────────────────────────────────────────────────── + { + id: 'app.toggle-right-panel', + label: 'Toggle Right Panel', + category: 'app', + icon: PanelRightClose, + shortcut: { modifiers: ['meta'], key: 'b' }, + when: () => store().activePage === 'spaces', + execute: () => store().toggleRightPanel(), + }, + { + id: 'app.command-palette', + label: 'Command Palette', + category: 'app', + icon: Command, + shortcut: { modifiers: ['meta'], key: 'k' }, + execute: () => { + const s = store(); + if (s.activeModal?.type === 'command-palette') { + s.closeModal(); + } else { + s.openModal('command-palette'); + } + }, + }, + { + id: 'app.toggle-theme', + label: 'Toggle Light/Dark Theme', + category: 'app', + icon: Sun, + execute: () => { + const s = store(); + const current = getThemeById(s.activeThemeId); + if (!current) return; + if (current.group === 'dark') { + const firstLight = themes.find((t) => t.group === 'light'); + if (firstLight) s.setTheme(firstLight.id); + } else { + const firstDark = themes.find((t) => t.group === 'dark'); + if (firstDark) s.setTheme(firstDark.id); + } + }, + }, + { + id: 'app.appearance-settings', + label: 'Open Appearance Settings', + category: 'app', + icon: Palette, + execute: () => { + const s = store(); + s.setActivePage('settings'); + s.setSettingsSection('appearance'); + }, + }, + + // ── Individual themes ─────────────────────────────────────── + ...themes.map((t) => ({ + id: `theme.${t.id}`, + label: `Theme: ${t.name}`, + category: 'app' as const, + icon: t.group === 'dark' ? Moon : Sun, + when: () => store().activeThemeId !== t.id, + execute: () => store().setTheme(t.id), + })), +]); diff --git a/OPUS-tauri-app/src/commands/fuzzyMatch.ts b/OPUS-tauri-app/src/commands/fuzzyMatch.ts new file mode 100644 index 00000000..6f8505ad --- /dev/null +++ b/OPUS-tauri-app/src/commands/fuzzyMatch.ts @@ -0,0 +1,67 @@ +export type FuzzyResult = { + item: T; + score: number; + matches: number[]; +}; + +export function fuzzyMatch( + query: string, + label: string, +): { score: number; matches: number[] } | null { + const q = query.toLowerCase(); + const l = label.toLowerCase(); + + if (q.length === 0) return { score: 1, matches: [] }; + if (q.length > l.length) return null; + + const matches: number[] = []; + let qi = 0; + let score = 0; + let prevMatchIndex = -2; + + for (let li = 0; li < l.length && qi < q.length; li++) { + if (l[li] === q[qi]) { + matches.push(li); + + // Consecutive match bonus + if (li === prevMatchIndex + 1) { + score += 3; + } + + // Word boundary bonus + if (li === 0 || /[\s\-/]/.test(l[li - 1])) { + score += 2; + } + + // Earlier match bonus (diminishing) + score += Math.max(0, 10 - li); + + prevMatchIndex = li; + qi++; + } + } + + if (qi < q.length) return null; + + return { score, matches }; +} + +export function fuzzyFilter( + items: T[], + query: string, + getLabel: (item: T) => string, +): FuzzyResult[] { + if (query.trim() === '') { + return items.map((item) => ({ item, score: 0, matches: [] })); + } + + const results: FuzzyResult[] = []; + for (const item of items) { + const match = fuzzyMatch(query, getLabel(item)); + if (match) { + results.push({ item, score: match.score, matches: match.matches }); + } + } + + return results.sort((a, b) => b.score - a.score); +} diff --git a/OPUS-tauri-app/src/commands/layoutUtils.ts b/OPUS-tauri-app/src/commands/layoutUtils.ts new file mode 100644 index 00000000..cb5540f5 --- /dev/null +++ b/OPUS-tauri-app/src/commands/layoutUtils.ts @@ -0,0 +1,41 @@ +import type { LayoutNode, PaneNode, SplitNode } from '@/types/layout'; + +export function findPane(node: LayoutNode, paneId: string): PaneNode | null { + if (node.kind === 'pane') return node.id === paneId ? node : null; + return findPane(node.first, paneId) ?? findPane(node.second, paneId); +} + +export function findSplit(node: LayoutNode, splitId: string): SplitNode | null { + if (node.kind === 'pane') return null; + if (node.id === splitId) return node; + return findSplit(node.first, splitId) ?? findSplit(node.second, splitId); +} + +export function findParentSplit( + node: LayoutNode, + childId: string, +): { parent: SplitNode; side: 'first' | 'second' } | null { + if (node.kind === 'pane') return null; + if (node.first.id === childId) return { parent: node, side: 'first' }; + if (node.second.id === childId) return { parent: node, side: 'second' }; + return findParentSplit(node.first, childId) ?? findParentSplit(node.second, childId); +} + +export function replaceNodeInTree( + node: LayoutNode, + targetId: string, + replacement: LayoutNode, +): LayoutNode { + if (node.id === targetId) return replacement; + if (node.kind === 'pane') return node; + return { + ...node, + first: replaceNodeInTree(node.first, targetId, replacement), + second: replaceNodeInTree(node.second, targetId, replacement), + }; +} + +export function collectPaneIds(node: LayoutNode): string[] { + if (node.kind === 'pane') return [node.id]; + return [...collectPaneIds(node.first), ...collectPaneIds(node.second)]; +} diff --git a/OPUS-tauri-app/src/commands/registry.ts b/OPUS-tauri-app/src/commands/registry.ts new file mode 100644 index 00000000..5f689fb3 --- /dev/null +++ b/OPUS-tauri-app/src/commands/registry.ts @@ -0,0 +1,50 @@ +import type { LucideIcon } from 'lucide-react'; + +export type CommandCategory = + | 'navigation' + | 'workspace' + | 'terminal' + | 'workset' + | 'app'; + +export type KeyboardShortcut = { + modifiers: ('meta' | 'shift' | 'alt' | 'ctrl')[]; + key: string; +}; + +export type CommandDefinition = { + id: string; + label: string; + category: CommandCategory; + icon?: LucideIcon; + shortcut?: KeyboardShortcut; + /** Return false to hide from palette in current context */ + when?: () => boolean; + execute: () => void | Promise; +}; + +const commands = new Map(); + +export function registerCommand(cmd: CommandDefinition): () => void { + commands.set(cmd.id, cmd); + return () => { + commands.delete(cmd.id); + }; +} + +export function registerCommands(cmds: CommandDefinition[]): () => void { + cmds.forEach((cmd) => commands.set(cmd.id, cmd)); + return () => { + cmds.forEach((cmd) => commands.delete(cmd.id)); + }; +} + +export function getCommands(): CommandDefinition[] { + return Array.from(commands.values()); +} + +export function getVisibleCommands(): CommandDefinition[] { + return Array.from(commands.values()).filter( + (cmd) => !cmd.when || cmd.when(), + ); +} diff --git a/OPUS-tauri-app/src/components/diff/DiffFileRow.css b/OPUS-tauri-app/src/components/diff/DiffFileRow.css new file mode 100644 index 00000000..9e57b89f --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffFileRow.css @@ -0,0 +1,59 @@ +.diff-file-row { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 4px 12px 4px 24px; + font-size: 12px; + color: var(--text); + text-align: left; + transition: background var(--transition-fast); +} + +.diff-file-row:hover { + background: var(--surface); +} + +.diff-file-row__icon { + flex-shrink: 0; + opacity: 0.7; +} + +.diff-file-row__icon--m { color: var(--warning); } +.diff-file-row__icon--a { color: var(--success); } +.diff-file-row__icon--d { color: var(--danger); } +.diff-file-row__icon--r { color: var(--accent); } + +.diff-file-row__path { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + min-width: 0; +} + +.diff-file-row__name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 500; +} + +.diff-file-row__dir { + font-size: 10px; + color: var(--muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.diff-file-row__stats { + display: flex; + gap: 4px; + flex-shrink: 0; + font-family: var(--font-mono); + font-size: 11px; +} + +.diff-file-row__added { color: var(--success); } +.diff-file-row__removed { color: var(--danger); } diff --git a/OPUS-tauri-app/src/components/diff/DiffFileRow.tsx b/OPUS-tauri-app/src/components/diff/DiffFileRow.tsx new file mode 100644 index 00000000..13d020fd --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffFileRow.tsx @@ -0,0 +1,35 @@ +import type { DiffFileSummary } from '@/types/diff'; +import { FileText, FilePlus, FileMinus, FileEdit, ArrowRightLeft } from 'lucide-react'; +import './DiffFileRow.css'; + +type Props = { + file: DiffFileSummary; + onClick: () => void; +}; + +const statusIcons: Record = { + M: FileEdit, + A: FilePlus, + D: FileMinus, + R: ArrowRightLeft, +}; + +export function DiffFileRow({ file, onClick }: Props) { + const Icon = statusIcons[file.status] ?? FileText; + const filename = file.path.split('/').pop() ?? file.path; + const dir = file.path.includes('/') ? file.path.slice(0, file.path.lastIndexOf('/')) : ''; + + return ( + + ); +} diff --git a/OPUS-tauri-app/src/components/diff/DiffNavigator.css b/OPUS-tauri-app/src/components/diff/DiffNavigator.css new file mode 100644 index 00000000..cf7a8ce4 --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffNavigator.css @@ -0,0 +1,39 @@ +.diff-navigator { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.diff-navigator__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + font-size: 12px; + font-weight: 600; + color: var(--text); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.diff-navigator__totals { + font-size: 11px; + font-weight: 400; + color: var(--muted); +} + +.diff-navigator__added { color: var(--success); } +.diff-navigator__removed { color: var(--danger); } + +.diff-navigator__body { + flex: 1; + overflow-y: auto; +} + +.diff-navigator__empty { + padding: 24px; + text-align: center; + font-size: 12px; + color: var(--muted); +} diff --git a/OPUS-tauri-app/src/components/diff/DiffNavigator.tsx b/OPUS-tauri-app/src/components/diff/DiffNavigator.tsx new file mode 100644 index 00000000..ef1ac2a9 --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffNavigator.tsx @@ -0,0 +1,123 @@ +import { useCallback, useEffect, useRef } from 'react'; +import type { DiffFileSummary } from '@/types/diff'; +import { useAppStore } from '@/state/store'; +import { DiffRepoGroup } from './DiffRepoGroup'; +import { useTauriEvent } from '@/hooks/useTauriEvent'; +import { listWorkspaceRepos } from '@/api/repos'; +import { diffWatchStart, diffWatchStop } from '@/api/diff'; +import './DiffNavigator.css'; + +type DiffSummaryEvent = { + workspace_name: string; + repo: string; + summary: { files: DiffFileSummary[]; total_added: number; total_removed: number }; +}; + +export function DiffNavigator() { + const repoDiffs = useAppStore((s) => s.repoDiffs); + const updateDiffSummary = useAppStore((s) => s.updateDiffSummary); + const loadDiffSummary = useAppStore((s) => s.loadDiffSummary); + const activeWorkspaceName = useAppStore((s) => s.activeWorkspaceName); + const focusedPaneId = useAppStore((s) => s.focusedPaneId); + const addTab = useAppStore((s) => s.addTab); + const watchedReposRef = useRef<{ workspace: string; repo: string }[]>([]); + + // Listen for watcher events + const handleSummaryEvent = useCallback( + (payload: DiffSummaryEvent) => { + updateDiffSummary(payload.repo, payload.summary); + }, + [updateDiffSummary], + ); + useTauriEvent('diff:summary', handleSummaryEvent); + + // Fetch repos, load initial diffs, start watchers when workspace changes + useEffect(() => { + if (!activeWorkspaceName) return; + let cancelled = false; + const wsName = activeWorkspaceName; + + (async () => { + try { + const repos = await listWorkspaceRepos(wsName); + if (cancelled) return; + + for (const repo of repos) { + if (repo.missing) continue; + // Load initial diff summary + loadDiffSummary(wsName, repo.name, repo.worktree_path); + // Start background watcher + diffWatchStart(wsName, repo.name, repo.worktree_path).catch(() => {}); + watchedReposRef.current.push({ workspace: wsName, repo: repo.name }); + } + } catch (err) { + console.error('Failed to load workspace repos for diff:', err); + } + })(); + + return () => { + cancelled = true; + // Stop watchers on cleanup + for (const { workspace, repo } of watchedReposRef.current) { + diffWatchStop(workspace, repo).catch(() => {}); + } + watchedReposRef.current = []; + }; + }, [activeWorkspaceName, loadDiffSummary]); + + const repos = Object.values(repoDiffs).filter( + (rd) => rd.summary && rd.summary.files.length > 0, + ); + + function handleFileClick(repo: string, repoPath: string, file: DiffFileSummary) { + const paneId = focusedPaneId ?? 'main'; + const tabId = `diff-${repo}-${file.path}`; + addTab(paneId, { + id: tabId, + terminal_id: '', + title: file.path, + kind: 'diff', + diff_repo: repo, + diff_repo_path: repoPath, + diff_file_path: file.path, + diff_prev_path: file.prev_path, + diff_status: file.status, + }); + } + + if (repos.length === 0) { + return ( +
+
Diff
+
No changes detected
+
+ ); + } + + const totalFiles = repos.reduce((sum, r) => sum + (r.summary?.files.length ?? 0), 0); + const totalAdded = repos.reduce((sum, r) => sum + (r.summary?.total_added ?? 0), 0); + const totalRemoved = repos.reduce((sum, r) => sum + (r.summary?.total_removed ?? 0), 0); + + return ( +
+
+ Diff + + {totalFiles} file{totalFiles !== 1 ? 's' : ''} + {totalAdded > 0 && +{totalAdded}} + {totalRemoved > 0 && -{totalRemoved}} + +
+
+ {repos.map((rd) => ( + handleFileClick(rd.repo, rd.repoPath, file)} + /> + ))} +
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/diff/DiffRenderer.css b/OPUS-tauri-app/src/components/diff/DiffRenderer.css new file mode 100644 index 00000000..e9de5d8b --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffRenderer.css @@ -0,0 +1,71 @@ +.diff-renderer { + position: absolute; + inset: 0; + overflow-y: auto; + overflow-x: hidden; + font-family: var(--font-mono); + font-size: 12px; + line-height: 1.6; +} + +.diff-renderer--binary { + display: flex; + align-items: center; + justify-content: center; + color: var(--muted); + font-family: var(--font-body); +} + +.diff-renderer__truncated { + padding: 8px 12px; + background: rgba(251, 191, 36, 0.1); + border-bottom: 1px solid var(--border); + color: var(--warning); + font-family: var(--font-body); + font-size: 12px; +} + +.diff-renderer__content { + margin: 0; + padding: 0; +} + +.diff-line { + display: flex; + padding: 0 12px; + min-height: 20px; +} + +.diff-line--added { + background: rgba(52, 211, 153, 0.08); +} + +.diff-line--removed { + background: rgba(248, 113, 113, 0.08); +} + +.diff-line--hunk { + background: rgba(129, 140, 248, 0.06); + color: var(--accent); +} + +.diff-line--meta { + color: var(--muted); +} + +.diff-line__number { + width: 48px; + text-align: right; + padding-right: 12px; + color: var(--muted); + opacity: 0.5; + flex-shrink: 0; + user-select: none; +} + +.diff-line__text { + flex: 1; + white-space: pre-wrap; + word-break: break-all; + min-width: 0; +} diff --git a/OPUS-tauri-app/src/components/diff/DiffRenderer.tsx b/OPUS-tauri-app/src/components/diff/DiffRenderer.tsx new file mode 100644 index 00000000..1eecf668 --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffRenderer.tsx @@ -0,0 +1,49 @@ +import './DiffRenderer.css'; + +type Props = { + patch: string; + truncated: boolean; + binary?: boolean; +}; + +export function DiffRenderer({ patch, truncated, binary }: Props) { + if (binary) { + return ( +
+ Binary file — cannot display diff +
+ ); + } + + const lines = patch.split('\n'); + + return ( +
+ {truncated && ( +
+ Diff truncated — file is too large to display in full +
+ )} +
+        {lines.map((line, i) => {
+          let cls = 'diff-line';
+          if (line.startsWith('+') && !line.startsWith('+++')) {
+            cls += ' diff-line--added';
+          } else if (line.startsWith('-') && !line.startsWith('---')) {
+            cls += ' diff-line--removed';
+          } else if (line.startsWith('@@')) {
+            cls += ' diff-line--hunk';
+          } else if (line.startsWith('diff ') || line.startsWith('index ')) {
+            cls += ' diff-line--meta';
+          }
+          return (
+            
+ {i + 1} + {line} +
+ ); + })} +
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/diff/DiffRepoGroup.css b/OPUS-tauri-app/src/components/diff/DiffRepoGroup.css new file mode 100644 index 00000000..81f72040 --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffRepoGroup.css @@ -0,0 +1,58 @@ +.diff-repo-group { + border-bottom: 1px solid var(--border); +} + +.diff-repo-group__header { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 8px 8px; + font-size: 12px; + font-weight: 600; + color: var(--text); + text-align: left; + transition: background var(--transition-fast); +} + +.diff-repo-group__header:hover { + background: var(--surface); +} + +.diff-repo-group__icon { + color: var(--muted); + flex-shrink: 0; +} + +.diff-repo-group__name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.diff-repo-group__count { + font-size: 10px; + font-weight: 400; + color: var(--muted); + background: var(--surface); + padding: 1px 5px; + border-radius: 8px; + flex-shrink: 0; +} + +.diff-repo-group__stats { + display: flex; + gap: 4px; + flex-shrink: 0; + font-family: var(--font-mono); + font-size: 11px; + font-weight: 400; +} + +.diff-repo-group__added { color: var(--success); } +.diff-repo-group__removed { color: var(--danger); } + +.diff-repo-group__files { + padding-bottom: 4px; +} diff --git a/OPUS-tauri-app/src/components/diff/DiffRepoGroup.tsx b/OPUS-tauri-app/src/components/diff/DiffRepoGroup.tsx new file mode 100644 index 00000000..d686ac24 --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffRepoGroup.tsx @@ -0,0 +1,45 @@ +import { useState } from 'react'; +import type { DiffSummary, DiffFileSummary } from '@/types/diff'; +import { DiffFileRow } from './DiffFileRow'; +import { ChevronDown, ChevronRight, GitBranch } from 'lucide-react'; +import './DiffRepoGroup.css'; + +type Props = { + repo: string; + summary: DiffSummary; + onFileClick: (file: DiffFileSummary) => void; +}; + +export function DiffRepoGroup({ repo, summary, onFileClick }: Props) { + const [expanded, setExpanded] = useState(true); + + return ( +
+ + {expanded && ( +
+ {summary.files.map((file) => ( + onFileClick(file)} + /> + ))} +
+ )} +
+ ); +} diff --git a/OPUS-tauri-app/src/components/diff/DiffTabView.tsx b/OPUS-tauri-app/src/components/diff/DiffTabView.tsx new file mode 100644 index 00000000..cbcbf404 --- /dev/null +++ b/OPUS-tauri-app/src/components/diff/DiffTabView.tsx @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { useAppStore } from '@/state/store'; +import { DiffRenderer } from './DiffRenderer'; +import type { FilePatch } from '@/types/diff'; + +type Props = { + repoPath: string; + filePath: string; + prevPath?: string; + status: string; +}; + +export function DiffTabView({ repoPath, filePath, prevPath, status }: Props) { + const fetchFilePatch = useAppStore((s) => s.fetchFilePatch); + const [patch, setPatch] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + setLoading(true); + setError(null); + + fetchFilePatch(repoPath, filePath, prevPath, status) + .then((result) => { + if (!cancelled) setPatch(result); + }) + .catch((err) => { + if (!cancelled) { + const msg = typeof err === 'object' && err !== null && 'message' in err + ? String(err.message) + : 'Failed to load diff'; + setError(msg); + } + }) + .finally(() => { + if (!cancelled) setLoading(false); + }); + + return () => { cancelled = true; }; + }, [repoPath, filePath, prevPath, status, fetchFilePatch]); + + if (loading) { + return ( +
+ Loading diff... +
+ ); + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + if (!patch || !patch.patch) { + return ( +
+ No changes +
+ ); + } + + return ( + + ); +} diff --git a/OPUS-tauri-app/src/components/layout/AppShell.css b/OPUS-tauri-app/src/components/layout/AppShell.css new file mode 100644 index 00000000..ff514989 --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/AppShell.css @@ -0,0 +1,50 @@ +.app-shell { + display: grid; + grid-template-columns: var(--rail-width) var(--sidebar-width) 1fr var(--right-panel-width); + grid-template-rows: var(--chrome-height) 1fr; + height: 100%; + overflow: hidden; +} + +.app-shell--no-right { + grid-template-columns: var(--rail-width) var(--sidebar-width) 1fr; +} + +.app-shell__chrome { + grid-column: 1 / -1; + grid-row: 1; + background: var(--panel); + border-bottom: 1px solid var(--border); + z-index: 10; +} + +.app-shell__rail { + grid-column: 1; + grid-row: 2; + background: var(--panel-soft); + border-right: 1px solid var(--border); +} + +.app-shell__sidebar { + grid-column: 2; + grid-row: 2; + background: var(--panel); + border-right: 1px solid var(--border); + overflow-y: auto; +} + +.app-shell__main { + grid-column: 3; + grid-row: 2; + display: flex; + background: var(--bg); + overflow: hidden; +} + +.app-shell__right-panel { + grid-column: 4; + grid-row: 2; + background: var(--panel); + border-left: 1px solid var(--border); + overflow-y: auto; +} diff --git a/OPUS-tauri-app/src/components/layout/AppShell.tsx b/OPUS-tauri-app/src/components/layout/AppShell.tsx new file mode 100644 index 00000000..50237671 --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/AppShell.tsx @@ -0,0 +1,26 @@ +import { type ReactNode } from 'react'; +import { useAppStore } from '@/state/store'; +import './AppShell.css'; + +type Props = { + chrome: ReactNode; + rail: ReactNode; + sidebar: ReactNode; + main: ReactNode; + rightPanel?: ReactNode; +}; + +export function AppShell({ chrome, rail, sidebar, main, rightPanel }: Props) { + const rightPanelCollapsed = useAppStore((s) => s.rightPanelCollapsed); + const showRight = rightPanel && !rightPanelCollapsed; + + return ( +
+
{chrome}
+
{rail}
+
{sidebar}
+
{main}
+ {showRight &&
{rightPanel}
} +
+ ); +} diff --git a/OPUS-tauri-app/src/components/layout/IconRail.css b/OPUS-tauri-app/src/components/layout/IconRail.css new file mode 100644 index 00000000..3618b01d --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/IconRail.css @@ -0,0 +1,41 @@ +.icon-rail { + display: flex; + flex-direction: column; + align-items: center; + padding-top: var(--space-3); + gap: var(--space-1); +} + +.icon-rail__btn { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: var(--radius-md); + color: var(--muted); + transition: all var(--transition-fast); + position: relative; +} + +.icon-rail__btn:hover { + color: var(--text); + background: var(--surface); +} + +.icon-rail__btn.active { + color: var(--accent); + background: var(--accent-soft); +} + +.icon-rail__btn.active::before { + content: ''; + position: absolute; + left: -8px; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 16px; + background: var(--accent); + border-radius: 0 2px 2px 0; +} diff --git a/OPUS-tauri-app/src/components/layout/IconRail.tsx b/OPUS-tauri-app/src/components/layout/IconRail.tsx new file mode 100644 index 00000000..1b1c0850 --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/IconRail.tsx @@ -0,0 +1,30 @@ +import { useAppStore } from '@/state/store'; +import type { NavPage } from '@/state/slices/uiSlice'; +import { LayoutGrid, Layers, Settings } from 'lucide-react'; +import './IconRail.css'; + +const navItems: { page: NavPage; icon: typeof LayoutGrid; label: string }[] = [ + { page: 'command-center', icon: LayoutGrid, label: 'Command Center' }, + { page: 'spaces', icon: Layers, label: 'Spaces' }, + { page: 'settings', icon: Settings, label: 'Settings' }, +]; + +export function IconRail() { + const activePage = useAppStore((s) => s.activePage); + const setActivePage = useAppStore((s) => s.setActivePage); + + return ( + + ); +} diff --git a/OPUS-tauri-app/src/components/layout/RightPanel.css b/OPUS-tauri-app/src/components/layout/RightPanel.css new file mode 100644 index 00000000..19433270 --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/RightPanel.css @@ -0,0 +1,29 @@ +.right-panel { + display: flex; + flex-direction: column; + height: 100%; +} + +.right-panel__header { + display: flex; + align-items: center; + padding: var(--space-4); + border-bottom: 1px solid var(--border); +} + +.right-panel__title { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--muted); +} + +.right-panel__empty { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + color: var(--muted); +} diff --git a/OPUS-tauri-app/src/components/layout/RightPanel.tsx b/OPUS-tauri-app/src/components/layout/RightPanel.tsx new file mode 100644 index 00000000..3f287acb --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/RightPanel.tsx @@ -0,0 +1,26 @@ +import { useAppStore } from '@/state/store'; +import { DiffNavigator } from '@/components/diff/DiffNavigator'; +import './RightPanel.css'; + +export function RightPanel() { + const activeWorkspaceName = useAppStore((s) => s.activeWorkspaceName); + + if (!activeWorkspaceName) { + return ( +
+
+ Diff +
+
+ No workspace selected +
+
+ ); + } + + return ( +
+ +
+ ); +} diff --git a/OPUS-tauri-app/src/components/layout/SecondarySidebar.css b/OPUS-tauri-app/src/components/layout/SecondarySidebar.css new file mode 100644 index 00000000..fa9d14b5 --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/SecondarySidebar.css @@ -0,0 +1,73 @@ +.secondary-sidebar { + display: flex; + flex-direction: column; + height: 100%; +} + +.secondary-sidebar__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-4); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--muted); +} + +.secondary-sidebar__action-btn { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: var(--radius-md); + color: var(--muted); + transition: all var(--transition-fast); +} + +.secondary-sidebar__action-btn:hover { + color: var(--text); + background: var(--surface); +} + +.secondary-sidebar__nav { + display: flex; + flex-direction: column; + padding: 0 var(--space-2); +} + +.secondary-sidebar__nav-item { + display: block; + width: 100%; + padding: 6px var(--space-3); + font-size: 13px; + text-align: left; + color: var(--muted); + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.secondary-sidebar__nav-item:hover { + color: var(--text); + background: var(--surface); +} + +.secondary-sidebar__nav-item.active { + color: var(--text); + background: var(--accent-soft); +} + +.secondary-sidebar__placeholder { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-4); +} + +.secondary-sidebar__hint { + font-size: 12px; + color: var(--muted); +} diff --git a/OPUS-tauri-app/src/components/layout/SecondarySidebar.tsx b/OPUS-tauri-app/src/components/layout/SecondarySidebar.tsx new file mode 100644 index 00000000..4ceabd31 --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/SecondarySidebar.tsx @@ -0,0 +1,73 @@ +import { useAppStore } from '@/state/store'; +import { WorkspaceList } from '@/components/pages/Spaces/WorkspaceList'; +import { Plus } from 'lucide-react'; +import './SecondarySidebar.css'; + +export function SecondarySidebar() { + const activePage = useAppStore((s) => s.activePage); + const openModal = useAppStore((s) => s.openModal); + const ccSection = useAppStore((s) => s.commandCenterSection); + const setCcSection = useAppStore((s) => s.setCommandCenterSection); + const settingsSection = useAppStore((s) => s.settingsSection); + const setSettingsSection = useAppStore((s) => s.setSettingsSection); + + if (activePage === 'command-center') { + return ( +
+
Command Center
+ +
+ ); + } + + if (activePage === 'spaces') { + return ( +
+
+ Workspaces + +
+ +
+ ); + } + + return ( +
+
Settings
+ +
+ ); +} diff --git a/OPUS-tauri-app/src/components/layout/TopChrome.css b/OPUS-tauri-app/src/components/layout/TopChrome.css new file mode 100644 index 00000000..1f836d00 --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/TopChrome.css @@ -0,0 +1,168 @@ +.top-chrome { + display: flex; + align-items: center; + height: 100%; + padding: 0 var(--space-3); + padding-left: 80px; + gap: var(--space-3); +} + +.top-chrome__selector { + position: relative; + width: clamp(240px, 40%, 420px); +} + +.top-chrome__selector-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + width: 100%; + padding: 5px 16px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--text); + font-size: 12px; + font-weight: 500; + transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast); +} + +.top-chrome__selector-btn:hover { + border-color: var(--border-active); + background: var(--surface-hover); + color: var(--text); +} + +.top-chrome__selector-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.top-chrome__selector-chevron { + flex-shrink: 0; + opacity: 0.5; + margin-left: auto; +} + +.top-chrome__dropdown { + position: absolute; + top: calc(100% + 6px); + left: 0; + right: 0; + background: var(--panel-strong); + border: 1px solid var(--border-active); + border-radius: 10px; + padding: var(--space-1) 0; + z-index: 100; +} + +.top-chrome__dropdown-row { + position: relative; +} + +.top-chrome__dropdown-item { + display: block; + width: 100%; + padding: 6px 12px; + font-size: 13px; + text-align: left; + color: var(--text); + transition: background var(--transition-fast); +} + +.top-chrome__dropdown-item:hover { + background: var(--surface-hover); +} + +.top-chrome__dropdown-item.active { + background: var(--accent-soft); + color: var(--accent); +} + +.top-chrome__dropdown-action { + color: var(--muted); +} + +.top-chrome__more-anchor { + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); +} + +.top-chrome__more { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + background: none; + color: var(--muted); + cursor: pointer; + border-radius: 3px; + font-size: 16px; + line-height: 1; + opacity: 0; + transition: opacity var(--transition-fast), background var(--transition-fast); +} + +.top-chrome__dropdown-row:hover .top-chrome__more { + opacity: 1; +} + +.top-chrome__more:hover { + background: var(--border); + color: var(--text); +} + +.top-chrome__submenu { + position: absolute; + top: 0; + left: calc(100% + 4px); + min-width: 140px; + background: var(--panel-strong); + border: 1px solid var(--border-active); + border-radius: 3px; + padding: 4px 0; + z-index: 200; +} + +.top-chrome__submenu-item { + display: block; + width: 100%; + padding: 6px 12px; + font-size: 13px; + text-align: left; + color: var(--text); + border: none; + background: none; + cursor: pointer; + transition: background var(--transition-fast); +} + +.top-chrome__submenu-item:hover { + background: var(--surface-hover); +} + +.top-chrome__submenu-item--danger { + color: var(--danger, #ef4444); +} + +.top-chrome__submenu-item--confirm { + background: rgba(239, 68, 68, 0.1); +} + +.top-chrome__dropdown-divider { + height: 1px; + background: var(--border); + margin: var(--space-1) 0; +} + +.top-chrome__spacer { + flex: 1; + align-self: stretch; +} diff --git a/OPUS-tauri-app/src/components/layout/TopChrome.tsx b/OPUS-tauri-app/src/components/layout/TopChrome.tsx new file mode 100644 index 00000000..f09d88af --- /dev/null +++ b/OPUS-tauri-app/src/components/layout/TopChrome.tsx @@ -0,0 +1,117 @@ +import { useState, useRef, useEffect } from 'react'; +import { useAppStore } from '@/state/store'; +import { ChevronDown } from 'lucide-react'; +import './TopChrome.css'; + +export function TopChrome() { + const worksets = useAppStore((s) => s.worksets); + const activeWorksetId = useAppStore((s) => s.activeWorksetId); + const setActiveWorkset = useAppStore((s) => s.setActiveWorkset); + const deleteWorkset = useAppStore((s) => s.deleteWorkset); + const openModal = useAppStore((s) => s.openModal); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [menuOpenId, setMenuOpenId] = useState(null); + const [confirmDeleteId, setConfirmDeleteId] = useState(null); + const dropdownRef = useRef(null); + + const activeWorkset = worksets.find((w) => w.id === activeWorksetId); + + useEffect(() => { + function handleClick(e: MouseEvent) { + if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { + setDropdownOpen(false); + setMenuOpenId(null); + setConfirmDeleteId(null); + } + } + document.addEventListener('mousedown', handleClick); + return () => document.removeEventListener('mousedown', handleClick); + }, []); + + function handleMoreClick(e: React.MouseEvent, id: string) { + e.stopPropagation(); + if (menuOpenId === id) { + setMenuOpenId(null); + setConfirmDeleteId(null); + } else { + setMenuOpenId(id); + setConfirmDeleteId(null); + } + } + + function handleDelete(e: React.MouseEvent, id: string) { + e.stopPropagation(); + if (confirmDeleteId !== id) { + setConfirmDeleteId(id); + return; + } + deleteWorkset(id); + setMenuOpenId(null); + setConfirmDeleteId(null); + setDropdownOpen(false); + } + + return ( +
+
+
+ + {dropdownOpen && ( +
+ {worksets.map((w) => ( +
+ +
+ + {menuOpenId === w.id && ( +
+ +
+ )} +
+
+ ))} + {worksets.length > 0 &&
} + +
+ )} +
+
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/modals/CommandPalette.css b/OPUS-tauri-app/src/components/modals/CommandPalette.css new file mode 100644 index 00000000..7e9e27a7 --- /dev/null +++ b/OPUS-tauri-app/src/components/modals/CommandPalette.css @@ -0,0 +1,133 @@ +.command-palette__overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + padding-top: 20vh; + z-index: 1100; +} + +.command-palette { + width: 100%; + max-width: 540px; + max-height: 400px; + background: var(--panel); + border: 1px solid var(--border-active); + border-radius: var(--radius-md); + display: flex; + flex-direction: column; + overflow: hidden; + align-self: flex-start; +} + +.command-palette__input-row { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--border); +} + +.command-palette__search-icon { + color: var(--muted); + flex-shrink: 0; +} + +.command-palette__input { + flex: 1; + background: none; + border: none; + outline: none; + color: var(--text); + font-size: 14px; + font-family: var(--font-body); +} + +.command-palette__input::placeholder { + color: var(--muted); +} + +.command-palette__input:focus-visible { + outline: none; +} + +.command-palette__list { + overflow-y: auto; + padding: var(--space-1) 0; +} + +.command-palette__empty { + padding: var(--space-6) var(--space-4); + text-align: center; + color: var(--muted); + font-size: 13px; +} + +.command-palette__group { + padding: var(--space-1) 0; +} + +.command-palette__group-label { + padding: var(--space-1) var(--space-4); + font-size: 11px; + font-weight: 600; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.command-palette__item { + display: flex; + align-items: center; + gap: var(--space-2); + width: 100%; + padding: 6px var(--space-4); + text-align: left; + font-size: 13px; + color: var(--text); + background: none; + border: none; + border-left: 2px solid transparent; + cursor: pointer; + transition: background var(--transition-fast); +} + +.command-palette__item:hover, +.command-palette__item--selected { + background: var(--accent-soft); + border-left-color: var(--accent); +} + +.command-palette__item-icon { + color: var(--muted); + flex-shrink: 0; +} + +.command-palette__item--selected .command-palette__item-icon { + color: var(--accent); +} + +.command-palette__item-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.command-palette__match { + color: var(--accent); + font-weight: 600; +} + +.command-palette__kbd { + font-family: var(--font-mono); + font-size: 11px; + color: var(--muted); + background: var(--surface); + padding: 2px 6px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); + flex-shrink: 0; +} diff --git a/OPUS-tauri-app/src/components/modals/CommandPalette.tsx b/OPUS-tauri-app/src/components/modals/CommandPalette.tsx new file mode 100644 index 00000000..70185dbd --- /dev/null +++ b/OPUS-tauri-app/src/components/modals/CommandPalette.tsx @@ -0,0 +1,179 @@ +import { useState, useRef, useEffect, useMemo, useCallback } from 'react'; +import { useAppStore } from '@/state/store'; +import { getVisibleCommands } from '@/commands/registry'; +import { fuzzyFilter } from '@/commands/fuzzyMatch'; +import type { CommandDefinition, KeyboardShortcut } from '@/commands/registry'; +import type { FuzzyResult } from '@/commands/fuzzyMatch'; +import { Search } from 'lucide-react'; +import './CommandPalette.css'; + +const KEY_SYMBOLS: Record = { + arrowup: '\u2191', + arrowdown: '\u2193', + arrowleft: '\u2190', + arrowright: '\u2192', + enter: '\u21A9', + escape: 'Esc', + backspace: '\u232B', +}; + +function formatShortcut(shortcut: KeyboardShortcut): string { + const parts: string[] = []; + if (shortcut.modifiers.includes('meta')) parts.push('\u2318'); + if (shortcut.modifiers.includes('ctrl')) parts.push('\u2303'); + if (shortcut.modifiers.includes('alt')) parts.push('\u2325'); + if (shortcut.modifiers.includes('shift')) parts.push('\u21E7'); + parts.push(KEY_SYMBOLS[shortcut.key] ?? shortcut.key.toUpperCase()); + return parts.join(''); +} + +function HighlightedLabel({ label, matches }: { label: string; matches: number[] }) { + if (matches.length === 0) return <>{label}; + const matchSet = new Set(matches); + return ( + <> + {label.split('').map((char, i) => + matchSet.has(i) ? ( + {char} + ) : ( + {char} + ), + )} + + ); +} + +const CATEGORY_LABELS: Record = { + navigation: 'Navigation', + workspace: 'Workspace', + terminal: 'Terminal', + workset: 'Workset', + app: 'App', +}; + +export function CommandPalette() { + const closeModal = useAppStore((s) => s.closeModal); + const [query, setQuery] = useState(''); + const [selectedIndex, setSelectedIndex] = useState(0); + const inputRef = useRef(null); + const listRef = useRef(null); + + const visibleCommands = useMemo(() => getVisibleCommands(), []); + const results: FuzzyResult[] = useMemo( + () => fuzzyFilter(visibleCommands, query, (cmd) => cmd.label), + [visibleCommands, query], + ); + + // Group by category, preserving a flat index for keyboard navigation + const grouped = useMemo(() => { + const flat = results.map((r, i) => ({ ...r, flatIndex: i })); + const groups = new Map(); + for (const r of flat) { + const cat = r.item.category; + if (!groups.has(cat)) groups.set(cat, []); + groups.get(cat)!.push(r); + } + return groups; + }, [results]); + + useEffect(() => { + setSelectedIndex(0); + }, [query]); + + useEffect(() => { + inputRef.current?.focus(); + }, []); + + useEffect(() => { + const el = listRef.current?.querySelector('[data-selected="true"]'); + el?.scrollIntoView({ block: 'nearest' }); + }, [selectedIndex]); + + const executeSelected = useCallback(() => { + const result = results[selectedIndex]; + if (result) { + closeModal(); + requestAnimationFrame(() => result.item.execute()); + } + }, [results, selectedIndex, closeModal]); + + function handleKeyDown(e: React.KeyboardEvent) { + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + setSelectedIndex((i) => Math.min(i + 1, results.length - 1)); + break; + case 'ArrowUp': + e.preventDefault(); + setSelectedIndex((i) => Math.max(i - 1, 0)); + break; + case 'Enter': + e.preventDefault(); + executeSelected(); + break; + case 'Escape': + e.preventDefault(); + closeModal(); + break; + } + } + + return ( +
+
e.stopPropagation()}> +
+ + setQuery(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Type a command..." + spellCheck={false} + autoComplete="off" + /> +
+
+ {results.length === 0 && ( +
No matching commands
+ )} + {Array.from(grouped.entries()).map(([category, items]) => ( +
+
+ {CATEGORY_LABELS[category] ?? category} +
+ {items.map((r) => { + const Icon = r.item.icon; + return ( + + ); + })} +
+ ))} +
+
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/modals/MigrationStatusModal.css b/OPUS-tauri-app/src/components/modals/MigrationStatusModal.css new file mode 100644 index 00000000..5d208922 --- /dev/null +++ b/OPUS-tauri-app/src/components/modals/MigrationStatusModal.css @@ -0,0 +1,80 @@ +.modal-card--wide { + max-width: 480px; +} + +.migration-loading { + padding: 16px; + text-align: center; + color: var(--muted); + font-size: 13px; +} + +.migration-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.migration-item { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + padding: 6px 8px; + border-radius: 3px; + font-size: 13px; +} + +.migration-item--failed { + background: rgba(239, 68, 68, 0.06); +} + +.migration-item__name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-family: var(--font-mono); + font-size: 12px; +} + +.migration-item__state { + font-size: 11px; + color: var(--muted); + text-transform: capitalize; +} + +.migration-item__retry { + display: flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border: none; + background: none; + color: var(--accent); + cursor: pointer; + border-radius: 3px; + transition: background var(--transition-fast); +} + +.migration-item__retry:hover { + background: var(--surface); +} + +.migration-item__error { + width: 100%; + font-size: 11px; + color: var(--danger, #ef4444); + padding-left: 22px; +} + +.migration-icon--success { color: var(--success); } +.migration-icon--failed { color: var(--danger, #ef4444); } +.migration-icon--running { color: var(--accent); animation: spin 1s linear infinite; } +.migration-icon--pending { color: var(--muted); } + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/OPUS-tauri-app/src/components/modals/MigrationStatusModal.tsx b/OPUS-tauri-app/src/components/modals/MigrationStatusModal.tsx new file mode 100644 index 00000000..26758288 --- /dev/null +++ b/OPUS-tauri-app/src/components/modals/MigrationStatusModal.tsx @@ -0,0 +1,154 @@ +import { useState, useCallback } from 'react'; +import type { MigrationProgress } from '@/types/jobs'; +import { useAppStore } from '@/state/store'; +import { useTauriEvent } from '@/hooks/useTauriEvent'; +import { migrationCancel, migrationStart } from '@/api/migrations'; +import { Button } from '@/components/ui/Button'; +import { CheckCircle, XCircle, Loader, Circle, RotateCcw } from 'lucide-react'; +import './Modal.css'; +import './MigrationStatusModal.css'; + +export function MigrationStatusModal() { + const activeModal = useAppStore((s) => s.activeModal); + const closeModal = useAppStore((s) => s.closeModal); + const openModal = useAppStore((s) => s.openModal); + + const jobId = (activeModal?.props?.jobId as string) ?? ''; + const worksetId = (activeModal?.props?.worksetId as string) ?? ''; + const repoUrl = (activeModal?.props?.repoUrl as string) ?? ''; + const action = (activeModal?.props?.action as 'add' | 'remove') ?? 'add'; + const deleteWorktrees = activeModal?.props?.deleteWorktrees as boolean | undefined; + const deleteLocal = activeModal?.props?.deleteLocal as boolean | undefined; + + const [progress, setProgress] = useState(null); + const [retrying, setRetrying] = useState(false); + + const handleProgress = useCallback( + (payload: MigrationProgress) => { + if (payload.job_id === jobId) { + setProgress(payload); + } + }, + [jobId], + ); + useTauriEvent('migration:progress', handleProgress); + + const isDone = progress?.state === 'done' || progress?.state === 'failed' || progress?.state === 'canceled'; + const hasFailures = progress?.workspaces.some((ws) => ws.state === 'failed') ?? false; + + async function handleRetryFailed() { + if (!progress || !worksetId || !repoUrl) return; + const failedNames = progress.workspaces + .filter((ws) => ws.state === 'failed') + .map((ws) => ws.workspace_name); + if (failedNames.length === 0) return; + + setRetrying(true); + try { + const { job_id } = await migrationStart({ + worksetId, + repoUrl, + action, + workspaceNames: failedNames, + deleteWorktrees, + deleteLocal, + }); + // Reopen modal with the new job + openModal('migration-status', { + jobId: job_id, + worksetId, + repoUrl, + action, + deleteWorktrees, + deleteLocal, + }); + } finally { + setRetrying(false); + } + } + + async function handleRetryWorkspace(wsName: string) { + if (!worksetId || !repoUrl) return; + setRetrying(true); + try { + const { job_id } = await migrationStart({ + worksetId, + repoUrl, + action, + workspaceNames: [wsName], + deleteWorktrees, + deleteLocal, + }); + openModal('migration-status', { + jobId: job_id, + worksetId, + repoUrl, + action, + deleteWorktrees, + deleteLocal, + }); + } finally { + setRetrying(false); + } + } + + function statusIcon(state: string) { + switch (state) { + case 'success': return ; + case 'failed': return ; + case 'running': return ; + default: return ; + } + } + + return ( +
+
e.stopPropagation()}> +
Migration Progress
+
+ {!progress ? ( +
Waiting for progress...
+ ) : ( +
+ {progress.workspaces.map((ws) => ( +
+ {statusIcon(ws.state)} + {ws.workspace_name} + {ws.state} + {ws.state === 'failed' && isDone && ( + + )} + {ws.error && ( +
{ws.error.message}
+ )} +
+ ))} +
+ )} +
+
+ {!isDone && ( + + )} + {isDone && hasFailures && ( + + )} + +
+
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/modals/Modal.css b/OPUS-tauri-app/src/components/modals/Modal.css new file mode 100644 index 00000000..5b28b8af --- /dev/null +++ b/OPUS-tauri-app/src/components/modals/Modal.css @@ -0,0 +1,138 @@ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal-card { + background: var(--panel); + border: 1px solid var(--border-active); + border-radius: var(--radius-md); + width: 100%; + max-width: 400px; + overflow: hidden; +} + +.modal-repos-card { + background: var(--panel); + border: 1px solid var(--border-active); + border-radius: var(--radius-md); + width: 100%; + max-width: 600px; + overflow: visible; +} + +.modal-repos-card .modal-body { + overflow: visible; +} + +.modal-repos-card .repo-search { + margin-bottom: 0; +} + +.repo-search__spinner { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.modal-header { + padding: var(--space-4); + font-size: 14px; + font-weight: 600; + border-bottom: 1px solid var(--border); +} + +.modal-body { + padding: var(--space-4); + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.modal-label { + font-size: 12px; + font-weight: 500; + color: var(--muted); +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + border-top: 1px solid var(--border); +} + +.modal-repo-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.modal-repo-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 8px; + background: var(--surface); + border-radius: 3px; + font-size: 12px; + font-family: var(--font-mono); +} + +.modal-repo-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +.modal-repo-remove { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border: none; + background: none; + color: var(--muted); + cursor: pointer; + flex-shrink: 0; + border-radius: 3px; +} + +.modal-repo-remove:hover { + background: var(--border); + color: var(--text); +} + +.remove-confirm__description { + font-size: 13px; + color: var(--text); + margin: 0; + line-height: 1.5; +} + +.remove-confirm__checkbox { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: var(--text); + cursor: pointer; +} + +.remove-confirm__checkbox input[type="checkbox"] { + accent-color: var(--accent); +} diff --git a/OPUS-tauri-app/src/components/modals/RepoRemoveConfirmModal.tsx b/OPUS-tauri-app/src/components/modals/RepoRemoveConfirmModal.tsx new file mode 100644 index 00000000..a0a1a5b5 --- /dev/null +++ b/OPUS-tauri-app/src/components/modals/RepoRemoveConfirmModal.tsx @@ -0,0 +1,87 @@ +import { useState } from 'react'; +import { useAppStore } from '@/state/store'; +import { migrationStart } from '@/api/migrations'; +import { listWorkspaces } from '@/api/workspaces'; +import { Button } from '@/components/ui/Button'; +import './Modal.css'; + +export function RepoRemoveConfirmModal() { + const activeModal = useAppStore((s) => s.activeModal); + const closeModal = useAppStore((s) => s.closeModal); + const openModal = useAppStore((s) => s.openModal); + const removeWorksetRepo = useAppStore((s) => s.removeWorksetRepo); + + const worksetId = (activeModal?.props?.worksetId as string) ?? ''; + const repoUrl = (activeModal?.props?.repoUrl as string) ?? ''; + + const [deleteWorktrees, setDeleteWorktrees] = useState(true); + const [deleteLocal, setDeleteLocal] = useState(false); + const [removing, setRemoving] = useState(false); + + async function handleConfirm() { + if (!worksetId || !repoUrl) return; + setRemoving(true); + try { + await removeWorksetRepo(repoUrl); + const freshWorkspaces = await listWorkspaces(worksetId); + const wsNames = freshWorkspaces.map((ws) => ws.name); + if (wsNames.length > 0) { + const { job_id } = await migrationStart({ + worksetId, + repoUrl, + action: 'remove', + workspaceNames: wsNames, + deleteWorktrees, + deleteLocal, + }); + openModal('migration-status', { + jobId: job_id, + worksetId, + repoUrl, + action: 'remove', + deleteWorktrees, + deleteLocal, + }); + } else { + closeModal(); + } + } finally { + setRemoving(false); + } + } + + return ( +
+
e.stopPropagation()}> +
Remove Repository
+
+

+ Remove {repoUrl} from all workspaces? +

+ + +
+
+ + +
+
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/modals/WorksetCreateModal.tsx b/OPUS-tauri-app/src/components/modals/WorksetCreateModal.tsx new file mode 100644 index 00000000..ebca61ef --- /dev/null +++ b/OPUS-tauri-app/src/components/modals/WorksetCreateModal.tsx @@ -0,0 +1,387 @@ +import { useState, useEffect, useRef, useMemo } from 'react'; +import { useAppStore } from '@/state/store'; +import { Button } from '@/components/ui/Button'; +import { Input } from '@/components/ui/Input'; +import { listGitHubRepos, githubAuthStatus, listGitHubAccounts, switchGitHubAccount } from '@/api/github'; +import type { GitHubRepo, GitHubAccount } from '@/types/github'; +import { X, FolderGit2, Lock, Search, ChevronDown, Check, Loader2 } from 'lucide-react'; +import './Modal.css'; +import '../pages/CommandCenter/CommandCenterPage.css'; + +type Step = 'name' | 'repos'; + +function parseRepoName(repoUrl: string): { org: string; repo: string } { + const match = repoUrl.match(/(?:github\.com\/)?([^/]+)\/([^/]+?)(?:\.git)?$/); + if (match) return { org: match[1], repo: match[2] }; + return { org: '', repo: repoUrl }; +} + +export function WorksetCreateModal() { + const createWorkset = useAppStore((s) => s.createWorkset); + const setActiveWorkset = useAppStore((s) => s.setActiveWorkset); + const addWorksetRepo = useAppStore((s) => s.addWorksetRepo); + const closeModal = useAppStore((s) => s.closeModal); + + const [step, setStep] = useState('name'); + const [name, setName] = useState(''); + const [loading, setLoading] = useState(false); + + // Repos step + const [repoInput, setRepoInput] = useState(''); + const [repos, setRepos] = useState([]); + const [addingRepo, setAddingRepo] = useState(false); + const [repoError, setRepoError] = useState(null); + + // GitHub autocomplete state + const [allRepos, setAllRepos] = useState([]); + const [ghAvailable, setGhAvailable] = useState(null); + const [reposLoading, setReposLoading] = useState(false); + const [accounts, setAccounts] = useState([]); + const [activeAccount, setActiveAccount] = useState(null); + const [switching, setSwitching] = useState(false); + const [showSuggestions, setShowSuggestions] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(-1); + const [accountDropdownOpen, setAccountDropdownOpen] = useState(false); + const searchRef = useRef(null); + const accountDropdownRef = useRef(null); + + function loadRepos() { + setReposLoading(true); + listGitHubRepos() + .then(setAllRepos) + .catch(() => {}) + .finally(() => setReposLoading(false)); + } + + // Pre-fetch GitHub data on mount so it's ready when user reaches the repos step + useEffect(() => { + setReposLoading(true); + githubAuthStatus() + .then((s) => { + setGhAvailable(s.authenticated); + if (s.authenticated) { + loadRepos(); + listGitHubAccounts() + .then((accts) => { + setAccounts(accts); + const active = accts.find((a) => a.active); + if (active) setActiveAccount(active.login); + }) + .catch(() => {}); + } else { + setReposLoading(false); + } + }) + .catch(() => { + setGhAvailable(false); + setReposLoading(false); + }); + }, []); + + async function handleSwitchAccount(login: string) { + if (login === activeAccount || switching) return; + setSwitching(true); + try { + await switchGitHubAccount(login); + setActiveAccount(login); + setAccounts((prev) => + prev.map((a) => ({ ...a, active: a.login === login })), + ); + setAllRepos([]); + setRepoInput(''); + loadRepos(); + } finally { + setSwitching(false); + } + } + + // Close dropdowns on outside click + useEffect(() => { + function handleClick(e: MouseEvent) { + if (searchRef.current && !searchRef.current.contains(e.target as Node)) { + setShowSuggestions(false); + } + if (accountDropdownRef.current && !accountDropdownRef.current.contains(e.target as Node)) { + setAccountDropdownOpen(false); + } + } + document.addEventListener('mousedown', handleClick); + return () => document.removeEventListener('mousedown', handleClick); + }, []); + + // Filter repos client-side — exclude already-added repos + const filtered = useMemo(() => { + const q = repoInput.trim().toLowerCase(); + if (!ghAvailable || q.length < 1 || allRepos.length === 0) return []; + const added = new Set(repos.map((r) => { + const { org, repo } = parseRepoName(r); + return org ? `${org}/${repo}`.toLowerCase() : r.toLowerCase(); + })); + return allRepos + .filter((r) => { + if (added.has(r.full_name.toLowerCase())) return false; + return r.full_name.toLowerCase().includes(q); + }) + .slice(0, 10); + }, [repoInput, allRepos, ghAvailable, repos]); + + // Keep showSuggestions in sync with filtered results + useEffect(() => { + if (repoInput.trim().length >= 1 && filtered.length > 0) { + setShowSuggestions(true); + } else { + setShowSuggestions(false); + } + }, [filtered, repoInput]); + + // Build description lookup + const repoDescriptions = useMemo(() => { + const map = new Map(); + for (const r of allRepos) { + if (r.description) map.set(r.full_name.toLowerCase(), r.description); + } + return map; + }, [allRepos]); + + function handleInputChange(value: string) { + setRepoInput(value); + setSelectedIndex(-1); + setRepoError(null); + } + + async function handleCreateName() { + if (!name.trim()) return; + setLoading(true); + try { + const profile = await createWorkset(name.trim()); + // Don't await — let it run in background so the step transition is instant + setActiveWorkset(profile.id); + setStep('repos'); + } finally { + setLoading(false); + } + } + + async function handleAddRepo(repoUrl?: string) { + let source = repoUrl || repoInput.trim(); + if (!source) return; + // Normalize short "org/repo" to SSH URL + if (!source.includes('://') && !source.includes('@') && !source.startsWith('/') && !source.startsWith('~') && !source.startsWith('.') && source.includes('/')) { + source = `git@github.com:${source}.git`; + } + setAddingRepo(true); + setRepoError(null); + setShowSuggestions(false); + try { + await addWorksetRepo(source); + setRepos((prev) => [...prev, source]); + setRepoInput(''); + } catch (err) { + const msg = + typeof err === 'object' && err !== null && 'message' in err + ? String((err as { message: string }).message) + : 'Failed to add repo'; + setRepoError(msg); + } finally { + setAddingRepo(false); + } + } + + function handleKeyDown(e: React.KeyboardEvent) { + if (!showSuggestions || filtered.length === 0) { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddRepo(); + } + return; + } + + if (e.key === 'ArrowDown') { + e.preventDefault(); + setSelectedIndex((i) => Math.min(i + 1, filtered.length - 1)); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setSelectedIndex((i) => Math.max(i - 1, 0)); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (selectedIndex >= 0 && selectedIndex < filtered.length) { + handleAddRepo(filtered[selectedIndex].full_name); + } else { + handleAddRepo(); + } + } else if (e.key === 'Escape') { + setShowSuggestions(false); + } + } + + function handleDone() { + closeModal(); + } + + if (step === 'name') { + return ( +
+
e.stopPropagation()}> +
Create Workset
+
+ + setName(e.target.value)} + placeholder="e.g. payments-platform" + autoFocus + onKeyDown={(e) => e.key === 'Enter' && handleCreateName()} + /> +
+
+ + +
+
+
+ ); + } + + return ( +
+
e.stopPropagation()}> +
Add Repositories
+
+
+ {accounts.length > 0 && ( +
+ + {accountDropdownOpen && accounts.length > 1 && ( +
+ {accounts.map((acct) => ( + + ))} +
+ )} +
+ )} + {accounts.length > 0 &&
} + {reposLoading ? ( + + ) : ( + + )} + handleInputChange(e.target.value)} + onFocus={() => repoInput.trim().length >= 1 && filtered.length > 0 && setShowSuggestions(true)} + onKeyDown={handleKeyDown} + spellCheck={false} + autoCorrect="off" + autoCapitalize="off" + autoFocus + disabled={addingRepo} + placeholder={ + reposLoading + ? 'Loading your repositories...' + : ghAvailable + ? 'Search your repos or paste a GitHub URL...' + : 'Paste org/repo or a full GitHub URL and press Enter' + } + /> + {addingRepo && Adding...} + {showSuggestions && filtered.length > 0 && ( +
+ {filtered.map((repo, i) => { + const { org, repo: repoName } = parseRepoName(repo.full_name); + return ( + + ); + })} +
+ )} +
+ + {repoError && ( + {repoError} + )} + + {repos.length > 0 && ( +
+ {repos.map((r) => { + const { org, repo } = parseRepoName(r); + const descKey = org ? `${org}/${repo}`.toLowerCase() : r.toLowerCase(); + const desc = repoDescriptions.get(descKey); + return ( +
+ +
+
+ {org && {org}/} + {repo} +
+ {desc &&
{desc}
} +
+ +
+ ); + })} +
+ )} +
+
+ +
+
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/CommandCenter/CommandCenterPage.css b/OPUS-tauri-app/src/components/pages/CommandCenter/CommandCenterPage.css new file mode 100644 index 00000000..ae8fe63f --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/CommandCenter/CommandCenterPage.css @@ -0,0 +1,408 @@ +.command-center { + padding: var(--space-5) var(--space-6); + overflow-y: auto; + width: 100%; + height: 100%; +} + +.command-center__header h2 { + font-size: 16px; + font-weight: 600; + margin: 0 0 var(--space-5); +} + +.command-center__section { + margin-bottom: var(--space-5); +} + +.command-center__section-title { + font-size: 13px; + font-weight: 600; + margin: 0 0 var(--space-3); +} + +/* ─── Composite Search Bar ────────────────────────────────────── */ + +.repo-search { + position: relative; + display: flex; + align-items: center; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); + margin-bottom: var(--space-5); + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.repo-search:focus-within { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(129, 140, 248, 0.15); +} + +.repo-search *:focus, +.repo-search *:focus-visible { + outline: none; + box-shadow: none; +} + +/* ── Account selector (left portion) ── */ + +.repo-search__account { + position: relative; + flex-shrink: 0; +} + +.repo-search__account-btn { + display: flex; + align-items: center; + gap: 4px; + padding: 12px 12px 12px 16px; + background: none; + border: none; + outline: none; + color: var(--text); + font-size: 13px; + font-family: var(--font-mono); + font-weight: 500; + cursor: pointer; + white-space: nowrap; + transition: color var(--transition-fast); +} + +.repo-search__account-btn:hover { + color: var(--accent); +} + +.repo-search__account-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.repo-search__account-chevron { + color: var(--muted); + flex-shrink: 0; +} + +.repo-search__account-name { + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; +} + +.repo-search__account-dropdown { + position: absolute; + top: calc(100% + 6px); + left: 0; + min-width: 160px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + z-index: 60; + padding: 4px; +} + +.repo-search__account-option { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + padding: 8px 10px; + background: none; + border: none; + outline: none; + border-radius: var(--radius-sm); + color: var(--text); + font-size: 13px; + font-family: var(--font-mono); + cursor: pointer; + transition: background var(--transition-fast); +} + +.repo-search__account-option:hover { + background: var(--surface-hover); +} + +.repo-search__account-option svg { + color: var(--accent); +} + +/* ── Divider ── */ + +.repo-search__divider { + width: 1px; + align-self: stretch; + margin: 8px 0; + background: var(--border); + flex-shrink: 0; +} + +/* ── Search input (right portion) ── */ + +.repo-search__icon { + flex-shrink: 0; + margin-left: 12px; + color: var(--muted); + pointer-events: none; + opacity: 0.5; + transition: color var(--transition-fast), opacity var(--transition-fast); +} + +.repo-search:focus-within .repo-search__icon { + color: var(--accent); + opacity: 1; +} + +.repo-search__input { + flex: 1; + padding: 14px 16px 14px 8px; + background: none; + border: none; + color: var(--text); + font-size: 15px; + font-family: var(--font-body); + outline: none; + min-width: 0; +} + +.repo-search__input::placeholder { + color: var(--muted); +} + +.repo-search__input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.repo-search__loading { + flex-shrink: 0; + padding-right: 14px; + font-size: 12px; + color: var(--accent); + font-weight: 500; +} + +/* ─── Autocomplete Suggestions ────────────────────────────────── */ + +.repo-suggestions { + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + z-index: 50; + max-height: 300px; + overflow-y: auto; + padding: 4px; +} + +.repo-suggestions__item { + display: flex; + align-items: flex-start; + gap: 10px; + width: 100%; + padding: 10px 12px; + border-radius: var(--radius-sm); + cursor: pointer; + text-align: left; + background: none; + border: none; + color: var(--text); + font-family: inherit; + transition: background var(--transition-fast); +} + +.repo-suggestions__item:hover, +.repo-suggestions__item--selected { + background: var(--surface-hover); +} + +.repo-suggestions__icon { + color: var(--muted); + flex-shrink: 0; + margin-top: 2px; +} + +.repo-suggestions__info { + flex: 1; + min-width: 0; +} + +.repo-suggestions__name { + font-size: 13px; + font-family: var(--font-mono); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.repo-suggestions__org { + color: var(--muted); + font-weight: 400; +} + +.repo-suggestions__desc { + font-size: 11px; + color: var(--muted); + margin-top: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.repo-suggestions__private { + color: var(--muted); + flex-shrink: 0; + margin-top: 3px; + opacity: 0.6; +} + +/* ─── Repository List ──────────────────────────────────────────── */ + +.repo-list { + display: flex; + flex-direction: column; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--surface); + overflow: hidden; +} + +.repo-row { + display: flex; + align-items: flex-start; + gap: 14px; + padding: 14px 16px; + transition: background var(--transition-fast); +} + +.repo-row + .repo-row { + border-top: 1px solid var(--border); +} + +.repo-row:hover { + background: var(--surface-hover); +} + +.repo-row__icon { + color: var(--muted); + flex-shrink: 0; + margin-top: 2px; + transition: color var(--transition-fast); +} + +.repo-row:hover .repo-row__icon { + color: var(--accent); +} + +.repo-row__info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 3px; +} + +.repo-row__name-line { + font-size: 14px; + font-family: var(--font-mono); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.repo-row__org { + color: var(--muted); + font-weight: 400; +} + +.repo-row__name { + font-weight: 600; + color: var(--text); +} + +.repo-row__desc { + font-size: 12px; + color: var(--muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.repo-row__remove { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: var(--radius-sm); + border: none; + background: none; + color: var(--muted); + cursor: pointer; + opacity: 0; + margin-top: -2px; + transition: all var(--transition-fast); +} + +.repo-row:hover .repo-row__remove { + opacity: 1; +} + +.repo-row__remove:hover { + background: var(--danger-soft); + color: var(--danger); +} + +/* Diagnostics */ +.command-center__diagnostics { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.command-center__env-details { + margin-top: var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} + +.command-center__env-row { + display: flex; + align-items: baseline; + gap: var(--space-3); + padding: 8px 12px; + font-size: 12px; +} + +.command-center__env-row + .command-center__env-row { + border-top: 1px solid var(--border); +} + +.command-center__env-label { + min-width: 120px; + font-weight: 500; + color: var(--muted); + flex-shrink: 0; +} + +.command-center__env-value { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.command-center__env-value--wrap { + white-space: normal; + word-break: break-all; +} diff --git a/OPUS-tauri-app/src/components/pages/CommandCenter/CommandCenterPage.tsx b/OPUS-tauri-app/src/components/pages/CommandCenter/CommandCenterPage.tsx new file mode 100644 index 00000000..507f2d34 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/CommandCenter/CommandCenterPage.tsx @@ -0,0 +1,534 @@ +import { useState, useEffect, useRef, useMemo } from 'react'; +import { useAppStore } from '@/state/store'; +import { EmptyState } from '@/components/ui/EmptyState'; +import { Button } from '@/components/ui/Button'; +import { StatusRow } from '@/components/ui/StatusRow'; +import { migrationStart } from '@/api/migrations'; +import { listWorkspaces } from '@/api/workspaces'; +import { listWorkspaceRepos } from '@/api/repos'; +import { listGitHubRepos, githubAuthStatus, listGitHubAccounts, switchGitHubAccount } from '@/api/github'; +import { + envSnapshot, + reloadLoginEnv, + sessiondStatus, + sessiondRestart, + cliStatus, +} from '@/api/diagnostics'; +import type { EnvSnapshot, SessiondStatus, CliStatus } from '@/types/diagnostics'; +import type { GitHubRepo, GitHubAccount } from '@/types/github'; +import type { RepoInstance } from '@/types/repo'; +import { LayoutGrid, FolderGit2, Trash2, Lock, Search, ChevronDown, Check } from 'lucide-react'; +import './CommandCenterPage.css'; + +export function CommandCenterPage() { + const worksets = useAppStore((s) => s.worksets); + const activeWorksetId = useAppStore((s) => s.activeWorksetId); + const activeWorkspaceName = useAppStore((s) => s.activeWorkspaceName); + const addWorksetRepo = useAppStore((s) => s.addWorksetRepo); + const openModal = useAppStore((s) => s.openModal); + const section = useAppStore((s) => s.commandCenterSection); + + const activeWorkset = worksets.find((w) => w.id === activeWorksetId); + + if (!activeWorkset) { + return ( + } + title="No workset selected" + description="Select or create a workset to view its command center." + /> + ); + } + + return ( +
+
+

{activeWorkset.name}

+
+ {section === 'repositories' && ( + + )} + {section === 'diagnostics' && ( + + )} +
+ ); +} + +// --------------------------------------------------------------------------- +// Repositories +// --------------------------------------------------------------------------- +function parseRepoName(repoUrl: string): { org: string; repo: string } { + const match = repoUrl.match(/(?:github\.com\/)?([^/]+)\/([^/]+?)(?:\.git)?$/); + if (match) return { org: match[1], repo: match[2] }; + return { org: '', repo: repoUrl }; +} + +function RepositoriesSection({ + activeWorkset, + activeWorksetId, + addWorksetRepo, + openModal, +}: { + activeWorkset: { repos: string[] }; + activeWorksetId: string; + addWorksetRepo: (source: string) => Promise; + openModal: (type: string, props?: Record) => void; +}) { + const [newRepoUrl, setNewRepoUrl] = useState(''); + const [repoLoading, setRepoLoading] = useState(false); + const [allRepos, setAllRepos] = useState([]); + const [ghAvailable, setGhAvailable] = useState(null); + const [accounts, setAccounts] = useState([]); + const [activeAccount, setActiveAccount] = useState(null); + const [switching, setSwitching] = useState(false); + const [showSuggestions, setShowSuggestions] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(-1); + const [accountDropdownOpen, setAccountDropdownOpen] = useState(false); + const containerRef = useRef(null); + const accountDropdownRef = useRef(null); + + function loadRepos() { + listGitHubRepos() + .then(setAllRepos) + .catch(() => {}); + } + + // Check gh auth, load accounts and repos on mount + useEffect(() => { + githubAuthStatus() + .then((s) => { + setGhAvailable(s.authenticated); + if (s.authenticated) { + loadRepos(); + listGitHubAccounts() + .then((accts) => { + setAccounts(accts); + const active = accts.find((a) => a.active); + if (active) setActiveAccount(active.login); + }) + .catch(() => {}); + } + }) + .catch(() => setGhAvailable(false)); + }, []); + + async function handleSwitchAccount(login: string) { + if (login === activeAccount || switching) return; + setSwitching(true); + try { + await switchGitHubAccount(login); + setActiveAccount(login); + setAccounts((prev) => + prev.map((a) => ({ ...a, active: a.login === login })), + ); + setAllRepos([]); + setNewRepoUrl(''); + loadRepos(); + } finally { + setSwitching(false); + } + } + + // Close dropdowns on outside click + useEffect(() => { + function handleClick(e: MouseEvent) { + if (containerRef.current && !containerRef.current.contains(e.target as Node)) { + setShowSuggestions(false); + } + if (accountDropdownRef.current && !accountDropdownRef.current.contains(e.target as Node)) { + setAccountDropdownOpen(false); + } + } + document.addEventListener('mousedown', handleClick); + return () => document.removeEventListener('mousedown', handleClick); + }, []); + + // Filter repos client-side — exclude already-added repos + const filtered = useMemo(() => { + const q = newRepoUrl.trim().toLowerCase(); + if (!ghAvailable || q.length < 1 || allRepos.length === 0) return []; + // Normalize stored URLs to short "org/repo" for comparison against full_name + const added = new Set(activeWorkset.repos.map((r) => { + const { org, repo } = parseRepoName(r); + return org ? `${org}/${repo}`.toLowerCase() : r.toLowerCase(); + })); + return allRepos + .filter((r) => { + if (added.has(r.full_name.toLowerCase())) return false; + return r.full_name.toLowerCase().includes(q); + }) + .slice(0, 15); + }, [newRepoUrl, allRepos, ghAvailable, activeWorkset.repos]); + + function handleInputChange(value: string) { + setNewRepoUrl(value); + setSelectedIndex(-1); + setShowSuggestions(value.trim().length >= 1 && filtered.length > 0); + } + + // Keep showSuggestions in sync with filtered results + useEffect(() => { + if (newRepoUrl.trim().length >= 1 && filtered.length > 0) { + setShowSuggestions(true); + } else { + setShowSuggestions(false); + } + }, [filtered, newRepoUrl]); + + // Build a lookup for descriptions from the fetched repo data + // Keys are normalized "org/repo" so they match regardless of stored URL format + const repoDescriptions = useMemo(() => { + const map = new Map(); + for (const r of allRepos) { + if (r.description) map.set(r.full_name.toLowerCase(), r.description); + } + return map; + }, [allRepos]); + + function selectSuggestion(fullName: string) { + handleAddRepo(fullName); + } + + async function handleAddRepo(repoUrl?: string) { + let url = repoUrl || newRepoUrl.trim(); + if (!url || !activeWorksetId) return; + // Normalize short "org/repo" to SSH URL so the Go backend recognizes it + if (!url.includes('://') && !url.includes('@') && !url.startsWith('/') && !url.startsWith('~') && !url.startsWith('.') && url.includes('/')) { + url = `git@github.com:${url}.git`; + } + setRepoLoading(true); + setShowSuggestions(false); + try { + await addWorksetRepo(url); + const freshWorkspaces = await listWorkspaces(activeWorksetId); + const wsNames = freshWorkspaces.map((ws) => ws.name); + if (wsNames.length > 0) { + const { job_id } = await migrationStart({ + worksetId: activeWorksetId, + repoUrl: url, + action: 'add', + workspaceNames: wsNames, + }); + openModal('migration-status', { + jobId: job_id, + worksetId: activeWorksetId, + repoUrl: url, + action: 'add', + }); + } + setNewRepoUrl(''); + } finally { + setRepoLoading(false); + } + } + + function handleKeyDown(e: React.KeyboardEvent) { + if (!showSuggestions || filtered.length === 0) { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddRepo(); + } + return; + } + + if (e.key === 'ArrowDown') { + e.preventDefault(); + setSelectedIndex((i) => Math.min(i + 1, filtered.length - 1)); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setSelectedIndex((i) => Math.max(i - 1, 0)); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (selectedIndex >= 0 && selectedIndex < filtered.length) { + selectSuggestion(filtered[selectedIndex].full_name); + } else { + handleAddRepo(); + } + } else if (e.key === 'Escape') { + setShowSuggestions(false); + } + } + + function handleRemoveRepo(repo: string) { + openModal('repo-remove-confirm', { + worksetId: activeWorksetId, + repoUrl: repo, + }); + } + + return ( +
+

Repositories

+ +
+ {accounts.length > 0 && ( +
+ + {accountDropdownOpen && accounts.length > 1 && ( +
+ {accounts.map((acct) => ( + + ))} +
+ )} +
+ )} + {accounts.length > 0 &&
} + + handleInputChange(e.target.value)} + onFocus={() => newRepoUrl.trim().length >= 1 && filtered.length > 0 && setShowSuggestions(true)} + onKeyDown={handleKeyDown} + spellCheck={false} + autoCorrect="off" + autoCapitalize="off" + disabled={repoLoading} + placeholder={ + ghAvailable + ? 'Search your repos or paste a GitHub URL...' + : 'Paste org/repo or a full GitHub URL and press Enter' + } + /> + {repoLoading && Adding...} + {showSuggestions && filtered.length > 0 && ( +
+ {filtered.map((repo, i) => { + const { org, repo: name } = parseRepoName(repo.full_name); + return ( + + ); + })} +
+ )} +
+ + {activeWorkset.repos.length > 0 ? ( +
+ {activeWorkset.repos.map((repoUrl) => { + const { org, repo } = parseRepoName(repoUrl); + const descKey = org ? `${org}/${repo}`.toLowerCase() : repoUrl.toLowerCase(); + const desc = repoDescriptions.get(descKey); + return ( +
+ +
+
+ {org && {org}/} + {repo} +
+ {desc &&
{desc}
} +
+ +
+ ); + })} +
+ ) : ( + } + title="No repositories yet" + description="Search for a repo above or paste a GitHub URL and press Enter." + /> + )} +
+ ); +} + +// --------------------------------------------------------------------------- +// Diagnostics +// --------------------------------------------------------------------------- +function DiagnosticsSection({ activeWorkspaceName }: { activeWorkspaceName: string | null }) { + const [env, setEnv] = useState(null); + const [sessiond, setSessiond] = useState(null); + const [cli, setCli] = useState(null); + const [repoInstances, setRepoInstances] = useState([]); + const [reloading, setReloading] = useState(false); + const [restarting, setRestarting] = useState(false); + + useEffect(() => { + envSnapshot().then(setEnv).catch(() => {}); + sessiondStatus().then(setSessiond).catch(() => {}); + cliStatus().then(setCli).catch(() => {}); + }, []); + + useEffect(() => { + if (activeWorkspaceName) { + listWorkspaceRepos(activeWorkspaceName) + .then(setRepoInstances) + .catch(() => setRepoInstances([])); + } else { + setRepoInstances([]); + } + }, [activeWorkspaceName]); + + async function handleReloadEnv() { + setReloading(true); + try { + const snap = await reloadLoginEnv(); + setEnv(snap); + } finally { + setReloading(false); + } + } + + async function handleRestartSessiond() { + setRestarting(true); + try { + const status = await sessiondRestart(); + setSessiond(status); + } finally { + setRestarting(false); + } + } + + return ( + <> +
+

Runtime

+
+ + + {restarting ? 'Restarting...' : 'Restart'} + + } + /> +
+
+ +
+

Authentication

+
+ + +
+
+ +
+

Environment

+
+ + {reloading ? 'Reloading...' : 'Reload'} + + } + /> +
+ {env && ( +
+
+ HOME + {env.home} +
+
+ PATH + {env.path} +
+ {env.git_ssh_command && ( +
+ GIT_SSH_COMMAND + {env.git_ssh_command} +
+ )} +
+ )} +
+ + {activeWorkspaceName && repoInstances.length > 0 && ( +
+

Workspace Repos

+
+ {repoInstances.map((repo) => ( + + ))} +
+
+ )} + + ); +} diff --git a/OPUS-tauri-app/src/components/pages/Settings/AppearanceSection.css b/OPUS-tauri-app/src/components/pages/Settings/AppearanceSection.css new file mode 100644 index 00000000..fa5c3289 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Settings/AppearanceSection.css @@ -0,0 +1,102 @@ +.appearance-group { + margin-bottom: var(--space-4); +} + +.appearance-group__label { + font-size: 12px; + font-weight: 500; + color: var(--muted); + margin-bottom: var(--space-2); +} + +.appearance-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-3); +} + +.theme-card { + display: flex; + flex-direction: column; + gap: var(--space-2); + padding: 0; + background: none; + border: 2px solid var(--border); + border-radius: var(--radius-md); + cursor: pointer; + overflow: hidden; + transition: border-color var(--transition-fast); +} + +.theme-card:hover { + border-color: var(--border-active); +} + +.theme-card--active { + border-color: var(--accent); +} + +.theme-card__preview { + height: 64px; + padding: 8px; + display: flex; + flex-direction: column; + gap: 4px; + position: relative; +} + +.theme-card__preview-panel { + flex: 1; + border-radius: 2px; + padding: 6px 8px; + display: flex; + flex-direction: column; + gap: 3px; +} + +.theme-card__preview-line { + height: 3px; + width: 70%; + border-radius: 1px; +} + +.theme-card__preview-line--short { + width: 45%; +} + +.theme-card__preview-accent { + position: absolute; + bottom: 8px; + right: 8px; + width: 8px; + height: 8px; + border-radius: 50%; +} + +.theme-card__label { + padding: 0 var(--space-2) var(--space-2); + font-size: 12px; + font-weight: 500; + color: var(--text); + text-align: center; +} + +.terminal-toggle { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + height: 32px; +} + +.terminal-toggle input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent); + cursor: pointer; +} + +.terminal-toggle__label { + font-size: 13px; + color: var(--text); +} diff --git a/OPUS-tauri-app/src/components/pages/Settings/AppearanceSection.tsx b/OPUS-tauri-app/src/components/pages/Settings/AppearanceSection.tsx new file mode 100644 index 00000000..4f4d84c6 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Settings/AppearanceSection.tsx @@ -0,0 +1,135 @@ +import { useAppStore } from '@/state/store'; +import { themes } from '@/styles/themes'; +import type { ThemeDefinition } from '@/styles/themes'; +import { Input } from '@/components/ui/Input'; +import './AppearanceSection.css'; + +function ThemeCard({ theme, active, onSelect }: { + theme: ThemeDefinition; + active: boolean; + onSelect: () => void; +}) { + const t = theme.tokens; + return ( + + ); +} + +export function AppearanceSection() { + const activeThemeId = useAppStore((s) => s.activeThemeId); + const setTheme = useAppStore((s) => s.setTheme); + const terminalStyle = useAppStore((s) => s.terminalStyle); + const setTerminalStyle = useAppStore((s) => s.setTerminalStyle); + + const darkThemes = themes.filter((t) => t.group === 'dark'); + const lightThemes = themes.filter((t) => t.group === 'light'); + + return ( + <> +
+

Theme

+ +
+
Dark
+
+ {darkThemes.map((t) => ( + setTheme(t.id)} + /> + ))} +
+
+ +
+
Light
+
+ {lightThemes.map((t) => ( + setTheme(t.id)} + /> + ))} +
+
+
+ +
+

Terminal

+ +
+
+ + setTerminalStyle({ fontFamily: e.target.value })} + placeholder="'JetBrains Mono', monospace" + /> +
+ +
+ + { + const v = parseInt(e.target.value, 10); + if (!isNaN(v) && v >= 8 && v <= 32) setTerminalStyle({ fontSize: v }); + }} + /> +
+ +
+ + { + const v = parseFloat(e.target.value); + if (!isNaN(v) && v >= 1.0 && v <= 2.0) setTerminalStyle({ lineHeight: v }); + }} + /> +
+ +
+ + +
+
+
+ + ); +} diff --git a/OPUS-tauri-app/src/components/pages/Settings/SettingsPage.css b/OPUS-tauri-app/src/components/pages/Settings/SettingsPage.css new file mode 100644 index 00000000..31c5e416 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Settings/SettingsPage.css @@ -0,0 +1,118 @@ +.settings-page { + padding: var(--space-5) var(--space-6); + max-width: 640px; + overflow-y: auto; +} + +.settings-page__title { + font-size: 16px; + font-weight: 600; + margin: 0 0 var(--space-5); +} + +.settings-section { + margin-bottom: var(--space-5); +} + +.settings-section__title { + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--muted); + margin: 0 0 var(--space-3); +} + +.settings-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: var(--space-3); +} + +.settings-field { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.settings-field__label { + font-size: 12px; + font-weight: 500; + color: var(--muted); +} + +.settings-field__hint { + font-size: 12px; + color: var(--muted); +} + +.settings-repo-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.settings-repo-list__item { + font-size: 12px; + padding: 4px 8px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-sm); +} + +.settings-repo-list__item code { + font-family: var(--font-mono); + font-size: 11px; +} + +.settings-diagnostics { + display: flex; + flex-direction: column; + gap: 6px; +} + +.settings-env-details { + display: flex; + flex-direction: column; + gap: 4px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 3px; + padding: 8px 12px; +} + +.settings-env-row { + display: flex; + gap: 12px; + font-size: 12px; + padding: 4px 0; + border-bottom: 1px solid var(--border); +} + +.settings-env-row:last-child { + border-bottom: none; +} + +.settings-env-label { + font-weight: 600; + color: var(--muted); + flex-shrink: 0; + min-width: 140px; +} + +.settings-env-value { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.settings-env-value--wrap { + white-space: normal; + word-break: break-all; +} diff --git a/OPUS-tauri-app/src/components/pages/Settings/SettingsPage.tsx b/OPUS-tauri-app/src/components/pages/Settings/SettingsPage.tsx new file mode 100644 index 00000000..25baa41b --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Settings/SettingsPage.tsx @@ -0,0 +1,210 @@ +import { useState, useEffect } from 'react'; +import { useAppStore } from '@/state/store'; +import { Input } from '@/components/ui/Input'; +import { Button } from '@/components/ui/Button'; +import { StatusRow } from '@/components/ui/StatusRow'; +import { + envSnapshot, + reloadLoginEnv, + sessiondStatus, + sessiondRestart, + cliStatus, +} from '@/api/diagnostics'; +import type { EnvSnapshot, SessiondStatus, CliStatus } from '@/types/diagnostics'; +import { AppearanceSection } from './AppearanceSection'; +import './SettingsPage.css'; + +export function SettingsPage() { + const section = useAppStore((s) => s.settingsSection); + + return ( +
+

Settings

+ {section === 'app' && } + {section === 'appearance' && } + {section === 'workset' && } + {section === 'diagnostics' && } +
+ ); +} + +// --------------------------------------------------------------------------- +// App Settings +// --------------------------------------------------------------------------- +function AppSettingsSection() { + const [env, setEnv] = useState(null); + + useEffect(() => { + envSnapshot().then(setEnv).catch(() => {}); + }, []); + + return ( +
+

App Settings

+
+
+ + +
+
+ + +
+
+
+ ); +} + +// --------------------------------------------------------------------------- +// Workset Settings +// --------------------------------------------------------------------------- +function WorksetSettingsSection() { + const worksets = useAppStore((s) => s.worksets); + const activeWorksetId = useAppStore((s) => s.activeWorksetId); + const activeWorkset = worksets.find((w) => w.id === activeWorksetId); + + if (!activeWorkset) { + return ( +
+

Workset Settings

+

No workset selected

+
+ ); + } + + return ( +
+

Workset: {activeWorkset.name}

+
+
+ + {activeWorkset.repos.length === 0 ? ( + No repositories added + ) : ( +
    + {activeWorkset.repos.map((r) => ( +
  • + {r} +
  • + ))} +
+ )} +
+
+
+ ); +} + +// --------------------------------------------------------------------------- +// Diagnostics +// --------------------------------------------------------------------------- +function DiagnosticsSection() { + const [env, setEnv] = useState(null); + const [sessiond, setSessiond] = useState(null); + const [cli, setCli] = useState(null); + const [reloading, setReloading] = useState(false); + const [restarting, setRestarting] = useState(false); + + useEffect(() => { + envSnapshot().then(setEnv).catch(() => {}); + sessiondStatus().then(setSessiond).catch(() => {}); + cliStatus().then(setCli).catch(() => {}); + }, []); + + async function handleReloadEnv() { + setReloading(true); + try { + const snap = await reloadLoginEnv(); + setEnv(snap); + } finally { + setReloading(false); + } + } + + async function handleRestartSessiond() { + setRestarting(true); + try { + const status = await sessiondRestart(); + setSessiond(status); + } finally { + setRestarting(false); + } + } + + return ( + <> +
+

Diagnostics

+
+ + + {restarting ? 'Restarting...' : 'Restart'} + + } + /> + + + + {reloading ? 'Reloading...' : 'Reload'} + + } + /> +
+
+ +
+

Environment Details

+ {env && ( +
+
+ HOME + {env.home} +
+
+ PATH + {env.path} +
+ {env.git_ssh_command && ( +
+ GIT_SSH_COMMAND + {env.git_ssh_command} +
+ )} +
+ )} +
+ + ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/CreateTabModal.css b/OPUS-tauri-app/src/components/pages/Spaces/CreateTabModal.css new file mode 100644 index 00000000..e59ce78d --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/CreateTabModal.css @@ -0,0 +1,43 @@ +.modal-card--narrow { + max-width: 360px; +} + +.create-tab-options { + display: flex; + flex-direction: column; + gap: 8px; +} + +.create-tab-option { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + border-radius: 3px; + background: var(--surface); + border: 1px solid var(--border); + color: var(--text); + text-align: left; + transition: background var(--transition-fast), border-color var(--transition-fast); +} + +.create-tab-option:hover { + background: var(--bg); + border-color: var(--accent); +} + +.create-tab-option__text { + display: flex; + flex-direction: column; + gap: 2px; +} + +.create-tab-option__title { + font-size: 13px; + font-weight: 500; +} + +.create-tab-option__desc { + font-size: 11px; + color: var(--muted); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/CreateTabModal.tsx b/OPUS-tauri-app/src/components/pages/Spaces/CreateTabModal.tsx new file mode 100644 index 00000000..bb39a039 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/CreateTabModal.tsx @@ -0,0 +1,57 @@ +import { useAppStore } from '@/state/store'; +import { Button } from '@/components/ui/Button'; +import { Terminal, Bot } from 'lucide-react'; +import '@/components/modals/Modal.css'; +import './CreateTabModal.css'; + +export function CreateTabModal() { + const activeModal = useAppStore((s) => s.activeModal); + const closeModal = useAppStore((s) => s.closeModal); + const allocatePtySession = useAppStore((s) => s.allocatePtySession); + const addTab = useAppStore((s) => s.addTab); + + const paneId = (activeModal?.props?.paneId as string) ?? 'main'; + const workspaceName = (activeModal?.props?.workspaceName as string) ?? ''; + + function handleCreate(kind: 'terminal' | 'agent') { + if (!workspaceName) return; + const terminalId = allocatePtySession(workspaceName, kind); + const tabId = `tab-${Date.now()}`; + addTab(paneId, { + id: tabId, + terminal_id: terminalId, + title: kind === 'agent' ? 'Agent' : 'Terminal', + kind, + }); + closeModal(); + } + + return ( +
+
e.stopPropagation()}> +
New Tab
+
+
+ + +
+
+
+ +
+
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/CreateWorkspaceModal.tsx b/OPUS-tauri-app/src/components/pages/Spaces/CreateWorkspaceModal.tsx new file mode 100644 index 00000000..07e3dc28 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/CreateWorkspaceModal.tsx @@ -0,0 +1,96 @@ +import { useState } from 'react'; +import { useAppStore } from '@/state/store'; +import { Button } from '@/components/ui/Button'; +import { Input } from '@/components/ui/Input'; +import '@/components/modals/Modal.css'; + +export function CreateWorkspaceModal() { + const activeWorksetId = useAppStore((s) => s.activeWorksetId); + const worksets = useAppStore((s) => s.worksets); + const createWorkspace = useAppStore((s) => s.createWorkspace); + const setActiveWorkspace = useAppStore((s) => s.setActiveWorkspace); + const loadLayout = useAppStore((s) => s.loadLayout); + const allocatePtySession = useAppStore((s) => s.allocatePtySession); + const addTab = useAppStore((s) => s.addTab); + const closeModal = useAppStore((s) => s.closeModal); + const [name, setName] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const activeWorkset = worksets.find((w) => w.id === activeWorksetId); + + async function handleCreate() { + if (!name.trim() || !activeWorksetId) return; + setLoading(true); + setError(null); + try { + const wsName = name.trim(); + await createWorkspace(activeWorksetId, wsName); + await setActiveWorkspace(wsName); + await loadLayout(wsName); + + // Open an initial terminal tab + try { + const terminalId = allocatePtySession(wsName, 'terminal'); + addTab('main', { + id: `tab-${Date.now()}`, + terminal_id: terminalId, + title: 'Terminal', + kind: 'terminal', + }); + } catch { + // Non-fatal — workspace is created even if terminal setup fails + } + + closeModal(); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message + : typeof err === 'object' && err !== null && 'message' in err ? String((err as { message: string }).message) + : 'Failed to create workspace'; + setError(msg); + } finally { + setLoading(false); + } + } + + return ( +
+
e.stopPropagation()}> +
Create Workspace
+
+ + setName(e.target.value)} + placeholder="e.g. feature/search-ranking" + autoFocus + onKeyDown={(e) => e.key === 'Enter' && handleCreate()} + /> + {activeWorkset && activeWorkset.repos.length > 0 && ( +
+ +
+ {activeWorkset.repos.map((r) => ( +
+ {r} +
+ ))} +
+
+ )} +
+ {error && ( +
+ {error} +
+ )} +
+ + +
+
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/SpacesPage.css b/OPUS-tauri-app/src/components/pages/Spaces/SpacesPage.css new file mode 100644 index 00000000..51d99fa4 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/SpacesPage.css @@ -0,0 +1,14 @@ +.spaces-page { + display: flex; + flex: 1; + overflow: hidden; + min-width: 0; + min-height: 0; +} + +.spaces-page--loading { + align-items: center; + justify-content: center; + color: var(--muted); + font-size: 13px; +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/SpacesPage.tsx b/OPUS-tauri-app/src/components/pages/Spaces/SpacesPage.tsx new file mode 100644 index 00000000..4b6ee422 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/SpacesPage.tsx @@ -0,0 +1,99 @@ +import { useEffect } from 'react'; +import { useAppStore } from '@/state/store'; +import { EmptyState } from '@/components/ui/EmptyState'; +import { Button } from '@/components/ui/Button'; +import { TerminalLayoutNodeView } from './TerminalLayoutNode'; +import { Layers } from 'lucide-react'; +import './SpacesPage.css'; + +export function SpacesPage() { + const activeWorksetId = useAppStore((s) => s.activeWorksetId); + const activeWorkspaceName = useAppStore((s) => s.activeWorkspaceName); + const workspaces = useAppStore((s) => s.workspaces); + const layout = useAppStore((s) => s.layout); + const loadLayout = useAppStore((s) => s.loadLayout); + const saveLayout = useAppStore((s) => s.saveLayout); + const openModal = useAppStore((s) => s.openModal); + const worksets = useAppStore((s) => s.worksets); + + // Load layout when workspace changes + useEffect(() => { + if (activeWorkspaceName) { + loadLayout(activeWorkspaceName); + } + }, [activeWorkspaceName, loadLayout]); + + // Auto-save layout on changes + useEffect(() => { + if (activeWorkspaceName && layout) { + const timer = setTimeout(() => { + saveLayout(activeWorkspaceName); + }, 1000); + return () => clearTimeout(timer); + } + }, [activeWorkspaceName, layout, saveLayout]); + + if (worksets.length === 0) { + return ( + } + title="Create your first workset" + description="A workset groups repos together. Create one to get started." + action={ + + } + /> + ); + } + + if (!activeWorksetId) { + return ( + } + title="Select a workset to get started" + description="Use the workset selector in the top bar to choose or create a workset." + /> + ); + } + + if (workspaces.length === 0) { + return ( + } + title="Create your first workspace" + description="A workspace is a named work thread across all repos in this workset." + action={ + + } + /> + ); + } + + if (!activeWorkspaceName) { + return ( + } + title="Select a workspace" + description="Choose a workspace from the sidebar to begin." + /> + ); + } + + if (!layout) { + return ( +
+ Loading layout... +
+ ); + } + + return ( +
+ +
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TabContent.css b/OPUS-tauri-app/src/components/pages/Spaces/TabContent.css new file mode 100644 index 00000000..47aeb8a2 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TabContent.css @@ -0,0 +1,15 @@ +.tab-content { + flex: 1; + min-height: 0; + overflow: hidden; + position: relative; +} + +.tab-content--empty, +.tab-content--diff { + display: flex; + align-items: center; + justify-content: center; + color: var(--muted); + font-size: 13px; +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TabContent.tsx b/OPUS-tauri-app/src/components/pages/Spaces/TabContent.tsx new file mode 100644 index 00000000..53154552 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TabContent.tsx @@ -0,0 +1,50 @@ +import type { LayoutTab } from '@/types/layout'; +import { TerminalSurface } from './TerminalSurface'; +import { DiffTabView } from '@/components/diff/DiffTabView'; +import './TabContent.css'; + +type Props = { + tab: LayoutTab | undefined; + workspaceName: string; +}; + +export function TabContent({ tab, workspaceName }: Props) { + if (!tab) { + return ( +
+ No tabs open +
+ ); + } + + if (tab.kind === 'diff') { + if (!tab.diff_repo_path || !tab.diff_file_path || !tab.diff_status) { + return ( +
+ Missing diff metadata +
+ ); + } + return ( +
+ +
+ ); + } + + // Both 'terminal' and 'agent' kinds use the terminal surface + return ( +
+ +
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TabStrip.css b/OPUS-tauri-app/src/components/pages/Spaces/TabStrip.css new file mode 100644 index 00000000..9808a01a --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TabStrip.css @@ -0,0 +1,204 @@ +.tab-strip { + display: flex; + align-items: center; + height: 36px; + padding: 0 4px; + background: var(--panel); + border-bottom: 1px solid var(--border); + gap: 2px; + flex-shrink: 0; +} + +.tab-strip__tabs { + display: flex; + align-items: center; + gap: 2px; + overflow-x: auto; + flex: 1; + min-width: 0; +} + +.tab-strip__tabs::-webkit-scrollbar { + display: none; +} + +.tab-strip__tab { + display: flex; + align-items: center; + gap: 6px; + height: 28px; + padding: 0 8px; + border-radius: 3px; + font-size: 12px; + color: var(--muted); + white-space: nowrap; + cursor: grab; + transition: background var(--transition-fast), color var(--transition-fast); + flex-shrink: 0; + position: relative; +} + +.tab-strip__tab:hover { + background: var(--surface); + color: var(--text); +} + +.tab-strip__tab.active { + background: var(--surface); + color: var(--text); +} + +.tab-strip__icon { + flex-shrink: 0; + opacity: 0.7; +} + +.tab-strip__label { + overflow: hidden; + text-overflow: ellipsis; + max-width: 120px; +} + +.tab-strip__close { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 3px; + font-size: 16px; + line-height: 1; + color: var(--muted); + opacity: 0; + cursor: pointer; + transition: opacity var(--transition-fast), background var(--transition-fast); +} + +.tab-strip__tab:hover .tab-strip__close { + opacity: 1; +} + +.tab-strip__close:hover { + background: var(--border-hover); + color: var(--text); +} + +.tab-strip__add { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 3px; + color: var(--muted); + cursor: pointer; + flex-shrink: 0; + transition: background var(--transition-fast), color var(--transition-fast); +} + +.tab-strip__add:hover { + background: var(--surface); + color: var(--text); +} + +/* Drop indicator line shown during tab drag */ +.tab-strip__drop-line { + position: absolute; + left: -1px; + top: 4px; + bottom: 4px; + width: 2px; + background: var(--accent); + border-radius: 1px; + z-index: 1; + pointer-events: none; +} + +.tab-strip__drop-line--end { + position: relative; + left: auto; + height: 20px; + flex-shrink: 0; +} + +/* Floating ghost that follows the cursor during tab drag */ +.tab-strip__ghost { + position: fixed; + display: flex; + align-items: center; + gap: 6px; + height: 28px; + padding: 0 10px; + border-radius: 3px; + font-size: 12px; + color: var(--text); + background: var(--surface); + border: 1px solid var(--border-active); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + white-space: nowrap; + pointer-events: none; + z-index: 10000; + opacity: 0.9; +} + +.tab-strip__ghost .tab-strip__icon { + opacity: 0.8; +} + +/* Inline rename input */ +.tab-strip__rename-input { + width: 90px; + height: 20px; + padding: 0 4px; + border: 1px solid var(--accent); + border-radius: 2px; + background: var(--bg); + color: var(--text); + font-size: 12px; + font-family: inherit; + outline: none; +} + +/* Context menu */ +.tab-context-menu { + position: fixed; + z-index: 10000; + min-width: 160px; + padding: 4px 0; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 3px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); +} + +.tab-context-menu button { + display: block; + width: 100%; + padding: 6px 12px; + text-align: left; + font-size: 12px; + color: var(--text); + background: none; + border: none; + cursor: pointer; +} + +.tab-context-menu button:hover { + background: var(--surface); +} + +.tab-context-menu__separator { + height: 1px; + margin: 4px 0; + background: var(--border); +} + +/* Body state during tab drag */ +body.tab-dragging { + cursor: grabbing !important; + user-select: none; +} + +body.tab-dragging * { + cursor: grabbing !important; +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TabStrip.tsx b/OPUS-tauri-app/src/components/pages/Spaces/TabStrip.tsx new file mode 100644 index 00000000..697f5f0d --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TabStrip.tsx @@ -0,0 +1,346 @@ +import { useState, useEffect, useRef, useCallback } from 'react'; +import type { LayoutTab } from '@/types/layout'; +import { useAppStore } from '@/state/store'; +import { Terminal, Bot, GitCompare, Plus } from 'lucide-react'; +import './TabStrip.css'; + +type Props = { + paneId: string; + tabs: LayoutTab[]; + activeTabId?: string; + workspaceName: string; +}; + +const tabIcons: Record = { + terminal: Terminal, + agent: Bot, + diff: GitCompare, +}; + +// ── Module-level drag state shared across all TabStrip instances ── + +type DragInfo = { tabId: string; fromPaneId: string }; +type DropTarget = { paneId: string; index: number }; + +let activeDrag: DragInfo | null = null; +let activeDropTarget: DropTarget | null = null; + +type StripEntry = { + element: HTMLDivElement; + tabsContainer: HTMLDivElement; + setDropIdx: (n: number | null) => void; +}; +const stripRegistry = new Map(); + +function getTabDropIndex(tabsEl: HTMLDivElement, x: number): number { + const tabEls = Array.from(tabsEl.querySelectorAll('[role="tab"]')) as HTMLElement[]; + for (let i = 0; i < tabEls.length; i++) { + const rect = tabEls[i].getBoundingClientRect(); + if (x < rect.left + rect.width / 2) return i; + } + return tabEls.length; +} + +function findStripAtPoint(x: number, y: number): { paneId: string; entry: StripEntry } | null { + for (const [paneId, entry] of stripRegistry) { + const rect = entry.element.getBoundingClientRect(); + if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { + return { paneId, entry }; + } + } + return null; +} + +function clearAllDropIndicators() { + stripRegistry.forEach(({ setDropIdx }) => setDropIdx(null)); +} + +// Floating ghost element that follows the cursor during drag +let ghostEl: HTMLDivElement | null = null; + +function createGhost(sourceTab: HTMLElement) { + const ghost = document.createElement('div'); + ghost.className = 'tab-strip__ghost'; + + // Clone the tab content (icon + label) but not the close button + const icon = sourceTab.querySelector('.tab-strip__icon'); + const label = sourceTab.querySelector('.tab-strip__label'); + if (icon) ghost.appendChild(icon.cloneNode(true)); + if (label) ghost.appendChild(label.cloneNode(true)); + + document.body.appendChild(ghost); + ghostEl = ghost; +} + +function moveGhost(x: number, y: number) { + if (!ghostEl) return; + ghostEl.style.left = `${x + 12}px`; + ghostEl.style.top = `${y - 14}px`; +} + +function removeGhost() { + if (ghostEl) { + ghostEl.remove(); + ghostEl = null; + } +} + +// ── Component ── + +export function TabStrip({ paneId, tabs, activeTabId, workspaceName }: Props) { + const setActiveTab = useAppStore((s) => s.setActiveTab); + const removeTab = useAppStore((s) => s.removeTab); + const closePtySession = useAppStore((s) => s.closePtySession); + const allocatePtySession = useAppStore((s) => s.allocatePtySession); + const addTab = useAppStore((s) => s.addTab); + const moveTab = useAppStore((s) => s.moveTab); + const reorderTab = useAppStore((s) => s.reorderTab); + const renameTab = useAppStore((s) => s.renameTab); + const splitPane = useAppStore((s) => s.splitPane); + + const [dropIdx, setDropIdx] = useState(null); + const [contextMenu, setContextMenu] = useState<{ x: number; y: number; tab: LayoutTab } | null>(null); + const [renamingTabId, setRenamingTabId] = useState(null); + const [renameValue, setRenameValue] = useState(''); + const renameInputRef = useRef(null); + const stripRef = useRef(null); + const tabsRef = useRef(null); + + // Register this TabStrip so other instances can find it during drag + useEffect(() => { + if (!stripRef.current || !tabsRef.current) return; + stripRegistry.set(paneId, { + element: stripRef.current, + tabsContainer: tabsRef.current, + setDropIdx, + }); + return () => { stripRegistry.delete(paneId); }; + }, [paneId]); + + // Close context menu on outside click + useEffect(() => { + if (!contextMenu) return; + const handler = () => setContextMenu(null); + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [contextMenu]); + + // Focus rename input when entering rename mode + useEffect(() => { + if (renamingTabId) renameInputRef.current?.focus(); + }, [renamingTabId]); + + function handleContextMenu(e: React.MouseEvent, tab: LayoutTab) { + e.preventDefault(); + setContextMenu({ x: e.clientX, y: e.clientY, tab }); + } + + function startRename(tab: LayoutTab) { + setRenamingTabId(tab.id); + setRenameValue(tab.title); + setContextMenu(null); + } + + function commitRename() { + if (renamingTabId && renameValue.trim()) { + renameTab(paneId, renamingTabId, renameValue.trim()); + } + setRenamingTabId(null); + } + + function cancelRename() { + setRenamingTabId(null); + } + + const handleTabClick = useCallback((tab: LayoutTab) => { + setActiveTab(paneId, tab.id); + }, [paneId, setActiveTab]); + + function handleClose(e: React.MouseEvent, tab: LayoutTab) { + e.stopPropagation(); + removeTab(paneId, tab.id); + if (tab.kind !== 'diff') { + closePtySession(tab.terminal_id); + } + } + + function handleAddTerminal() { + if (!workspaceName) return; + const terminalId = allocatePtySession(workspaceName, 'terminal'); + const tabId = `tab-${Date.now()}`; + addTab(paneId, { + id: tabId, + terminal_id: terminalId, + title: 'Terminal', + kind: 'terminal', + }); + } + + function handleTabMouseDown(e: React.MouseEvent, tab: LayoutTab) { + // Only left-click + if (e.button !== 0) return; + // Don't start drag from close button + if ((e.target as HTMLElement).closest('.tab-strip__close')) return; + + e.preventDefault(); + const startX = e.clientX; + const startY = e.clientY; + const sourceEl = (e.target as HTMLElement).closest('.tab-strip__tab') as HTMLElement | null; + let dragStarted = false; + + const onMouseMove = (ev: MouseEvent) => { + if (!dragStarted) { + const dx = Math.abs(ev.clientX - startX); + const dy = Math.abs(ev.clientY - startY); + if (dx + dy < 5) return; + dragStarted = true; + activeDrag = { tabId: tab.id, fromPaneId: paneId }; + document.body.classList.add('tab-dragging'); + if (sourceEl) createGhost(sourceEl); + } + + moveGhost(ev.clientX, ev.clientY); + + // Clear all indicators, then set the one we're hovering + clearAllDropIndicators(); + const hit = findStripAtPoint(ev.clientX, ev.clientY); + if (hit) { + const idx = getTabDropIndex(hit.entry.tabsContainer, ev.clientX); + hit.entry.setDropIdx(idx); + activeDropTarget = { paneId: hit.paneId, index: idx }; + } else { + activeDropTarget = null; + } + }; + + const onMouseUp = () => { + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + + if (dragStarted && activeDrag && activeDropTarget) { + const { tabId: dragTabId, fromPaneId: dragFromPane } = activeDrag; + const { paneId: targetPane, index: targetIdx } = activeDropTarget; + + if (dragFromPane === targetPane) { + reorderTab(targetPane, dragTabId, targetIdx); + } else { + moveTab(dragFromPane, targetPane, dragTabId); + reorderTab(targetPane, dragTabId, targetIdx); + } + } else if (!dragStarted) { + // No drag occurred — treat as a click + handleTabClick(tab); + } + + activeDrag = null; + activeDropTarget = null; + document.body.classList.remove('tab-dragging'); + clearAllDropIndicators(); + removeGhost(); + }; + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + } + + return ( +
+
+ {tabs.map((tab, i) => { + const Icon = tabIcons[tab.kind] ?? Terminal; + const isActive = tab.id === activeTabId; + const isRenaming = renamingTabId === tab.id; + return ( +
handleTabMouseDown(e, tab)} + onContextMenu={(e) => handleContextMenu(e, tab)} + > + {dropIdx === i &&
} + + {isRenaming ? ( + setRenameValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') commitRename(); + if (e.key === 'Escape') cancelRename(); + }} + onBlur={commitRename} + onMouseDown={(e) => e.stopPropagation()} + /> + ) : ( + startRename(tab)} + > + {tab.title} + + )} + +
+ ); + })} + {dropIdx === tabs.length &&
} +
+ + + {contextMenu && ( +
e.stopPropagation()} + > + + + +
+ + +
+ )} +
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TerminalLayoutNode.css b/OPUS-tauri-app/src/components/pages/Spaces/TerminalLayoutNode.css new file mode 100644 index 00000000..366efa70 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TerminalLayoutNode.css @@ -0,0 +1,49 @@ +.layout-pane { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; + min-height: 0; + overflow: hidden; +} + +.layout-pane.focused { + outline: 1px solid var(--accent-soft); + outline-offset: -1px; +} + +.layout-split { + display: flex; + flex: 1; + min-width: 0; + min-height: 0; + overflow: hidden; + position: relative; +} + +.layout-split__divider { + flex-shrink: 0; + background: var(--border); + position: relative; + z-index: 1; +} + +.layout-split__divider--row { + width: 5px; + cursor: col-resize; +} + +.layout-split__divider--col { + height: 5px; + cursor: row-resize; +} + +.layout-split__divider:hover { + background: var(--accent-soft); +} + +.layout-split__drag-overlay { + position: fixed; + inset: 0; + z-index: 9999; +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TerminalLayoutNode.tsx b/OPUS-tauri-app/src/components/pages/Spaces/TerminalLayoutNode.tsx new file mode 100644 index 00000000..92371e1b --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TerminalLayoutNode.tsx @@ -0,0 +1,110 @@ +import { useRef, useCallback, useState } from 'react'; +import type { LayoutNode, SplitNode } from '@/types/layout'; +import { TabStrip } from './TabStrip'; +import { TabContent } from './TabContent'; +import { useAppStore } from '@/state/store'; +import './TerminalLayoutNode.css'; + +type Props = { + node: LayoutNode; + workspaceName: string; +}; + +function SplitDivider({ split }: { split: SplitNode }) { + const setSplitRatio = useAppStore((s) => s.setSplitRatio); + const containerRef = useRef(null); + const [dragging, setDragging] = useState(false); + const isRow = split.direction === 'row'; + + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + setDragging(true); + + const parent = containerRef.current?.parentElement; + if (!parent) return; + + const onMouseMove = (ev: MouseEvent) => { + const rect = parent.getBoundingClientRect(); + const pos = isRow ? ev.clientX - rect.left : ev.clientY - rect.top; + const total = isRow ? rect.width : rect.height; + if (total <= 0) return; + const ratio = pos / total; + setSplitRatio(split.id, ratio); + }; + + const onMouseUp = () => { + setDragging(false); + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + + document.body.style.cursor = isRow ? 'col-resize' : 'row-resize'; + document.body.style.userSelect = 'none'; + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + }, + [split.id, isRow, setSplitRatio], + ); + + return ( + <> + {dragging &&
} +
+ + ); +} + +export function TerminalLayoutNodeView({ node, workspaceName }: Props) { + const focusedPaneId = useAppStore((s) => s.focusedPaneId); + const setFocusedPane = useAppStore((s) => s.setFocusedPane); + + if (node.kind === 'pane') { + const activeTab = node.tabs.find((t) => t.id === node.active_tab_id) ?? node.tabs[0]; + const isFocused = node.id === focusedPaneId; + + return ( +
setFocusedPane(node.id)} + > + + +
+ ); + } + + // Split node + const isRow = node.direction === 'row'; + const firstSize = `calc(${(node.ratio * 100).toFixed(1)}% - 3px)`; + const secondSize = `calc(${((1 - node.ratio) * 100).toFixed(1)}% - 3px)`; + + return ( +
+
+ +
+ +
+ +
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TerminalSurface.css b/OPUS-tauri-app/src/components/pages/Spaces/TerminalSurface.css new file mode 100644 index 00000000..428b63ef --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TerminalSurface.css @@ -0,0 +1,24 @@ +.terminal-surface { + position: absolute; + inset: 0; + overflow: hidden; + background: var(--bg); +} + +.terminal-surface .xterm { + height: 100%; + padding: 4px 8px; +} + +.terminal-surface .xterm-viewport { + overflow-y: auto; +} + +.terminal-surface .xterm-viewport::-webkit-scrollbar { + width: 6px; +} + +.terminal-surface .xterm-viewport::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/TerminalSurface.tsx b/OPUS-tauri-app/src/components/pages/Spaces/TerminalSurface.tsx new file mode 100644 index 00000000..0f729b1e --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/TerminalSurface.tsx @@ -0,0 +1,206 @@ +import { useEffect, useRef } from 'react'; +import { Terminal } from '@xterm/xterm'; +import { FitAddon } from '@xterm/addon-fit'; +import { WebglAddon } from '@xterm/addon-webgl'; +import { WebLinksAddon } from '@xterm/addon-web-links'; +import { ClipboardAddon } from '@xterm/addon-clipboard'; +import { Unicode11Addon } from '@xterm/addon-unicode11'; +import { useAppStore } from '@/state/store'; +import { useTerminalTheme } from '@/styles/ThemeProvider'; +import { + terminalSpawn, + terminalAttach, + terminalDetach, + terminalWrite, + terminalResize, +} from '@/api/pty'; +import type { PtyEvent } from '@/api/pty'; +import '@xterm/xterm/css/xterm.css'; +import './TerminalSurface.css'; + +type Props = { + workspaceName: string; + terminalId: string; +}; + +export function TerminalSurface({ workspaceName, terminalId }: Props) { + const termTheme = useTerminalTheme(); + const terminalStyle = useAppStore((s) => s.terminalStyle); + const containerRef = useRef(null); + const terminalRef = useRef(null); + const fitAddonRef = useRef(null); + const openedRef = useRef(false); + + // Use a ref for the PTY event handler so the main effect never re-runs + // due to callback identity changes (e.g. during HMR). + const ptyEventRef = useRef<(event: PtyEvent) => void>(() => {}); + ptyEventRef.current = (event: PtyEvent) => { + const term = terminalRef.current; + if (!term || !openedRef.current) return; + + switch (event.type) { + case 'Data': + term.write(event.data); + break; + case 'Closed': + useAppStore.getState().closePtySession(terminalId); + useAppStore.getState().closeTabByTerminalId(terminalId); + break; + case 'Error': + console.error('[TerminalSurface] PTY error:', event.message); + useAppStore.getState().updatePtyStatus(terminalId, 'error'); + break; + } + }; + + // Stable handler that delegates to the ref — identity never changes. + const stableHandler = useRef((event: PtyEvent) => { + ptyEventRef.current(event); + }).current; + + // Main terminal lifecycle — only re-runs when identity changes. + useEffect(() => { + if (!containerRef.current) return; + const container = containerRef.current; + let disposed = false; + + const style = useAppStore.getState().terminalStyle; + const term = new Terminal({ + fontSize: style.fontSize, + fontFamily: style.fontFamily, + lineHeight: style.lineHeight, + cursorBlink: style.cursorBlink, + scrollback: 10000, + allowProposedApi: true, + theme: termTheme, + }); + + const fitAddon = new FitAddon(); + term.loadAddon(fitAddon); + term.loadAddon(new WebLinksAddon()); + term.loadAddon(new ClipboardAddon()); + term.loadAddon(new Unicode11Addon()); + + terminalRef.current = term; + fitAddonRef.current = fitAddon; + openedRef.current = false; + + const tryOpen = () => { + if (disposed || openedRef.current) return; + if (container.clientWidth <= 0 || container.clientHeight <= 0) return; + + term.open(container); + term.unicode.activeVersion = '11'; + + try { + term.loadAddon(new WebglAddon()); + } catch (e) { + console.warn('WebGL renderer unavailable, using canvas fallback:', e); + } + + openedRef.current = true; + + const safeFit = () => { + try { fitAddon.fit(); } catch { /* renderer not ready */ } + }; + if (document.fonts?.ready) { + document.fonts.ready.then(() => { + if (!disposed && openedRef.current) safeFit(); + requestAnimationFrame(() => { + if (!disposed && openedRef.current) term.focus(); + }); + }); + } else { + safeFit(); + requestAnimationFrame(() => { + if (!disposed && openedRef.current) term.focus(); + }); + } + + // Wire up input, resize, and title change + term.onData((data) => { + terminalWrite(terminalId, data); + }); + term.onResize(({ cols, rows }) => { + terminalResize(terminalId, cols, rows); + }); + term.onTitleChange((title) => { + useAppStore.getState().updateTabTitle(terminalId, title); + }); + + // Spawn or reattach the PTY + const isSpawned = useAppStore.getState().ptySessions[terminalId]?.spawned; + + if (isSpawned) { + // Reattach — ring buffer replay restores screen content + terminalAttach(terminalId, stableHandler) + .then(() => { + if (!disposed) useAppStore.getState().updatePtyStatus(terminalId, 'connected'); + }) + .catch((err) => { + console.error('[TerminalSurface] attach failed, trying spawn:', err); + spawnFallback(); + }); + } else { + spawnFallback(); + } + + function spawnFallback() { + const ws = useAppStore.getState().workspaces.find((w) => w.name === workspaceName); + const cwd = ws?.path ?? '/'; + terminalSpawn(terminalId, cwd, stableHandler) + .then(() => { + if (!disposed) useAppStore.getState().markPtySpawned(terminalId); + }) + .catch((e) => { + console.error('[TerminalSurface] spawn failed:', e); + if (!disposed) useAppStore.getState().updatePtyStatus(terminalId, 'error'); + }); + } + }; + + const observer = new ResizeObserver(() => { + if (!openedRef.current) { + tryOpen(); + } else if (!disposed) { + try { fitAddon.fit(); } catch { /* renderer not ready */ } + } + }); + observer.observe(container); + requestAnimationFrame(tryOpen); + + return () => { + disposed = true; + observer.disconnect(); + // Detach only — PTY keeps running in the backend + terminalDetach(terminalId).catch(() => {}); + term.dispose(); + terminalRef.current = null; + fitAddonRef.current = null; + openedRef.current = false; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [workspaceName, terminalId]); + + // Live-update terminal theme without recreating the PTY session + useEffect(() => { + const term = terminalRef.current; + if (term && openedRef.current) { + term.options.theme = termTheme; + } + }, [termTheme]); + + // Live-update terminal font/style + useEffect(() => { + const term = terminalRef.current; + if (term && openedRef.current) { + term.options.fontSize = terminalStyle.fontSize; + term.options.fontFamily = terminalStyle.fontFamily; + term.options.lineHeight = terminalStyle.lineHeight; + term.options.cursorBlink = terminalStyle.cursorBlink; + try { fitAddonRef.current?.fit(); } catch { /* renderer not ready */ } + } + }, [terminalStyle]); + + return
; +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceItem.css b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceItem.css new file mode 100644 index 00000000..f896bee7 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceItem.css @@ -0,0 +1,104 @@ +.workspace-item { + display: flex; + align-items: center; + gap: var(--space-2); + width: 100%; + padding: 8px var(--space-3); + border-radius: var(--radius-md); + text-align: left; + font-size: 13px; + color: var(--text); + cursor: pointer; + transition: background var(--transition-fast); +} + +.workspace-item:hover { + background: var(--surface); +} + +.workspace-item.active { + background: var(--accent-soft); +} + +.workspace-item__dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} + +.workspace-item__name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 500; +} + +.workspace-item__menu-anchor { + position: relative; + flex-shrink: 0; +} + +.workspace-item__more { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + background: none; + color: var(--muted); + cursor: pointer; + border-radius: 3px; + font-size: 16px; + line-height: 1; + opacity: 0; + transition: opacity var(--transition-fast), background var(--transition-fast); +} + +.workspace-item:hover .workspace-item__more { + opacity: 1; +} + +.workspace-item__more:hover { + background: var(--border); + color: var(--text); +} + +.workspace-item__menu { + position: absolute; + top: 100%; + right: 0; + min-width: 140px; + background: var(--panel-strong); + border: 1px solid var(--border-active); + border-radius: 3px; + padding: 4px 0; + z-index: 200; +} + +.workspace-item__menu-item { + display: block; + width: 100%; + padding: 6px 12px; + font-size: 13px; + text-align: left; + color: var(--text); + border: none; + background: none; + cursor: pointer; + transition: background var(--transition-fast); +} + +.workspace-item__menu-item:hover { + background: var(--surface-hover); +} + +.workspace-item__menu-item--danger { + color: var(--danger, #ef4444); +} + +.workspace-item__menu-item--confirm { + background: rgba(239, 68, 68, 0.1); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceItem.tsx b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceItem.tsx new file mode 100644 index 00000000..ba7907d7 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceItem.tsx @@ -0,0 +1,80 @@ +import { useState, useRef, useEffect } from 'react'; +import type { WorkspaceSummary } from '@/types/workspace'; +import './WorkspaceItem.css'; + +type Props = { + workspace: WorkspaceSummary; + active: boolean; + onClick: () => void; + onDelete: () => Promise; +}; + +export function WorkspaceItem({ workspace, active, onClick, onDelete }: Props) { + const [menuOpen, setMenuOpen] = useState(false); + const [confirming, setConfirming] = useState(false); + const [deleting, setDeleting] = useState(false); + const menuRef = useRef(null); + + useEffect(() => { + if (!menuOpen) return; + function handleClick(e: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + setMenuOpen(false); + setConfirming(false); + } + } + document.addEventListener('mousedown', handleClick); + return () => document.removeEventListener('mousedown', handleClick); + }, [menuOpen]); + + function handleMenuToggle(e: React.MouseEvent) { + e.stopPropagation(); + setMenuOpen(!menuOpen); + setConfirming(false); + } + + function handleDelete(e: React.MouseEvent) { + e.stopPropagation(); + if (!confirming) { + setConfirming(true); + return; + } + setDeleting(true); + onDelete().catch(() => { + setDeleting(false); + setMenuOpen(false); + }); + } + + return ( +
+ + {workspace.name} +
+ + {menuOpen && ( +
+ +
+ )} +
+
+ ); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceList.css b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceList.css new file mode 100644 index 00000000..6c46fd93 --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceList.css @@ -0,0 +1,12 @@ +.workspace-list { + display: flex; + flex-direction: column; + padding: 0 var(--space-2); +} + +.workspace-list__empty { + padding: var(--space-4); + text-align: center; + font-size: 12px; + color: var(--muted); +} diff --git a/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceList.tsx b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceList.tsx new file mode 100644 index 00000000..0924347f --- /dev/null +++ b/OPUS-tauri-app/src/components/pages/Spaces/WorkspaceList.tsx @@ -0,0 +1,49 @@ +import { useEffect } from 'react'; +import { useAppStore } from '@/state/store'; +import { WorkspaceItem } from './WorkspaceItem'; +import './WorkspaceList.css'; + +export function WorkspaceList() { + const workspaces = useAppStore((s) => s.workspaces); + const activeWorksetId = useAppStore((s) => s.activeWorksetId); + const activeWorkspaceName = useAppStore((s) => s.activeWorkspaceName); + const loadWorkspaces = useAppStore((s) => s.loadWorkspaces); + const setActiveWorkspace = useAppStore((s) => s.setActiveWorkspace); + const deleteWorkspace = useAppStore((s) => s.deleteWorkspace); + + useEffect(() => { + if (activeWorksetId) { + loadWorkspaces(activeWorksetId); + } + }, [activeWorksetId, loadWorkspaces]); + + if (!activeWorksetId) return null; + + async function handleDelete(name: string) { + await deleteWorkspace(activeWorksetId!, name); + if (activeWorkspaceName === name) { + // Clear active workspace since it was deleted + const remaining = useAppStore.getState().workspaces; + if (remaining.length > 0) { + setActiveWorkspace(remaining[0].name); + } + } + } + + return ( +
+ {workspaces.map((ws) => ( + setActiveWorkspace(ws.name)} + onDelete={() => handleDelete(ws.name)} + /> + ))} + {workspaces.length === 0 && ( +
No workspaces
+ )} +
+ ); +} diff --git a/OPUS-tauri-app/src/components/ui/Button.css b/OPUS-tauri-app/src/components/ui/Button.css new file mode 100644 index 00000000..4ba32a53 --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/Button.css @@ -0,0 +1,60 @@ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + border-radius: var(--radius-md); + font-size: 13px; + font-weight: 500; + transition: all var(--transition-fast); + white-space: nowrap; +} + +.btn--md { + padding: 6px 14px; +} + +.btn--sm { + padding: 4px 10px; + font-size: 12px; +} + +.btn--primary { + background: var(--accent); + color: #fff; +} +.btn--primary:hover { + opacity: 0.9; +} + +.btn--secondary { + background: var(--surface); + border: 1px solid var(--border); + color: var(--text); +} +.btn--secondary:hover { + border-color: var(--border-active); + background: var(--surface-hover); +} + +.btn--ghost { + background: transparent; + color: var(--muted); +} +.btn--ghost:hover { + background: var(--surface); + color: var(--text); +} + +.btn--danger { + background: var(--danger); + color: #fff; +} +.btn--danger:hover { + opacity: 0.9; +} + +.btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} diff --git a/OPUS-tauri-app/src/components/ui/Button.tsx b/OPUS-tauri-app/src/components/ui/Button.tsx new file mode 100644 index 00000000..d813b05f --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/Button.tsx @@ -0,0 +1,16 @@ +import type { ButtonHTMLAttributes, ReactNode } from 'react'; +import './Button.css'; + +type Props = ButtonHTMLAttributes & { + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + size?: 'sm' | 'md'; + children: ReactNode; +}; + +export function Button({ variant = 'secondary', size = 'md', className = '', children, ...rest }: Props) { + return ( + + ); +} diff --git a/OPUS-tauri-app/src/components/ui/EmptyState.css b/OPUS-tauri-app/src/components/ui/EmptyState.css new file mode 100644 index 00000000..62807b58 --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/EmptyState.css @@ -0,0 +1,31 @@ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--space-3); + padding: var(--space-6); + text-align: center; + flex: 1; + min-height: 200px; +} + +.empty-state__icon { + color: var(--muted); +} + +.empty-state__title { + font-size: 14px; + font-weight: 600; + color: var(--text); +} + +.empty-state__desc { + font-size: 13px; + color: var(--muted); + max-width: 300px; +} + +.empty-state__action { + margin-top: var(--space-2); +} diff --git a/OPUS-tauri-app/src/components/ui/EmptyState.tsx b/OPUS-tauri-app/src/components/ui/EmptyState.tsx new file mode 100644 index 00000000..82b9d558 --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/EmptyState.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react'; +import './EmptyState.css'; + +type Props = { + icon?: ReactNode; + title: string; + description?: string; + action?: ReactNode; +}; + +export function EmptyState({ icon, title, description, action }: Props) { + return ( +
+ {icon &&
{icon}
} +
{title}
+ {description &&
{description}
} + {action &&
{action}
} +
+ ); +} diff --git a/OPUS-tauri-app/src/components/ui/Input.css b/OPUS-tauri-app/src/components/ui/Input.css new file mode 100644 index 00000000..6bfd5200 --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/Input.css @@ -0,0 +1,25 @@ +.input { + width: 100%; + padding: 6px 10px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); + color: var(--text); + font-size: 13px; + font-family: var(--font-body); + transition: border-color var(--transition-fast); +} + +.input::placeholder { + color: var(--muted); +} + +.input:focus { + border-color: var(--accent); + outline: none; +} + +.input:disabled { + opacity: 0.5; + cursor: not-allowed; +} diff --git a/OPUS-tauri-app/src/components/ui/Input.tsx b/OPUS-tauri-app/src/components/ui/Input.tsx new file mode 100644 index 00000000..b92f19d6 --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/Input.tsx @@ -0,0 +1,8 @@ +import type { InputHTMLAttributes } from 'react'; +import './Input.css'; + +type Props = InputHTMLAttributes; + +export function Input({ className = '', ...rest }: Props) { + return ; +} diff --git a/OPUS-tauri-app/src/components/ui/StatusRow.css b/OPUS-tauri-app/src/components/ui/StatusRow.css new file mode 100644 index 00000000..72aedd47 --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/StatusRow.css @@ -0,0 +1,34 @@ +.status-row { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-radius: 3px; + font-size: 13px; + border: 1px solid var(--border); + background: var(--surface); +} + +.status-row__icon--ok { color: var(--success); } +.status-row__icon--warning { color: var(--warning); } +.status-row__icon--error { color: var(--danger); } +.status-row__icon--unknown { color: var(--muted); } + +.status-row__label { + font-weight: 500; +} + +.status-row__value { + flex: 1; + text-align: right; + font-family: var(--font-mono); + font-size: 12px; + color: var(--muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.status-row__action { + flex-shrink: 0; +} diff --git a/OPUS-tauri-app/src/components/ui/StatusRow.tsx b/OPUS-tauri-app/src/components/ui/StatusRow.tsx new file mode 100644 index 00000000..146514fc --- /dev/null +++ b/OPUS-tauri-app/src/components/ui/StatusRow.tsx @@ -0,0 +1,27 @@ +import { CheckCircle, XCircle, AlertCircle } from 'lucide-react'; +import './StatusRow.css'; + +type Props = { + label: string; + status: 'ok' | 'warning' | 'error' | 'unknown'; + value?: string; + action?: React.ReactNode; +}; + +const statusIcons = { + ok: , + warning: , + error: , + unknown: , +}; + +export function StatusRow({ label, status, value, action }: Props) { + return ( +
+ {statusIcons[status]} + {label} + {value && {value}} + {action &&
{action}
} +
+ ); +} diff --git a/OPUS-tauri-app/src/hooks/useGlobalShortcuts.ts b/OPUS-tauri-app/src/hooks/useGlobalShortcuts.ts new file mode 100644 index 00000000..a7b4e278 --- /dev/null +++ b/OPUS-tauri-app/src/hooks/useGlobalShortcuts.ts @@ -0,0 +1,49 @@ +import { useEffect } from 'react'; +import { getCommands } from '@/commands/registry'; +import type { KeyboardShortcut } from '@/commands/registry'; + +function matchesShortcut(e: KeyboardEvent, shortcut: KeyboardShortcut): boolean { + const wantMeta = shortcut.modifiers.includes('meta'); + const wantShift = shortcut.modifiers.includes('shift'); + const wantAlt = shortcut.modifiers.includes('alt'); + const wantCtrl = shortcut.modifiers.includes('ctrl'); + + if (e.metaKey !== wantMeta) return false; + if (e.shiftKey !== wantShift) return false; + if (e.altKey !== wantAlt) return false; + if (e.ctrlKey !== wantCtrl) return false; + + return e.key.toLowerCase() === shortcut.key; +} + +export function useGlobalShortcuts() { + useEffect(() => { + function handleKeyDown(e: KeyboardEvent) { + // When focused on a text input, only intercept modifier-based + // shortcuts (Cmd+...) and Escape — never plain keystrokes. + if (!e.metaKey && e.key !== 'Escape') { + const target = e.target as HTMLElement; + if ( + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.isContentEditable + ) { + return; + } + } + + const commands = getCommands(); + for (const cmd of commands) { + if (!cmd.shortcut) continue; + if (!matchesShortcut(e, cmd.shortcut)) continue; + if (cmd.when && !cmd.when()) continue; + e.preventDefault(); + cmd.execute(); + return; + } + } + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, []); +} diff --git a/OPUS-tauri-app/src/hooks/useTauriEvent.ts b/OPUS-tauri-app/src/hooks/useTauriEvent.ts new file mode 100644 index 00000000..a55e01a0 --- /dev/null +++ b/OPUS-tauri-app/src/hooks/useTauriEvent.ts @@ -0,0 +1,19 @@ +import { useEffect } from 'react'; +import { onEvent } from '@/api/events'; + +/** + * Subscribe to a Tauri event, auto-unsubscribing on unmount. + */ +export function useTauriEvent(event: string, handler: (payload: T) => void) { + useEffect(() => { + let unlisten: (() => void) | undefined; + + onEvent(event, handler).then((fn) => { + unlisten = fn; + }); + + return () => { + unlisten?.(); + }; + }, [event, handler]); +} diff --git a/OPUS-tauri-app/src/main.tsx b/OPUS-tauri-app/src/main.tsx new file mode 100644 index 00000000..47718102 --- /dev/null +++ b/OPUS-tauri-app/src/main.tsx @@ -0,0 +1,5 @@ +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './styles/global.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/OPUS-tauri-app/src/state/slices/diffSlice.ts b/OPUS-tauri-app/src/state/slices/diffSlice.ts new file mode 100644 index 00000000..7c295a08 --- /dev/null +++ b/OPUS-tauri-app/src/state/slices/diffSlice.ts @@ -0,0 +1,62 @@ +import type { StateCreator } from 'zustand'; +import type { DiffSummary, FilePatch } from '@/types/diff'; +import { diffSummary, diffFilePatch } from '@/api/diff'; + +export type RepoDiffState = { + repo: string; + repoPath: string; + summary: DiffSummary | null; + loading: boolean; +}; + +export type DiffSlice = { + repoDiffs: Record; + loadDiffSummary: (workspaceName: string, repo: string, repoPath: string) => Promise; + updateDiffSummary: (repo: string, summary: DiffSummary) => void; + fetchFilePatch: (repoPath: string, path: string, prevPath: string | undefined, status: string) => Promise; +}; + +export const createDiffSlice: StateCreator = (set, _get) => ({ + repoDiffs: {}, + + loadDiffSummary: async (workspaceName, repo, repoPath) => { + set((state) => ({ + repoDiffs: { + ...state.repoDiffs, + [repo]: { repo, repoPath, summary: state.repoDiffs[repo]?.summary ?? null, loading: true }, + }, + })); + try { + const summary = await diffSummary(workspaceName, repo, repoPath); + set((state) => ({ + repoDiffs: { + ...state.repoDiffs, + [repo]: { repo, repoPath, summary, loading: false }, + }, + })); + } catch { + set((state) => ({ + repoDiffs: { + ...state.repoDiffs, + [repo]: { ...state.repoDiffs[repo], loading: false }, + }, + })); + } + }, + + updateDiffSummary: (repo, summary) => { + set((state) => { + const existing = state.repoDiffs[repo]; + return { + repoDiffs: { + ...state.repoDiffs, + [repo]: { repo, repoPath: existing?.repoPath ?? '', summary, loading: false }, + }, + }; + }); + }, + + fetchFilePatch: async (repoPath, path, prevPath, status) => { + return diffFilePatch(repoPath, path, prevPath, status); + }, +}); diff --git a/OPUS-tauri-app/src/state/slices/layoutSlice.ts b/OPUS-tauri-app/src/state/slices/layoutSlice.ts new file mode 100644 index 00000000..44c19d7b --- /dev/null +++ b/OPUS-tauri-app/src/state/slices/layoutSlice.ts @@ -0,0 +1,285 @@ +import type { StateCreator } from 'zustand'; +import type { TerminalLayout, LayoutNode, LayoutTab, PaneNode, SplitNode } from '@/types/layout'; +import { layoutGet, layoutSave } from '@/api/pty'; +import { findPane, findParentSplit, replaceNodeInTree } from '@/commands/layoutUtils'; + +export type LayoutSlice = { + layout: TerminalLayout | null; + focusedPaneId: string | null; + loadLayout: (workspaceName: string) => Promise; + saveLayout: (workspaceName: string) => Promise; + setLayout: (layout: TerminalLayout) => void; + addTab: (paneId: string, tab: LayoutTab) => void; + removeTab: (paneId: string, tabId: string) => void; + setActiveTab: (paneId: string, tabId: string) => void; + setFocusedPane: (paneId: string) => void; + updateTabTitle: (terminalId: string, title: string) => void; + closeTabByTerminalId: (terminalId: string) => void; + splitPane: (paneId: string, direction: 'row' | 'column') => string; + closePane: (paneId: string) => void; + setSplitRatio: (splitId: string, ratio: number) => void; + moveTab: (fromPaneId: string, toPaneId: string, tabId: string) => void; + reorderTab: (paneId: string, tabId: string, newIndex: number) => void; + renameTab: (paneId: string, tabId: string, title: string) => void; +}; + +function findAndUpdatePane( + node: LayoutNode, + paneId: string, + updater: (pane: PaneNode) => PaneNode, +): LayoutNode { + if (node.kind === 'pane') { + if (node.id === paneId) { + return updater(node); + } + return node; + } + return { + ...node, + first: findAndUpdatePane(node.first, paneId, updater), + second: findAndUpdatePane(node.second, paneId, updater), + }; +} + +function updateTabInNode( + node: LayoutNode, + terminalId: string, + updater: (tab: LayoutTab) => LayoutTab, +): LayoutNode { + if (node.kind === 'pane') { + const updated = node.tabs.map((t) => + t.terminal_id === terminalId ? updater(t) : t, + ); + if (updated === node.tabs) return node; + return { ...node, tabs: updated }; + } + return { + ...node, + first: updateTabInNode(node.first, terminalId, updater), + second: updateTabInNode(node.second, terminalId, updater), + }; +} + +function findTabLocation( + node: LayoutNode, + terminalId: string, +): { paneId: string; tabId: string } | null { + if (node.kind === 'pane') { + const tab = node.tabs.find((t) => t.terminal_id === terminalId); + return tab ? { paneId: node.id, tabId: tab.id } : null; + } + return findTabLocation(node.first, terminalId) ?? findTabLocation(node.second, terminalId); +} + +function defaultLayout(): TerminalLayout { + return { + version: 1, + root: { + kind: 'pane', + id: 'main', + tabs: [], + active_tab_id: undefined, + }, + focused_pane_id: 'main', + }; +} + +export const createLayoutSlice: StateCreator = (set, get) => ({ + layout: null, + focusedPaneId: null, + + loadLayout: async (workspaceName) => { + try { + const saved = await layoutGet(workspaceName); + const layout = saved ?? defaultLayout(); + set({ layout, focusedPaneId: layout.focused_pane_id ?? 'main' }); + } catch { + set({ layout: defaultLayout(), focusedPaneId: 'main' }); + } + }, + + saveLayout: async (workspaceName) => { + const { layout } = get(); + if (layout) { + await layoutSave(workspaceName, layout).catch(() => {}); + } + }, + + setLayout: (layout) => set({ layout }), + + addTab: (paneId, tab) => { + const { layout } = get(); + if (!layout) return; + const newRoot = findAndUpdatePane(layout.root, paneId, (pane) => ({ + ...pane, + tabs: [...pane.tabs, tab], + active_tab_id: tab.id, + })); + set({ layout: { ...layout, root: newRoot } }); + }, + + removeTab: (paneId, tabId) => { + const { layout } = get(); + if (!layout) return; + const newRoot = findAndUpdatePane(layout.root, paneId, (pane) => { + const tabs = pane.tabs.filter((t) => t.id !== tabId); + const activeId = + pane.active_tab_id === tabId + ? tabs[tabs.length - 1]?.id + : pane.active_tab_id; + return { ...pane, tabs, active_tab_id: activeId }; + }); + set({ layout: { ...layout, root: newRoot } }); + + // Auto-collapse empty panes (unless it's the root) + const updatedPane = findPane(newRoot, paneId); + if (updatedPane && updatedPane.tabs.length === 0 && newRoot.id !== paneId) { + get().closePane(paneId); + } + }, + + setActiveTab: (paneId, tabId) => { + const { layout } = get(); + if (!layout) return; + const newRoot = findAndUpdatePane(layout.root, paneId, (pane) => ({ + ...pane, + active_tab_id: tabId, + })); + set({ layout: { ...layout, root: newRoot } }); + }, + + setFocusedPane: (paneId) => { + const { layout } = get(); + if (!layout) return; + set({ + layout: { ...layout, focused_pane_id: paneId }, + focusedPaneId: paneId, + }); + }, + + updateTabTitle: (terminalId, title) => { + const { layout } = get(); + if (!layout) return; + const newRoot = updateTabInNode(layout.root, terminalId, (tab) => ({ + ...tab, + title, + })); + set({ layout: { ...layout, root: newRoot } }); + }, + + closeTabByTerminalId: (terminalId) => { + const { layout } = get(); + if (!layout) return; + const loc = findTabLocation(layout.root, terminalId); + if (!loc) return; + get().removeTab(loc.paneId, loc.tabId); + }, + + splitPane: (paneId, direction) => { + const { layout } = get(); + if (!layout) return paneId; + const pane = findPane(layout.root, paneId); + if (!pane) return paneId; + const newPaneId = `pane-${crypto.randomUUID().slice(0, 8)}`; + const newPane: PaneNode = { kind: 'pane', id: newPaneId, tabs: [], active_tab_id: undefined }; + const split: SplitNode = { + kind: 'split', + id: `split-${crypto.randomUUID().slice(0, 8)}`, + direction, + ratio: 0.5, + first: pane, + second: newPane, + }; + const newRoot = replaceNodeInTree(layout.root, paneId, split); + set({ layout: { ...layout, root: newRoot }, focusedPaneId: newPaneId }); + return newPaneId; + }, + + closePane: (paneId) => { + const { layout, focusedPaneId } = get(); + if (!layout) return; + // Can't close root pane + if (layout.root.id === paneId) return; + const result = findParentSplit(layout.root, paneId); + if (!result) return; + const sibling = result.side === 'first' ? result.parent.second : result.parent.first; + const newRoot = replaceNodeInTree(layout.root, result.parent.id, sibling); + // If focused pane was the closed one, focus the first pane in the sibling + const newFocused = focusedPaneId === paneId + ? (sibling.kind === 'pane' ? sibling.id : findFirstPaneId(sibling)) + : focusedPaneId; + set({ + layout: { ...layout, root: newRoot, focused_pane_id: newFocused ?? undefined }, + focusedPaneId: newFocused, + }); + }, + + setSplitRatio: (splitId, ratio) => { + const { layout } = get(); + if (!layout) return; + const clamped = Math.min(0.85, Math.max(0.15, ratio)); + const newRoot = updateSplitRatio(layout.root, splitId, clamped); + set({ layout: { ...layout, root: newRoot } }); + }, + + moveTab: (fromPaneId, toPaneId, tabId) => { + const { layout } = get(); + if (!layout) return; + if (fromPaneId === toPaneId) return; + const srcPane = findPane(layout.root, fromPaneId); + if (!srcPane) return; + const tab = srcPane.tabs.find((t) => t.id === tabId); + if (!tab) return; + let newRoot = findAndUpdatePane(layout.root, fromPaneId, (pane) => { + const tabs = pane.tabs.filter((t) => t.id !== tabId); + const activeId = pane.active_tab_id === tabId ? tabs[tabs.length - 1]?.id : pane.active_tab_id; + return { ...pane, tabs, active_tab_id: activeId }; + }); + newRoot = findAndUpdatePane(newRoot, toPaneId, (pane) => ({ + ...pane, + tabs: [...pane.tabs, tab], + active_tab_id: tab.id, + })); + set({ layout: { ...layout, root: newRoot }, focusedPaneId: toPaneId }); + }, + + reorderTab: (paneId, tabId, newIndex) => { + const { layout } = get(); + if (!layout) return; + const newRoot = findAndUpdatePane(layout.root, paneId, (pane) => { + const tabs = [...pane.tabs]; + const oldIndex = tabs.findIndex((t) => t.id === tabId); + if (oldIndex === -1 || oldIndex === newIndex) return pane; + const [tab] = tabs.splice(oldIndex, 1); + tabs.splice(newIndex, 0, tab); + return { ...pane, tabs }; + }); + set({ layout: { ...layout, root: newRoot } }); + }, + + renameTab: (paneId, tabId, title) => { + const { layout } = get(); + if (!layout) return; + const newRoot = findAndUpdatePane(layout.root, paneId, (pane) => ({ + ...pane, + tabs: pane.tabs.map((t) => (t.id === tabId ? { ...t, title } : t)), + })); + set({ layout: { ...layout, root: newRoot } }); + }, + +}); + +function findFirstPaneId(node: LayoutNode): string { + if (node.kind === 'pane') return node.id; + return findFirstPaneId(node.first); +} + +function updateSplitRatio(node: LayoutNode, splitId: string, ratio: number): LayoutNode { + if (node.kind === 'pane') return node; + if (node.id === splitId) return { ...node, ratio }; + return { + ...node, + first: updateSplitRatio(node.first, splitId, ratio), + second: updateSplitRatio(node.second, splitId, ratio), + }; +} diff --git a/OPUS-tauri-app/src/state/slices/ptySlice.ts b/OPUS-tauri-app/src/state/slices/ptySlice.ts new file mode 100644 index 00000000..c9523fe4 --- /dev/null +++ b/OPUS-tauri-app/src/state/slices/ptySlice.ts @@ -0,0 +1,83 @@ +import type { StateCreator } from 'zustand'; +import { terminalWrite, terminalResize, terminalKill } from '@/api/pty'; + +export type PtySessionState = { + terminalId: string; + workspaceName: string; + kind: 'terminal' | 'agent'; + status: 'starting' | 'connected' | 'disconnected' | 'error'; + spawned: boolean; +}; + +export type PtySlice = { + ptySessions: Record; + allocatePtySession: (workspaceName: string, kind: 'terminal' | 'agent') => string; + markPtySpawned: (terminalId: string) => void; + closePtySession: (terminalId: string) => Promise; + writePty: (terminalId: string, data: string) => Promise; + resizePty: (terminalId: string, cols: number, rows: number) => Promise; + updatePtyStatus: (terminalId: string, status: PtySessionState['status']) => void; +}; + +export const createPtySlice: StateCreator = (set) => ({ + ptySessions: {}, + + allocatePtySession: (workspaceName, kind) => { + const terminalId = crypto.randomUUID(); + set((state) => ({ + ptySessions: { + ...state.ptySessions, + [terminalId]: { + terminalId, + workspaceName, + kind, + status: 'starting', + spawned: false, + }, + }, + })); + return terminalId; + }, + + markPtySpawned: (terminalId) => { + set((state) => { + const session = state.ptySessions[terminalId]; + if (!session) return state; + return { + ptySessions: { + ...state.ptySessions, + [terminalId]: { ...session, spawned: true, status: 'connected' }, + }, + }; + }); + }, + + closePtySession: async (terminalId) => { + await terminalKill(terminalId).catch(() => {}); + set((state) => { + const { [terminalId]: _, ...rest } = state.ptySessions; + return { ptySessions: rest }; + }); + }, + + writePty: async (terminalId, data) => { + await terminalWrite(terminalId, data); + }, + + resizePty: async (terminalId, cols, rows) => { + await terminalResize(terminalId, cols, rows); + }, + + updatePtyStatus: (terminalId, status) => { + set((state) => { + const session = state.ptySessions[terminalId]; + if (!session) return state; + return { + ptySessions: { + ...state.ptySessions, + [terminalId]: { ...session, status }, + }, + }; + }); + }, +}); diff --git a/OPUS-tauri-app/src/state/slices/uiSlice.ts b/OPUS-tauri-app/src/state/slices/uiSlice.ts new file mode 100644 index 00000000..da47309a --- /dev/null +++ b/OPUS-tauri-app/src/state/slices/uiSlice.ts @@ -0,0 +1,61 @@ +import type { StateCreator } from 'zustand'; + +export type NavPage = 'command-center' | 'spaces' | 'settings'; +export type CommandCenterSection = 'repositories' | 'diagnostics'; +export type SettingsSection = 'app' | 'appearance' | 'workset' | 'diagnostics'; + +export type ModalState = { + type: string; + props?: Record; +} | null; + +export type TerminalStyle = { + fontFamily: string; + fontSize: number; + lineHeight: number; + cursorBlink: boolean; +}; + +export type UiSlice = { + activePage: NavPage; + commandCenterSection: CommandCenterSection; + settingsSection: SettingsSection; + rightPanelCollapsed: boolean; + activeModal: ModalState; + activeThemeId: string; + terminalStyle: TerminalStyle; + setActivePage: (page: NavPage) => void; + setCommandCenterSection: (section: CommandCenterSection) => void; + setSettingsSection: (section: SettingsSection) => void; + toggleRightPanel: () => void; + openModal: (type: string, props?: Record) => void; + closeModal: () => void; + setTheme: (id: string) => void; + setTerminalStyle: (style: Partial) => void; +}; + +export const createUiSlice: StateCreator = (set) => ({ + activePage: 'spaces', + commandCenterSection: 'repositories', + settingsSection: 'app', + rightPanelCollapsed: false, + activeModal: null, + activeThemeId: 'dark', + terminalStyle: { + fontFamily: "'JetBrains Mono', monospace", + fontSize: 13, + lineHeight: 1.2, + cursorBlink: true, + }, + + setActivePage: (page) => set({ activePage: page }), + setCommandCenterSection: (section) => set({ commandCenterSection: section }), + setSettingsSection: (section) => set({ settingsSection: section }), + toggleRightPanel: () => set((s) => ({ rightPanelCollapsed: !s.rightPanelCollapsed })), + openModal: (type, props) => set({ activeModal: { type, props } }), + closeModal: () => set({ activeModal: null }), + setTheme: (id) => set({ activeThemeId: id }), + setTerminalStyle: (patch) => set((s) => ({ + terminalStyle: { ...s.terminalStyle, ...patch }, + })), +}); diff --git a/OPUS-tauri-app/src/state/slices/worksetSlice.ts b/OPUS-tauri-app/src/state/slices/worksetSlice.ts new file mode 100644 index 00000000..9a3393e5 --- /dev/null +++ b/OPUS-tauri-app/src/state/slices/worksetSlice.ts @@ -0,0 +1,90 @@ +import type { StateCreator } from 'zustand'; +import type { WorksetProfile, WorksetDefaults } from '@/types/workset'; +import * as api from '@/api/worksets'; +import * as contextApi from '@/api/context'; + +export type WorksetSlice = { + worksets: WorksetProfile[]; + activeWorksetId: string | null; + activeWorkspaceName: string | null; + worksetsLoading: boolean; + loadWorksets: () => Promise; + createWorkset: (name: string, defaults?: WorksetDefaults) => Promise; + updateWorkset: (id: string, name?: string, defaults?: WorksetDefaults) => Promise; + deleteWorkset: (id: string) => Promise; + setActiveWorkset: (id: string) => Promise; + setActiveWorkspace: (name: string) => Promise; + addWorksetRepo: (source: string) => Promise; + removeWorksetRepo: (source: string) => Promise; +}; + +export const createWorksetSlice: StateCreator = (set, get) => ({ + worksets: [], + activeWorksetId: null, + activeWorkspaceName: null, + worksetsLoading: false, + + loadWorksets: async () => { + set({ worksetsLoading: true }); + try { + const [worksets, context] = await Promise.all([ + api.listWorksets(), + contextApi.getContext(), + ]); + const validWorksetId = worksets.some((w) => w.id === context.active_workset_id) + ? context.active_workset_id + : null; + set({ + worksets, + activeWorksetId: validWorksetId, + activeWorkspaceName: validWorksetId ? (context.active_workspace || null) : null, + worksetsLoading: false, + }); + } catch { + set({ worksetsLoading: false }); + } + }, + + createWorkset: async (name, defaults) => { + const profile = await api.createWorkset(name, defaults); + set((s) => ({ worksets: [...s.worksets, profile] })); + return profile; + }, + + updateWorkset: async (id, name, defaults) => { + const updated = await api.updateWorkset(id, name, defaults); + set((s) => ({ worksets: s.worksets.map((w) => (w.id === id ? updated : w)) })); + }, + + deleteWorkset: async (id) => { + await api.deleteWorkset(id); + set((s) => ({ + worksets: s.worksets.filter((w) => w.id !== id), + activeWorksetId: s.activeWorksetId === id ? null : s.activeWorksetId, + })); + }, + + setActiveWorkset: async (id) => { + await contextApi.setActiveWorkset(id); + set({ activeWorksetId: id, activeWorkspaceName: null }); + }, + + setActiveWorkspace: async (name) => { + await contextApi.setActiveWorkspace(name); + set({ activeWorkspaceName: name }); + }, + + addWorksetRepo: async (source) => { + const { activeWorksetId } = get(); + if (!activeWorksetId) return; + const updated = await api.addWorksetRepo(activeWorksetId, source); + set((s) => ({ worksets: s.worksets.map((w) => (w.id === activeWorksetId ? updated : w)) })); + }, + + removeWorksetRepo: async (source) => { + const { activeWorksetId } = get(); + if (!activeWorksetId) return; + const updated = await api.removeWorksetRepo(activeWorksetId, source); + set((s) => ({ worksets: s.worksets.map((w) => (w.id === activeWorksetId ? updated : w)) })); + }, +}); diff --git a/OPUS-tauri-app/src/state/slices/workspaceSlice.ts b/OPUS-tauri-app/src/state/slices/workspaceSlice.ts new file mode 100644 index 00000000..cfdf983f --- /dev/null +++ b/OPUS-tauri-app/src/state/slices/workspaceSlice.ts @@ -0,0 +1,42 @@ +import type { StateCreator } from 'zustand'; +import type { WorkspaceSummary } from '@/types/workspace'; +import * as api from '@/api/workspaces'; + +export type WorkspaceSlice = { + workspaces: WorkspaceSummary[]; + workspacesLoading: boolean; + loadWorkspaces: (worksetId: string) => Promise; + createWorkspace: (worksetId: string, name: string) => Promise; + deleteWorkspace: (worksetId: string, name: string) => Promise; +}; + +export const createWorkspaceSlice: StateCreator = (set) => ({ + workspaces: [], + workspacesLoading: false, + + loadWorkspaces: async (worksetId) => { + set({ workspacesLoading: true }); + try { + const workspaces = await api.listWorkspaces(worksetId); + set({ workspaces, workspacesLoading: false }); + } catch { + set({ workspacesLoading: false }); + } + }, + + createWorkspace: async (worksetId, name) => { + await api.createWorkspace(worksetId, name); + try { + const workspaces = await api.listWorkspaces(worksetId); + set({ workspaces }); + } catch {} + }, + + deleteWorkspace: async (worksetId, name) => { + await api.deleteWorkspace(worksetId, name, true); + try { + const workspaces = await api.listWorkspaces(worksetId); + set({ workspaces }); + } catch {} + }, +}); diff --git a/OPUS-tauri-app/src/state/store.ts b/OPUS-tauri-app/src/state/store.ts new file mode 100644 index 00000000..d86818f0 --- /dev/null +++ b/OPUS-tauri-app/src/state/store.ts @@ -0,0 +1,18 @@ +import { create } from 'zustand'; +import { createWorksetSlice, type WorksetSlice } from './slices/worksetSlice'; +import { createWorkspaceSlice, type WorkspaceSlice } from './slices/workspaceSlice'; +import { createLayoutSlice, type LayoutSlice } from './slices/layoutSlice'; +import { createPtySlice, type PtySlice } from './slices/ptySlice'; +import { createDiffSlice, type DiffSlice } from './slices/diffSlice'; +import { createUiSlice, type UiSlice } from './slices/uiSlice'; + +export type AppStore = WorksetSlice & WorkspaceSlice & LayoutSlice & PtySlice & DiffSlice & UiSlice; + +export const useAppStore = create()((...args) => ({ + ...createWorksetSlice(...args), + ...createWorkspaceSlice(...args), + ...createLayoutSlice(...args), + ...createPtySlice(...args), + ...createDiffSlice(...args), + ...createUiSlice(...args), +})); diff --git a/OPUS-tauri-app/src/styles/ThemeProvider.tsx b/OPUS-tauri-app/src/styles/ThemeProvider.tsx new file mode 100644 index 00000000..8e9a4ba5 --- /dev/null +++ b/OPUS-tauri-app/src/styles/ThemeProvider.tsx @@ -0,0 +1,28 @@ +import { useEffect } from 'react'; +import { useAppStore } from '@/state/store'; +import { getThemeById, DEFAULT_THEME_ID } from './themes'; +import type { ITheme } from '@xterm/xterm'; + +export function ThemeProvider() { + const activeThemeId = useAppStore((s) => s.activeThemeId); + + useEffect(() => { + const theme = getThemeById(activeThemeId) ?? getThemeById(DEFAULT_THEME_ID)!; + const root = document.documentElement; + + root.dataset.theme = theme.id; + root.style.setProperty('color-scheme', theme.group); + + for (const [prop, value] of Object.entries(theme.tokens)) { + root.style.setProperty(prop, value); + } + }, [activeThemeId]); + + return null; +} + +export function useTerminalTheme(): ITheme { + const activeThemeId = useAppStore((s) => s.activeThemeId); + const theme = getThemeById(activeThemeId) ?? getThemeById(DEFAULT_THEME_ID)!; + return theme.terminal; +} diff --git a/OPUS-tauri-app/src/styles/fonts.css b/OPUS-tauri-app/src/styles/fonts.css new file mode 100644 index 00000000..19ba28d0 --- /dev/null +++ b/OPUS-tauri-app/src/styles/fonts.css @@ -0,0 +1,2 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap'); diff --git a/OPUS-tauri-app/src/styles/global.css b/OPUS-tauri-app/src/styles/global.css new file mode 100644 index 00000000..d241e79f --- /dev/null +++ b/OPUS-tauri-app/src/styles/global.css @@ -0,0 +1,73 @@ +@import './fonts.css'; +@import './tokens.css'; + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + height: 100%; + background: var(--bg); + color: var(--text); +} + +body { + font-size: 13px; + font-family: var(--font-body); + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +button { + font-family: inherit; + cursor: pointer; + color: inherit; +} + +button, +input, +select, +textarea { + border: none; + outline: none; + background: transparent; +} + +#root { + height: 100%; +} + +::selection { + background: rgba(129, 140, 248, 0.3); + color: #fff; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: -1px; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--surface-hover); + border-radius: 3px; +} + +* { + scrollbar-width: thin; + scrollbar-color: var(--surface-hover) transparent; +} diff --git a/OPUS-tauri-app/src/styles/themes.ts b/OPUS-tauri-app/src/styles/themes.ts new file mode 100644 index 00000000..a9e947e9 --- /dev/null +++ b/OPUS-tauri-app/src/styles/themes.ts @@ -0,0 +1,335 @@ +import type { ITheme } from '@xterm/xterm'; + +export type ThemeDefinition = { + id: string; + name: string; + group: 'dark' | 'light'; + tokens: Record; + terminal: ITheme; +}; + +// ── Dark: Default ───────────────────────────────────────────── +const dark: ThemeDefinition = { + id: 'dark', + name: 'Dark', + group: 'dark', + tokens: { + '--bg': '#0a0a0c', + '--panel': '#111113', + '--panel-strong': '#19191b', + '--panel-soft': '#0f0f11', + '--surface': '#19191b', + '--surface-hover': '#202023', + '--text': '#ececee', + '--muted': '#737380', + '--border': 'rgba(255, 255, 255, 0.07)', + '--border-active': 'rgba(255, 255, 255, 0.12)', + '--accent': '#818cf8', + '--accent-soft': 'rgba(129, 140, 248, 0.08)', + '--accent-border': 'rgba(129, 140, 248, 0.2)', + '--success': '#34d399', + '--success-soft': 'rgba(52, 211, 153, 0.08)', + '--success-border': 'rgba(52, 211, 153, 0.18)', + '--warning': '#fbbf24', + '--warning-soft': 'rgba(251, 191, 36, 0.08)', + '--warning-border': 'rgba(251, 191, 36, 0.18)', + '--danger': '#f87171', + '--danger-soft': 'rgba(248, 113, 113, 0.08)', + '--danger-border': 'rgba(248, 113, 113, 0.18)', + }, + terminal: { + background: '#0a0a0c', + foreground: '#ececee', + cursor: '#818cf8', + selectionBackground: 'rgba(129, 140, 248, 0.3)', + black: '#19191b', + brightBlack: '#737380', + red: '#f87171', + brightRed: '#fca5a5', + green: '#34d399', + brightGreen: '#6ee7b7', + yellow: '#fbbf24', + brightYellow: '#fde68a', + blue: '#818cf8', + brightBlue: '#a5b4fc', + magenta: '#c084fc', + brightMagenta: '#d8b4fe', + cyan: '#67e8f9', + brightCyan: '#a5f3fc', + white: '#ececee', + brightWhite: '#ffffff', + }, +}; + +// ── Dark: Midnight ──────────────────────────────────────────── +const midnight: ThemeDefinition = { + id: 'midnight', + name: 'Midnight', + group: 'dark', + tokens: { + '--bg': '#080b14', + '--panel': '#0d1120', + '--panel-strong': '#141929', + '--panel-soft': '#0a0e18', + '--surface': '#141929', + '--surface-hover': '#1a2035', + '--text': '#e2e8f0', + '--muted': '#64748b', + '--border': 'rgba(148, 163, 184, 0.08)', + '--border-active': 'rgba(148, 163, 184, 0.14)', + '--accent': '#60a5fa', + '--accent-soft': 'rgba(96, 165, 250, 0.08)', + '--accent-border': 'rgba(96, 165, 250, 0.2)', + '--success': '#34d399', + '--success-soft': 'rgba(52, 211, 153, 0.08)', + '--success-border': 'rgba(52, 211, 153, 0.18)', + '--warning': '#fbbf24', + '--warning-soft': 'rgba(251, 191, 36, 0.08)', + '--warning-border': 'rgba(251, 191, 36, 0.18)', + '--danger': '#f87171', + '--danger-soft': 'rgba(248, 113, 113, 0.08)', + '--danger-border': 'rgba(248, 113, 113, 0.18)', + }, + terminal: { + background: '#080b14', + foreground: '#e2e8f0', + cursor: '#60a5fa', + selectionBackground: 'rgba(96, 165, 250, 0.3)', + black: '#141929', + brightBlack: '#64748b', + red: '#f87171', + brightRed: '#fca5a5', + green: '#34d399', + brightGreen: '#6ee7b7', + yellow: '#fbbf24', + brightYellow: '#fde68a', + blue: '#60a5fa', + brightBlue: '#93c5fd', + magenta: '#c084fc', + brightMagenta: '#d8b4fe', + cyan: '#67e8f9', + brightCyan: '#a5f3fc', + white: '#e2e8f0', + brightWhite: '#ffffff', + }, +}; + +// ── Dark: Ember ─────────────────────────────────────────────── +const ember: ThemeDefinition = { + id: 'ember', + name: 'Ember', + group: 'dark', + tokens: { + '--bg': '#0e0b09', + '--panel': '#161210', + '--panel-strong': '#1e1916', + '--panel-soft': '#12100e', + '--surface': '#1e1916', + '--surface-hover': '#27211d', + '--text': '#f0ebe6', + '--muted': '#8a7e74', + '--border': 'rgba(255, 235, 210, 0.07)', + '--border-active': 'rgba(255, 235, 210, 0.12)', + '--accent': '#f59e0b', + '--accent-soft': 'rgba(245, 158, 11, 0.08)', + '--accent-border': 'rgba(245, 158, 11, 0.2)', + '--success': '#34d399', + '--success-soft': 'rgba(52, 211, 153, 0.08)', + '--success-border': 'rgba(52, 211, 153, 0.18)', + '--warning': '#fbbf24', + '--warning-soft': 'rgba(251, 191, 36, 0.08)', + '--warning-border': 'rgba(251, 191, 36, 0.18)', + '--danger': '#f87171', + '--danger-soft': 'rgba(248, 113, 113, 0.08)', + '--danger-border': 'rgba(248, 113, 113, 0.18)', + }, + terminal: { + background: '#0e0b09', + foreground: '#f0ebe6', + cursor: '#f59e0b', + selectionBackground: 'rgba(245, 158, 11, 0.3)', + black: '#1e1916', + brightBlack: '#8a7e74', + red: '#f87171', + brightRed: '#fca5a5', + green: '#34d399', + brightGreen: '#6ee7b7', + yellow: '#f59e0b', + brightYellow: '#fde68a', + blue: '#60a5fa', + brightBlue: '#93c5fd', + magenta: '#c084fc', + brightMagenta: '#d8b4fe', + cyan: '#67e8f9', + brightCyan: '#a5f3fc', + white: '#f0ebe6', + brightWhite: '#ffffff', + }, +}; + +// ── Light: Default ──────────────────────────────────────────── +const light: ThemeDefinition = { + id: 'light', + name: 'Light', + group: 'light', + tokens: { + '--bg': '#f8f8fa', + '--panel': '#ffffff', + '--panel-strong': '#f0f0f2', + '--panel-soft': '#fafafa', + '--surface': '#f0f0f2', + '--surface-hover': '#e8e8ec', + '--text': '#1a1a1e', + '--muted': '#6e6e7a', + '--border': 'rgba(0, 0, 0, 0.08)', + '--border-active': 'rgba(0, 0, 0, 0.15)', + '--accent': '#6366f1', + '--accent-soft': 'rgba(99, 102, 241, 0.08)', + '--accent-border': 'rgba(99, 102, 241, 0.2)', + '--success': '#059669', + '--success-soft': 'rgba(5, 150, 105, 0.06)', + '--success-border': 'rgba(5, 150, 105, 0.18)', + '--warning': '#d97706', + '--warning-soft': 'rgba(217, 119, 6, 0.06)', + '--warning-border': 'rgba(217, 119, 6, 0.18)', + '--danger': '#dc2626', + '--danger-soft': 'rgba(220, 38, 38, 0.06)', + '--danger-border': 'rgba(220, 38, 38, 0.18)', + }, + terminal: { + background: '#f8f8fa', + foreground: '#1a1a1e', + cursor: '#6366f1', + selectionBackground: 'rgba(99, 102, 241, 0.2)', + black: '#1a1a1e', + brightBlack: '#6e6e7a', + red: '#dc2626', + brightRed: '#ef4444', + green: '#059669', + brightGreen: '#10b981', + yellow: '#d97706', + brightYellow: '#f59e0b', + blue: '#6366f1', + brightBlue: '#818cf8', + magenta: '#9333ea', + brightMagenta: '#a855f7', + cyan: '#0891b2', + brightCyan: '#06b6d4', + white: '#e8e8ec', + brightWhite: '#ffffff', + }, +}; + +// ── Light: Dawn ─────────────────────────────────────────────── +const dawn: ThemeDefinition = { + id: 'dawn', + name: 'Dawn', + group: 'light', + tokens: { + '--bg': '#faf8f5', + '--panel': '#fffefa', + '--panel-strong': '#f2efe9', + '--panel-soft': '#fdfbf8', + '--surface': '#f2efe9', + '--surface-hover': '#e9e5de', + '--text': '#1c1917', + '--muted': '#78716c', + '--border': 'rgba(0, 0, 0, 0.07)', + '--border-active': 'rgba(0, 0, 0, 0.13)', + '--accent': '#7c3aed', + '--accent-soft': 'rgba(124, 58, 237, 0.06)', + '--accent-border': 'rgba(124, 58, 237, 0.18)', + '--success': '#059669', + '--success-soft': 'rgba(5, 150, 105, 0.06)', + '--success-border': 'rgba(5, 150, 105, 0.18)', + '--warning': '#d97706', + '--warning-soft': 'rgba(217, 119, 6, 0.06)', + '--warning-border': 'rgba(217, 119, 6, 0.18)', + '--danger': '#dc2626', + '--danger-soft': 'rgba(220, 38, 38, 0.06)', + '--danger-border': 'rgba(220, 38, 38, 0.18)', + }, + terminal: { + background: '#faf8f5', + foreground: '#1c1917', + cursor: '#7c3aed', + selectionBackground: 'rgba(124, 58, 237, 0.2)', + black: '#1c1917', + brightBlack: '#78716c', + red: '#dc2626', + brightRed: '#ef4444', + green: '#059669', + brightGreen: '#10b981', + yellow: '#d97706', + brightYellow: '#f59e0b', + blue: '#7c3aed', + brightBlue: '#8b5cf6', + magenta: '#c026d3', + brightMagenta: '#d946ef', + cyan: '#0891b2', + brightCyan: '#06b6d4', + white: '#e9e5de', + brightWhite: '#ffffff', + }, +}; + +// ── Light: Paper ────────────────────────────────────────────── +const paper: ThemeDefinition = { + id: 'paper', + name: 'Paper', + group: 'light', + tokens: { + '--bg': '#f5f5f5', + '--panel': '#fafafa', + '--panel-strong': '#ebebeb', + '--panel-soft': '#f7f7f7', + '--surface': '#ebebeb', + '--surface-hover': '#e0e0e0', + '--text': '#171717', + '--muted': '#636363', + '--border': 'rgba(0, 0, 0, 0.09)', + '--border-active': 'rgba(0, 0, 0, 0.16)', + '--accent': '#0d9488', + '--accent-soft': 'rgba(13, 148, 136, 0.06)', + '--accent-border': 'rgba(13, 148, 136, 0.18)', + '--success': '#059669', + '--success-soft': 'rgba(5, 150, 105, 0.06)', + '--success-border': 'rgba(5, 150, 105, 0.18)', + '--warning': '#d97706', + '--warning-soft': 'rgba(217, 119, 6, 0.06)', + '--warning-border': 'rgba(217, 119, 6, 0.18)', + '--danger': '#dc2626', + '--danger-soft': 'rgba(220, 38, 38, 0.06)', + '--danger-border': 'rgba(220, 38, 38, 0.18)', + }, + terminal: { + background: '#f5f5f5', + foreground: '#171717', + cursor: '#0d9488', + selectionBackground: 'rgba(13, 148, 136, 0.2)', + black: '#171717', + brightBlack: '#636363', + red: '#dc2626', + brightRed: '#ef4444', + green: '#059669', + brightGreen: '#10b981', + yellow: '#d97706', + brightYellow: '#f59e0b', + blue: '#2563eb', + brightBlue: '#3b82f6', + magenta: '#9333ea', + brightMagenta: '#a855f7', + cyan: '#0d9488', + brightCyan: '#14b8a6', + white: '#e0e0e0', + brightWhite: '#ffffff', + }, +}; + +export const themes: ThemeDefinition[] = [dark, midnight, ember, light, dawn, paper]; + +export const DEFAULT_THEME_ID = 'dark'; + +export function getThemeById(id: string): ThemeDefinition | undefined { + return themes.find((t) => t.id === id); +} diff --git a/OPUS-tauri-app/src/styles/tokens.css b/OPUS-tauri-app/src/styles/tokens.css new file mode 100644 index 00000000..bfc063a2 --- /dev/null +++ b/OPUS-tauri-app/src/styles/tokens.css @@ -0,0 +1,57 @@ +:root { + color-scheme: dark; + + --font-body: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace; + + --bg: #0a0a0c; + --panel: #111113; + --panel-strong: #19191b; + --panel-soft: #0f0f11; + --surface: #19191b; + --surface-hover: #202023; + --text: #ececee; + --muted: #737380; + + --border: rgba(255, 255, 255, 0.07); + --border-active: rgba(255, 255, 255, 0.12); + + --accent: #818cf8; + --accent-soft: rgba(129, 140, 248, 0.08); + --accent-border: rgba(129, 140, 248, 0.2); + + --success: #34d399; + --success-soft: rgba(52, 211, 153, 0.08); + --success-border: rgba(52, 211, 153, 0.18); + + --warning: #fbbf24; + --warning-soft: rgba(251, 191, 36, 0.08); + --warning-border: rgba(251, 191, 36, 0.18); + + --danger: #f87171; + --danger-soft: rgba(248, 113, 113, 0.08); + --danger-border: rgba(248, 113, 113, 0.18); + + --radius-sm: 2px; + --radius-md: 3px; + --radius-lg: 0px; + + --transition-fast: 120ms ease; + --transition-normal: 160ms ease; + + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + + --shadow-sm: none; + --shadow-md: none; + --shadow-lg: none; + + --rail-width: 52px; + --sidebar-width: 250px; + --right-panel-width: 320px; + --chrome-height: 38px; +} diff --git a/OPUS-tauri-app/src/types/context.ts b/OPUS-tauri-app/src/types/context.ts new file mode 100644 index 00000000..d326808e --- /dev/null +++ b/OPUS-tauri-app/src/types/context.ts @@ -0,0 +1,4 @@ +export type ActiveContext = { + active_workset_id?: string; + active_workspace?: string; +}; diff --git a/OPUS-tauri-app/src/types/diagnostics.ts b/OPUS-tauri-app/src/types/diagnostics.ts new file mode 100644 index 00000000..cda39823 --- /dev/null +++ b/OPUS-tauri-app/src/types/diagnostics.ts @@ -0,0 +1,25 @@ +export type EnvSnapshot = { + path: string; + shell: string; + home: string; + ssh_auth_sock?: string; + git_ssh_command?: string; + git_askpass?: string; + gh_config_dir?: string; + gh_auth_summary?: string; +}; + +export type SessiondStatus = { + running: boolean; + version?: string; + socket_path?: string; + last_error?: string; + last_restart?: string; +}; + +export type CliStatus = { + available: boolean; + path?: string; + version?: string; + error?: string; +}; diff --git a/OPUS-tauri-app/src/types/diff.ts b/OPUS-tauri-app/src/types/diff.ts new file mode 100644 index 00000000..e7b1efba --- /dev/null +++ b/OPUS-tauri-app/src/types/diff.ts @@ -0,0 +1,22 @@ +export type DiffSummary = { + files: DiffFileSummary[]; + total_added: number; + total_removed: number; +}; + +export type DiffFileSummary = { + path: string; + prev_path?: string; + added: number; + removed: number; + status: 'M' | 'A' | 'D' | 'R'; + binary?: boolean; +}; + +export type FilePatch = { + patch: string; + truncated: boolean; + total_bytes: number; + total_lines: number; + binary?: boolean; +}; diff --git a/OPUS-tauri-app/src/types/errors.ts b/OPUS-tauri-app/src/types/errors.ts new file mode 100644 index 00000000..b76e4987 --- /dev/null +++ b/OPUS-tauri-app/src/types/errors.ts @@ -0,0 +1,10 @@ +export type ErrorCategory = 'auth' | 'network' | 'git' | 'config' | 'runtime' | 'unknown'; + +export type ErrorEnvelope = { + category: ErrorCategory; + operation: string; + message: string; + details?: string; + retryable: boolean; + suggested_actions: string[]; +}; diff --git a/OPUS-tauri-app/src/types/github.ts b/OPUS-tauri-app/src/types/github.ts new file mode 100644 index 00000000..b0462a8f --- /dev/null +++ b/OPUS-tauri-app/src/types/github.ts @@ -0,0 +1,16 @@ +export type GitHubRepo = { + full_name: string; + description: string | null; + private: boolean; +}; + +export type GitHubAuthStatus = { + available: boolean; + authenticated: boolean; + message: string; +}; + +export type GitHubAccount = { + login: string; + active: boolean; +}; diff --git a/OPUS-tauri-app/src/types/index.ts b/OPUS-tauri-app/src/types/index.ts new file mode 100644 index 00000000..8faf3dc9 --- /dev/null +++ b/OPUS-tauri-app/src/types/index.ts @@ -0,0 +1,11 @@ +export * from './errors'; +export * from './context'; +export * from './workset'; +export * from './workspace'; +export * from './diff'; +export * from './pty'; +export * from './jobs'; +export * from './layout'; +export * from './diagnostics'; +export * from './github'; +export * from './repo'; diff --git a/OPUS-tauri-app/src/types/jobs.ts b/OPUS-tauri-app/src/types/jobs.ts new file mode 100644 index 00000000..bc240c6b --- /dev/null +++ b/OPUS-tauri-app/src/types/jobs.ts @@ -0,0 +1,15 @@ +import type { ErrorEnvelope } from './errors'; + +export type MigrationJobRef = { job_id: string }; + +export type MigrationProgress = { + job_id: string; + state: 'queued' | 'running' | 'done' | 'failed' | 'canceled'; + workspaces: WorkspaceMigrationStatus[]; +}; + +export type WorkspaceMigrationStatus = { + workspace_name: string; + state: 'pending' | 'running' | 'success' | 'failed'; + error?: ErrorEnvelope; +}; diff --git a/OPUS-tauri-app/src/types/layout.ts b/OPUS-tauri-app/src/types/layout.ts new file mode 100644 index 00000000..46d5738f --- /dev/null +++ b/OPUS-tauri-app/src/types/layout.ts @@ -0,0 +1,35 @@ +export type TerminalLayout = { + version: number; + root: LayoutNode; + focused_pane_id?: string; +}; + +export type LayoutNode = PaneNode | SplitNode; + +export type PaneNode = { + kind: 'pane'; + id: string; + tabs: LayoutTab[]; + active_tab_id?: string; +}; + +export type SplitNode = { + kind: 'split'; + id: string; + direction: 'row' | 'column'; + ratio: number; + first: LayoutNode; + second: LayoutNode; +}; + +export type LayoutTab = { + id: string; + terminal_id: string; + title: string; + kind: 'agent' | 'terminal' | 'diff'; + diff_repo?: string; + diff_repo_path?: string; + diff_file_path?: string; + diff_prev_path?: string; + diff_status?: string; +}; diff --git a/OPUS-tauri-app/src/types/pty.ts b/OPUS-tauri-app/src/types/pty.ts new file mode 100644 index 00000000..3ab6aff4 --- /dev/null +++ b/OPUS-tauri-app/src/types/pty.ts @@ -0,0 +1,15 @@ +export type PtyCreateResult = { terminal_id: string }; + +export type BootstrapPayload = { + workspace_name: string; + terminal_id: string; + snapshot?: string; + backlog?: string; + backlog_truncated?: boolean; + next_offset?: number; + alt_screen?: boolean; + mouse?: boolean; + mouse_sgr?: boolean; + safe_to_replay?: boolean; + initial_credit?: number; +}; diff --git a/OPUS-tauri-app/src/types/repo.ts b/OPUS-tauri-app/src/types/repo.ts new file mode 100644 index 00000000..d049c746 --- /dev/null +++ b/OPUS-tauri-app/src/types/repo.ts @@ -0,0 +1,8 @@ +export type RepoInstance = { + name: string; + worktree_path: string; + repo_dir: string; + missing: boolean; + default_branch?: string; + default_remote?: string; +}; diff --git a/OPUS-tauri-app/src/types/workset.ts b/OPUS-tauri-app/src/types/workset.ts new file mode 100644 index 00000000..675516c0 --- /dev/null +++ b/OPUS-tauri-app/src/types/workset.ts @@ -0,0 +1,15 @@ +export type WorksetProfile = { + id: string; + name: string; + repos: string[]; + workspace_ids: string[]; + defaults?: WorksetDefaults; + created_at: string; + updated_at: string; +}; + +export type WorksetDefaults = { + base_branch?: string; + default_remote?: string; + workspace_root?: string; +}; diff --git a/OPUS-tauri-app/src/types/workspace.ts b/OPUS-tauri-app/src/types/workspace.ts new file mode 100644 index 00000000..79dc6354 --- /dev/null +++ b/OPUS-tauri-app/src/types/workspace.ts @@ -0,0 +1,29 @@ +import type { ErrorEnvelope } from './errors'; + +export type WorkspaceSummary = { + name: string; + path: string; + created_at?: string; + last_used?: string; + archived: boolean; + pinned: boolean; + pin_order: number; + expanded: boolean; +}; + +export type WorkspaceCreateJobRef = { job_id: string }; + +export type WorkspaceCreateProgress = { + job_id: string; + state: 'queued' | 'running' | 'succeeded' | 'partial' | 'failed'; + repos: RepoProvisionStatus[]; + workspace_name?: string; + workspace_path?: string; +}; + +export type RepoProvisionStatus = { + name: string; + state: 'pending' | 'running' | 'succeeded' | 'failed'; + step?: 'preflight' | 'fetch/clone' | 'worktree/checkout' | 'verify'; + error?: ErrorEnvelope; +}; diff --git a/OPUS-tauri-app/src/vite-env.d.ts b/OPUS-tauri-app/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/OPUS-tauri-app/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/OPUS-tauri-app/tsconfig.json b/OPUS-tauri-app/tsconfig.json new file mode 100644 index 00000000..5b7e0848 --- /dev/null +++ b/OPUS-tauri-app/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2021", + "useDefineForClassFields": true, + "lib": ["ES2021", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/OPUS-tauri-app/tsconfig.tsbuildinfo b/OPUS-tauri-app/tsconfig.tsbuildinfo new file mode 100644 index 00000000..803539aa --- /dev/null +++ b/OPUS-tauri-app/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/context.ts","./src/api/diagnostics.ts","./src/api/diff.ts","./src/api/events.ts","./src/api/github.ts","./src/api/invoke.ts","./src/api/migrations.ts","./src/api/pty.ts","./src/api/repos.ts","./src/api/worksets.ts","./src/api/workspaces.ts","./src/commands/appcommands.ts","./src/commands/fuzzymatch.ts","./src/commands/layoututils.ts","./src/commands/registry.ts","./src/components/diff/difffilerow.tsx","./src/components/diff/diffnavigator.tsx","./src/components/diff/diffrenderer.tsx","./src/components/diff/diffrepogroup.tsx","./src/components/diff/difftabview.tsx","./src/components/layout/appshell.tsx","./src/components/layout/iconrail.tsx","./src/components/layout/rightpanel.tsx","./src/components/layout/secondarysidebar.tsx","./src/components/layout/topchrome.tsx","./src/components/modals/commandpalette.tsx","./src/components/modals/migrationstatusmodal.tsx","./src/components/modals/reporemoveconfirmmodal.tsx","./src/components/modals/worksetcreatemodal.tsx","./src/components/pages/commandcenter/commandcenterpage.tsx","./src/components/pages/settings/appearancesection.tsx","./src/components/pages/settings/settingspage.tsx","./src/components/pages/spaces/createtabmodal.tsx","./src/components/pages/spaces/createworkspacemodal.tsx","./src/components/pages/spaces/spacespage.tsx","./src/components/pages/spaces/tabcontent.tsx","./src/components/pages/spaces/tabstrip.tsx","./src/components/pages/spaces/terminallayoutnode.tsx","./src/components/pages/spaces/terminalsurface.tsx","./src/components/pages/spaces/workspaceitem.tsx","./src/components/pages/spaces/workspacelist.tsx","./src/components/ui/button.tsx","./src/components/ui/emptystate.tsx","./src/components/ui/input.tsx","./src/components/ui/statusrow.tsx","./src/hooks/useglobalshortcuts.ts","./src/hooks/usetaurievent.ts","./src/state/store.ts","./src/state/slices/diffslice.ts","./src/state/slices/layoutslice.ts","./src/state/slices/ptyslice.ts","./src/state/slices/uislice.ts","./src/state/slices/worksetslice.ts","./src/state/slices/workspaceslice.ts","./src/styles/themeprovider.tsx","./src/styles/themes.ts","./src/types/context.ts","./src/types/diagnostics.ts","./src/types/diff.ts","./src/types/errors.ts","./src/types/github.ts","./src/types/index.ts","./src/types/jobs.ts","./src/types/layout.ts","./src/types/pty.ts","./src/types/repo.ts","./src/types/workset.ts","./src/types/workspace.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/OPUS-tauri-app/vite.config.ts b/OPUS-tauri-app/vite.config.ts new file mode 100644 index 00000000..86c99cd3 --- /dev/null +++ b/OPUS-tauri-app/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +const host = process.env.TAURI_DEV_HOST; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + clearScreen: false, + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host ? { protocol: 'ws', host, port: 1421 } : undefined, + watch: { ignored: ['**/src-tauri/**'] }, + }, +}); diff --git a/docs/tauri-product-spec.md b/docs/tauri-product-spec.md new file mode 100644 index 00000000..abfc7093 --- /dev/null +++ b/docs/tauri-product-spec.md @@ -0,0 +1,954 @@ +# Workset Desktop (Tauri) Product Specification (Working) + +This is a working product specification. We are drafting it one section at a time. + +--- + +## Section 1: Product Mental Model (What This App Is) + +### The problem we’re solving +Workset is a desktop app for doing one piece of work across many repositories without losing track of what belongs together. The core pain is that a “feature” or “thread” of work almost never lives in one repo, but git and most UIs force you to think repo-by-repo. + +This product treats a multi-repo feature as a first-class thing you can create, switch, and review as a single unit. + +### The three key concepts (and only these are “real”) + +#### 1) Workset (app concept, not a CLI capability) +A **Workset** is a saved configuration: “these are the repos that belong together for this product/project.” + +- It is durable and user-named. +- It contains a curated list of repos (and optional defaults). +- It is not a work thread by itself. +- Creating a Workset is an app responsibility (UI + persistence). The CLI does not “create worksets”. + +You can think of Workset as: a profile / recipe / config bundle. + +#### 2) Workspace (CLI-backed concept: the work thread) +A **Workspace** is a named work thread inside a Workset: “feature/search-ranking”, “bugfix/login-timeout”, etc. + +- A Workspace is what you create when you want to start a new multi-repo thread. +- A Workspace is always scoped to exactly one Workset (because it uses that Workset’s repo set). +- A Workspace has one name, and that name is used to coordinate work across repos. + +You can think of Workspace as: a feature branch identity applied to the whole repo set. + +#### 3) Branch (git concept, per repo) +A **branch** is still a per-repository git branch. The product goal is to make the user not have to manage these manually per repo. + +### The key semantic: what “Create Workspace” actually does +When the user creates a Workspace named `X` under the active Workset, the system provisions the Workset’s repositories so that: + +- Each repo ends up on a branch named `X` (or whatever exact naming/mapping the CLI defines). +- The user now has a coherent cross-repo “thread” they can switch to later by selecting Workspace `X`. +- The “unit of switching” in the UI is Workspace, not repository. + +If one repo fails to provision, the Workspace creation is not “done”; it is a partial failure state with a repair path. + +### Global selection rules (the invariants) +- There is exactly one active Workset at a time (selected in the top chrome). +- Everything the user sees is scoped to that Workset (repos, workspaces, diffs, agents). +- There is exactly one active Workspace at a time within that Workset. +- Workspace selection is not a second top-chrome selector; it is controlled within the Workset-scoped UI. + +### What the UI must never do +- Show Workspaces from multiple Worksets at the same time. +- Treat “branch” and “workspace” as separate user-facing primary concepts. Branches are an implementation detail unless the user explicitly drills into repo details. +- Implicitly change the active Workset as a side effect of creating/selecting a Workspace. + +### Role of the CLI (what the app delegates) +- The CLI defines and performs the actual multi-repo git operations for Workspace lifecycle (create/switch/provision/status). +- The app wraps those operations with: + - UX (progress, per-repo status, retries) + - persistence of Workset config + - safe error reporting and recovery + - higher-level views like “what changed in this Workspace across repos” + +--- + +## Section 2 (Revised): Information Architecture (Native Agent Tabs + No Review Page) + +This section removes the “Review” page and replaces it with an in-Space diff + diff-as-tab model, and clarifies that Spaces main content is primarily the terminal/agent surface. + +### 2.1 Global chrome (top bar) +Top chrome is reserved for global context only. + +Top chrome contains: +- **Workset selector** (the only persistent selector) + - pick/switch Workset + - “Create Workset…” (app-level) + - “Manage Worksets…” (rename/delete/export/import) +- Lightweight global items (optional): + - search/command palette + - app settings shortcut + - connection/health indicator + +Top chrome explicitly does **not** contain: +- a Workspace selector +- repo selection controls +- branch controls +- diff controls + +### 2.2 Primary navigation (left sidebar) +Primary nav is stable and Workset-scoped. + +Primary nav items: +1. **Command Center** +2. **Spaces** +3. **Settings** + +Removed: +- **Review** (no separate page) + +Rationale: review/diff is an always-available right panel capability of the active Space, and detailed diffs become tabs in the main terminal area. + +### 2.3 Page layout model +All pages share: + +`[Primary Sidebar] [Secondary Sidebar] [Main Content] [Right Panel]` + +For **Spaces** specifically: +- **Main Content** = the tabbed terminal/agent environment (like iTerm) +- **Right Panel** = lightweight diff summary + file list (not a full review app) + +### 2.4 Command Center (Workset config + health) +Command Center is for Workset configuration and diagnostics, not daily work. + +Secondary sidebar: +- Overview +- Repositories (add/remove repos in this Workset config) +- Diagnostics (CLI/env/auth checks, logs, debug bundle) + +Main content: +- Workset overview cards (repo count, workspace count, warnings) +- Primary CTAs: “Add repositories”, “Go to Spaces”, “Create workspace” + +Right panel (optional): +- Recent operations / failures / retry shortcuts + +### 2.5 Spaces (the daily driver) +Spaces is where you create/select the active Workspace and then live inside it (agents + terminals + diffs). + +#### 2.5.1 Secondary Sidebar (Spaces): Workspace list + lifecycle +- Header: “Workspaces” +- Action: “New Workspace” +- List: Workspaces (scoped to active Workset) + - each shows: name, status (Ready / Partial / Error), last used + - selecting sets active workspace + +This is the canonical place to switch work threads. + +#### 2.5.2 Main Content (Spaces): Tabbed “iTerm-like” surface +Main content is a tab strip plus active tab content. + +Tab types (minimum): +- **Agent tab**: a native coding agent session attached to this Workspace context +- **Terminal tab**: plain shell terminal (tests/builds/manual git, etc.) +- **Diff tab**: full diff viewer for a selected file (opened from right panel) + +Tab strip behavior: +- Create tab buttons: “New Agent”, “New Terminal” +- Tabs are per-workspace (when you switch workspace, you see that workspace’s tabs) +- Tabs can be renamed (“agent-1”, “tests”, “diff: server/api.go”, etc.) +- Tabs can be closed; closing should stop/cleanup that underlying session (with confirm if running) + +#### 2.5.3 Right Panel (Spaces): Lightweight diff summary +Right panel is always visible while in Spaces (unless user collapses it). + +It shows a compact, scannable summary: +- Grouped by repo: + - repo name + - changed files under it + - each file shows `+adds/-deletes` (green/red) and status (M/A/D) +- Optional top summary: total files changed, total `+/-` + +Interaction: +- Clicking a file opens a Diff tab in main content (does not navigate away) + +Constraints: +- This right panel is not a PR tool. +- It is meant to keep you oriented (“what changed in this workspace?”) and to open diffs quickly as tabs. + +### 2.6 Empty states + failure states (updated) +1) No Worksets: +- onboarding: create first Workset (top chrome flow) + +2) Workset has no repos: +- Command Center pushes “Add repositories” + +3) Workset has repos but no workspaces: +- Spaces shows “Create your first workspace” CTA + +4) Workspace exists but provisioning is partial: +- Workspace list marks it “Needs repair” +- Right panel shows only available diff data (or a clear “diff unavailable until provisioned” message) +- Main content shows repair CTA and per-repo failure details (in a “Workspace status” tab or a panel) + +--- + +## Section 3: Runtime Model (Tabs, Processes, and State Ownership) + +This section is grounded in what the existing Wails app already proves works well: a dedicated PTY daemon (`workset-sessiond`), per-workspace persisted terminal layout (tabs + splits), and a background diff watcher that keeps a repo-grouped `+/-` file list fresh in the right panel. + +### 3.1 The core runtime principle +In **Spaces**, the main content is a terminal surface that can host: +- interactive agent sessions (Claude Code running in a terminal) +- plain terminals (tests/builds/manual commands) +- full file diffs (opened as tabs) + +Runtime is “many terminal-like sessions + lightweight diff data,” all scoped to the active Workspace. + +### 3.2 Reuse the proven PTY architecture (sessiond) +Do not implement PTYs ad-hoc in the UI layer. The Wails app uses a daemon (`workset-sessiond`) that provides: +- attach/stream semantics (session stays alive even if UI detaches) +- backlog + snapshot (so the UI can recover after refresh/restart) +- backpressure/credit accounting (UI ACKs bytes to prevent runaway memory) +- terminal mode signals (alt-screen, mouse tracking) +- optional Kitty graphics event stream + +Spec requirements (Tauri): +- The Tauri backend must manage terminal/agent sessions via `workset-sessiond` (or an equivalent daemon with the same features). +- A “Terminal tab” and an “Agent tab” are the same underlying thing: a PTY session with a different startup command. + +### 3.3 What a “tab” is (concrete definition) +A tab is a view over a single underlying runtime session or a diff artifact. + +Tab types: +1. **Agent tab** + - Backed by a sessiond PTY + - Starts by launching the agent command (Claude Code) inside the workspace root +2. **Terminal tab** + - Backed by a sessiond PTY + - Starts a shell (or configured command) inside the workspace root +3. **Diff tab** + - Not a PTY + - Displays a patch for one file (or optionally a repo-level patch) + - Opened from the right-panel diff navigator + +Non-negotiable UX behavior: +- Tabs are per-workspace. Switching workspace swaps the whole tab set. +- Closing a PTY-backed tab stops that underlying session (with confirmation if needed). +- Diff tabs are disposable and should restore quickly from cached patch data (or refetch). + +### 3.4 Terminal layout model (tabs + splits) and persistence +The Wails app uses a layout tree with: +- panes containing tabs +- split nodes (row/column) with ratios +- active tab per pane +- focused pane + +Spec requirements (Tauri): +- Persist a layout per workspace context (keyed by workspace path, not just a name). +- On app launch: + - load the saved layouts + - opportunistically restore PTY sessions referenced by the layout (start/attach) + - show tabs as “reconnected” vs “fresh” if that concept exists + +### 3.5 Workspace root and repo worktrees (no guessing) +The Wails app resolves: +- workspace root path from the underlying service (workspace list includes `Path`) +- repo worktree paths as `workspacePath + repo.RepoDir` (with fallback to a local path) + +Spec requirements (Tauri): +- The backend must obtain the workspace root path and repo worktree paths from the same source of truth the CLI uses (service/CLI output). +- Terminals and agents must start with `cwd = workspace root path`. +- Repo diffs must be computed against the repo worktree path. + +### 3.6 Right-panel diffs: simple, always-on, event-updated +The right panel is a compact diff navigator: +- grouped by repo +- list of files with `+added/-removed` +- clicking a file opens a Diff tab + +Spec requirements (Tauri): +- Maintain a background “diff watcher” per (workspace, repo) while that workspace is active. +- The watcher emits updates that drive the right panel (so it’s “live” without manual refresh). +- The right panel is not a PR tool; it’s navigation and awareness. + +### 3.7 Diff tabs: file-level patch as the primitive +The existing backend provides: +- repo diff summary (files + totals) +- file diff patch (tracked + untracked) + +Spec requirements (Tauri): +- Clicking a file opens a Diff tab for that exact file entry (path + status + prevPath if rename). +- The Diff tab fetches a patch using the same approach: + - tracked: `git diff` staged + unstaged for that file + - untracked: `--no-index` diff against empty baseline +- Optional: “next/prev changed file” navigation driven by the current right-panel list. + +### 3.8 Auth/runtime environment is a first-class dependency +Spec requirements (Tauri): +- Provide an explicit “Reload login environment” capability. +- All spawned PTY sessions must inherit: + - PATH resolution consistent with login shell + - SSH agent socket (for git SSH) + - `gh` auth context (if used) + - git credential helper behavior + +### 3.9 Failure handling: make it boring and recoverable +Minimum recovery requirements: +- If sessiond is down: show clear status + “restart sessiond”; terminals show “disconnected” not “loading forever”. +- If a repo is missing/unavailable: right panel shows “diff unavailable” with reason. +- If workspace provisioning is partial: Spaces shows “needs repair” and per-repo remediation. + +--- + +## Section 4: Backend Contract + Persistence (Tauri Commands, Events, and Local Storage) + +This section defines the interface between the Tauri frontend and backend, plus what is persisted locally vs derived from Workset (CLI/service) state. The goal is to make Sections 1–3 implementable without ambiguity. + +### 4.1 Design goals for the contract +- Deterministic UI state transitions (no infinite loading). +- Long-running operations report progress (workspace create, repo membership changes). +- Streaming IO for PTY sessions (agent/terminal tabs) with backpressure and replay. +- Background diff updates for the right panel (watcher/event model). +- Structured errors with remediation hints. + +The frontend must not parse raw CLI output strings; it calls typed backend commands and consumes typed payloads/events. + +### 4.2 Sources of truth +There are four “engines” in the product: + +1) App-local Workset profile store (app-owned) +- Workset profiles (name + repo sources + defaults). +- Active selection state. +- UI layout/tab persistence keyed by workspace path. + +2) Workset workspace state (CLI-owned) +- A workspace is a directory with `/workset.yaml` and `/.workset/`. +- Repo membership in a workspace is modified by `workset repo add/rm`. +- Worktrees live under `/` by default. + +3) `workset-sessiond` (PTY daemon, runtime-owned) +- Terminal/agent sessions: attach/stream/bootstrap/ack, snapshot/backlog, modes. + +4) Derived git data (computed against repo worktrees) +- Diff summaries and file patches are computed against repo worktree paths for the active workspace. + +### 4.3 Local persistence (what the app stores) +The app persists: + +1) Workset profiles (app concept) +- `id` (uuid) +- `name` +- `repos[]` (repo sources as entered, plus display metadata) +- optional defaults (remote/base branch, workspace root, etc.) +- timestamps + +2) UI context +- active workset id +- last active workspace per workset +- panel collapsed state, etc. + +3) Workspace-scoped layout state (keyed by workspace path) +- split/pane layout tree + tab list + active tab id + focused pane id +- tab titles + +4) Workset membership migration history (recommended) +- records of “repo added/removed” events and per-workspace results (success/failure + error envelope) + +### 4.4 Non-negotiable behavior: Workset membership converges all workspaces +You decided: +- Adding a repo to a Workset profile applies to all existing workspaces under that workset immediately. +- Removing a repo from a Workset profile applies to all existing workspaces under that workset immediately. + +This means repo membership changes are not passive config edits; they start a long-running “migration job”: + +- Add repo: run `workset repo add -w ...` for each workspace. +- Remove repo: run `workset repo rm -w ...` for each workspace. + +There is no stable “Not provisioned” state. Instead, per workspace: +- `updating` (pending/running) +- `up_to_date` (succeeded) +- `update_failed` (failed; repairable with retry) + +### 4.5 Tauri backend command surface (proposed) +The contract is grouped by capability. Command names are placeholders; the important part is payload shape and event semantics. + +#### Group 1: Workset profiles (app-owned) +- `worksets.list() -> WorksetProfile[]` +- `worksets.create({ name, defaults? }) -> WorksetProfile` +- `worksets.update({ id, name?, defaults? }) -> WorksetProfile` +- `worksets.delete({ id }) -> void` + +Repo membership changes: +- `worksets.repos.add({ workset_id, source, display_name? }) -> { workset: WorksetProfile, job: MigrationJobRef }` +- `worksets.repos.remove({ workset_id, repo_id, remove_options }) -> { workset: WorksetProfile, job: MigrationJobRef }` + +`remove_options` must include: +- `delete_worktrees: boolean` (default true) +- `delete_local: boolean` (default false; only if the repo is managed) + +#### Group 2: Active context +- `context.get() -> { active_workset_id?: string, active_workspace?: string }` +- `context.set_active_workset({ workset_id }) -> void` +- `context.set_active_workspace({ workspace_name }) -> void` + +#### Group 3: Workspaces (CLI-backed) +- `workspaces.list({ workset_id }) -> WorkspaceSummary[]` +- `workspaces.create({ workset_id, name, path? }) -> WorkspaceCreateJobRef` +- `workspaces.create.status({ job_id }) -> WorkspaceCreateProgress` +- `workspaces.delete({ workset_id, workspace_name, delete?: boolean }) -> void` (optional) + +Workspace creation should internally use `workset new` and then converge workspace repos to the workset profile. + +#### Group 4: Migration jobs (repo membership apply-to-all) +- `migration.status({ job_id }) -> MigrationProgress` +- `migration.cancel({ job_id }) -> void` +- `migration.retry_failed({ job_id }) -> MigrationJobRef` +- `migration.retry_workspace({ job_id, workspace_name }) -> MigrationJobRef` + +Migration progress must include: +- job state: `queued|running|done|failed|canceled` +- per-workspace state: `pending|running|success|failed` + error envelope + +#### Group 5: Workspace repo resolution (CLI/service-backed) +- `workspace.repos.list({ workspace_name }) -> RepoInstance[]` + +RepoInstance must include: +- repo identity (name or stable id) +- `worktree_path` +- `repo_dir` +- missing/statusKnown flags +- default branch/remote if available + +#### Group 6: Diff summary + file patch +- `diff.summary({ workspace_name, repo }) -> DiffSummary` +- `diff.file_patch({ workspace_name, repo, path, prev_path?, status }) -> FilePatch` + +#### Group 7: Diff watcher (background) +- `diff.watch.start({ workspace_name, repo }) -> void` +- `diff.watch.stop({ workspace_name, repo }) -> void` + +Events: +- `diff:summary` `{ workspace_name, repo, summary }` +- `diff:status` `{ workspace_name, repo, statusKnown, missing, dirty }` + +#### Group 8: PTY sessions (sessiond-backed) +- `pty.create({ workspace_name }) -> { terminal_id }` +- `pty.start({ workspace_name, terminal_id, kind, command? }) -> void` where `kind` is `terminal|agent` +- `pty.write({ workspace_name, terminal_id, data }) -> void` +- `pty.resize({ workspace_name, terminal_id, cols, rows }) -> void` +- `pty.ack({ workspace_name, terminal_id, bytes }) -> void` +- `pty.bootstrap({ workspace_name, terminal_id }) -> BootstrapPayload` +- `pty.stop({ workspace_name, terminal_id }) -> void` + +Events: +- `pty:data` +- `pty:bootstrap` +- `pty:bootstrap_done` +- `pty:modes` +- `pty:lifecycle` +- `pty:kitty` (optional) + +#### Group 9: Diagnostics +- `diagnostics.env_snapshot() -> EnvSnapshot` +- `diagnostics.reload_login_env() -> EnvSnapshot` +- `diagnostics.sessiond.status() -> SessiondStatus` +- `diagnostics.sessiond.restart({ reason }) -> SessiondStatus` + +### 4.6 Error envelope (required) +Every failed operation must include: +- `category`: `auth | network | git | config | runtime | unknown` +- `operation` +- `message` +- `details` +- `retryable` +- `suggested_actions[]` (optional) + +--- + +## Section 5: Workspace Provisioning UX + Progress Model (Create, Partial Failure, Repair) + +This section defines how “Create Workspace” works in Spaces, and how we handle partial failures. It also defines the repo membership migration model (apply-to-all) for add/remove of repos in a Workset profile. + +### 5.1 User intent +When a user creates a workspace named `X` under the active workset, they expect: +- a coherent multi-repo thread named `X` +- all repos in the workset profile are present in that workspace directory and usable +- they can immediately open agent/terminal tabs and start work + +### 5.2 Where it lives +Spaces is the canonical home for: +- create workspace +- list/select workspace +- see provisioning/migration status +- repair failures (retry) + +### 5.3 Create Workspace modal +Inputs: +- workspace name (required) +- optional base branch override (only if supported) + +Preview: +- list of repos in the workset profile that will be included + +Primary action: +- Create + +### 5.4 Workspace creation is a job +Workspace creation must be non-blocking and progress-reporting. + +Job states: +- `queued -> running -> succeeded | partial | failed` + +Per-repo states: +- `pending | running | succeeded | failed` + +Per-repo step labels (UI-facing): +- `preflight` +- `fetch/clone` +- `worktree/checkout` +- `verify` + +### 5.5 When the new workspace appears +Immediately after job start: +- it appears in the workspace list as `Provisioning` +- it can be selected (UI shows provisioning state) + +### 5.6 Tabs availability during provisioning +Default rule: +- allow agent/terminal tabs once the workspace path exists and at least one repo worktree is available +- otherwise show disabled state with a clear reason + +### 5.7 Completion semantics +Outcomes: +- `Ready`: all repos succeeded +- `Needs repair`: at least one repo failed (workspace is partial) +- `Failed`: provisioning did not establish a usable workspace context + +### 5.8 Repair model +Repair is a first-class action: +- Retry failed repos (bulk) +- Retry a specific repo +- View details (error envelope + logs) +- Open diagnostics (auth/runtime) + +Repair must not require deleting/recreating a workspace. + +### 5.9 Workset repo membership changes are immediate migrations (apply-to-all) +When a repo is added/removed from the active workset profile, the app must converge all existing workspaces under that workset. + +Add repo: +- start a migration job that runs `workset repo add -w ...` for each workspace + +Remove repo: +- start a migration job that runs `workset repo rm -w [--delete-worktrees] [--delete-local]` for each workspace +- UI must require a confirmation and expose the destructive options: + - delete worktrees (default true) + - delete local clones (default false; only for managed repos) + +Per-workspace migration states (no stable “Not provisioned”): +- `updating` (pending/running) +- `up_to_date` (succeeded) +- `update_failed` (failed; retryable per workspace) + +### 5.10 Error categories and remediation +Errors must map cleanly to actions: +- `auth`: show steps (ssh agent, gh auth, credential helper) +- `network`: retry + offline hints +- `git`: conflicts, dirty state, worktree conflicts +- `config`: invalid repo source, repo missing +- `runtime`: CLI/sessiond unavailable, PATH issues + +### 5.11 Success UX +On success: +- auto-select the new workspace (recommended) +- open default tabs (1 agent, 1 terminal) +- start diff watchers for all repos so the right panel populates quickly + +--- + +## Section 6: Diff UX + Diff Tab Rendering (Right Panel + File Diffs as Tabs) + +This section defines the diff experience inside Spaces: +- the right panel is a compact “diff navigator” +- clicking a file opens a full diff as a tab in the main terminal area + +There is no separate Review page. + +### 6.1 Scope: what we are diffing (v1) +In v1, diffs represent local workspace changes in each repo worktree: +- unstaged changes +- staged changes +- untracked files (rendered via no-index diff against an empty baseline) + +We are not building PR creation/review tooling in this diff surface. + +### 6.2 Right panel: information density and hierarchy +The right panel is always visible in Spaces (collapsible). + +Structure: +- Panel header: `Diff` + totals (files changed, total `+/-`) +- Repo groups (one per repo in the active workspace): + - repo name + - repo total `+/-` + - optional repo status badges: `missing`, `dirty`, `status unknown` + - file list (changed files only) + +File row fields: +- path (display relative to repo root) +- status: `M/A/D/R` (icon or short label) +- `+added/-removed` (green/red) + +### 6.3 Right panel: interactions +Required behaviors: +- Clicking a repo header expands/collapses its file list. +- Clicking a file row opens a Diff tab for that file (see 6.5). +- If a Diff tab for that file is already open, focus it instead of opening a duplicate. + +Optional behaviors (nice to have): +- Search filter in the diff panel (substring match on path). +- Toggle: “Only changed repos” (default on). + +### 6.4 Right panel: ordering rules (v1 defaults) +Repo group order: +- repos with changes first +- then alphabetical by display name + +File order within a repo: +- directories first, then files (path sort) OR plain path sort +- stable ordering is more important than clever ordering + +### 6.5 Diff tabs: what opens when a file is clicked +A Diff tab is created with: +- repo identity +- file identity: `path`, `prev_path` (if rename), `status` +- a snapshot of the file’s `+/-` stats at open time + +Tab title: +- default: `diff: ` (truncate middle if long) +- tab subtitle/tooltip shows `` + +The tab content shows: +- repo + path header with status badge +- `+/-` summary +- unified diff view (v1) + +### 6.6 Diff rendering component (frontend) +Use a dedicated diff renderer component that can: +- render unified diff with syntax highlighting +- handle renames (show old/new path) +- handle deletes (no new content) +- handle binary (no textual diff) +- display “truncated” state when backend truncates patch + +Implementation note: +- The existing Wails app already uses `@pierre/diffs` for rendering; a Tauri rewrite can reuse the same renderer strategy. + +### 6.7 Patch source of truth (backend) +The backend must provide two primitives per (workspace, repo): + +1) Summary: +- changed file list + totals (for right panel) + +2) File patch: +- patch for a single file (for diff tabs) + +Rules: +- Summary is produced by a background watcher while workspace is active. +- File patches are fetched on-demand when a diff tab is opened (and on refresh). + +### 6.8 Keeping an open diff tab consistent with watcher updates +When watcher updates arrive for a repo: +- update the right panel immediately +- do not forcibly re-render any open Diff tab content (avoid scroll jumps) + +If the currently open file’s stats changed since the tab opened: +- show a small “Out of date” indicator in the tab header +- provide a `Refresh diff` action to refetch and rerender the patch + +### 6.9 Large diffs and truncation policy +Backends often need to truncate large patches. The UI must make this obvious and recoverable. + +If the backend reports `truncated: true`: +- show “Diff truncated” warning +- provide actions: + - `Open file in editor` + - `Open repo in Finder` + - optional: `Copy patch` + +The right panel should also cap file lists to a reasonable limit: +- show first N files and display “+ N more” (to avoid UI stalls). + +### 6.10 Binary files +If the backend reports `binary: true`: +- do not attempt to render a textual diff +- show a “Binary file” state and provide actions: + - `Open file` + - `Reveal in Finder` + +### 6.11 Missing repos and unknown status +If a repo worktree path is missing/unavailable: +- right panel shows the repo with a `missing` badge +- file list is empty with a clear message (“Repo path unavailable in this workspace”) +- clicking the repo shows actions: `Open diagnostics`, `Retry migration/provisioning` (if applicable) + +### 6.12 Staged vs unstaged (v1 presentation) +In v1, file patches may combine staged + unstaged into a single unified patch (simpler). + +If we later want more detail: +- add a toggle in Diff tab: `All | Staged | Unstaged` +- but keep the right panel summary aggregated (still one line per file). + +--- + +## Section 7: Settings + Diagnostics (Make Runtime + Auth Boring) + +This section exists because the app’s “real work” depends on the user’s local environment being correct: +- git authentication (SSH keys, credential helpers, `gh` auth) +- CLI availability and versioning +- `workset-sessiond` health for PTYs +- file system access for workspaces, repo worktrees, and diff watchers + +The UI must make these dependencies visible and fixable without guesswork. + +### 7.1 Settings scope and IA +Settings is a first-class page (primary nav), but it should be intentionally small. + +Settings is split into two categories: +1) **App Settings** (global) +2) **Workset Settings** (per workset profile, app-owned config) + +Diagnostics is accessible in two places: +- Settings (global diagnostics and tools) +- Command Center → Diagnostics (workset-scoped diagnostics and quick fixes) + +### 7.2 App Settings (global) +Minimum global settings: +- Default shell / command for “New Terminal” tabs +- Default agent command for “New Agent” tabs (e.g. `claude`) +- Workspace root location (where workspace directories are created, if the app chooses the path) +- Diff defaults: + - max patch bytes (soft cap, UI-friendly) + - file list cap per repo in diff navigator +- Telemetry (if any) with explicit opt-in/out + +Important: These settings should not contain “workset repo list” or workspace lifecycle. Those belong to Workset config and Spaces. + +### 7.3 Workset Settings (per-workset profile) +Minimum per-workset settings: +- Workset name (rename) +- Repo list (add/remove; also available via Command Center) +- Defaults that affect workspace creation/provisioning: + - optional “base branch” preference (if supported by CLI semantics) + - optional “default remote” preference + +Hard rule: Workset creation/editing is app-owned and must not pretend to be a CLI feature. + +### 7.4 Diagnostics: what we must show (always) +Diagnostics must have a single “Status” view that answers: +- Can the backend run `workset` successfully? +- Can the backend reach `workset-sessiond` and create/attach PTYs? +- Does the process environment look like a login environment (PATH, HOME, SHELL)? +- Can git authenticate to a private GitHub repo using the user’s normal setup? + +Displayed as: +- Green/Yellow/Red rows with a one-line explanation and “Fix” action(s). + +### 7.5 Diagnostics: environment snapshot + reload +The app must support: +- `View environment snapshot` +- `Reload login environment` + +This is not a “nice-to-have”: it is the primary escape hatch when the app is launched in an environment that doesn’t match the user’s shell (common on macOS). + +Environment snapshot should show (read-only): +- `PATH` +- `SHELL` +- `HOME` +- `SSH_AUTH_SOCK` (presence and value) +- `GIT_SSH_COMMAND` (if set) +- `GIT_ASKPASS` (if set) +- `GH_CONFIG_DIR` (if set) and `gh auth status` summary (redacted) + +Reload should: +- rehydrate env from the login shell (backend responsibility) +- restart any newly created PTY sessions with the updated env +- not silently mutate running tabs; show a banner: “New sessions use updated environment.” + +### 7.6 Git/GitHub auth checks (opinionated, actionable) +Diagnostics should include explicit checks and fixes for: +- SSH: + - key present (do not display private key paths) + - agent socket present (`SSH_AUTH_SOCK`) + - quick test: `ssh -T git@github.com` (run as an explicit diagnostic action, not on every launch) +- `gh`: + - `gh auth status` (for github.com) + - `gh` present in PATH +- Git credential helper: + - show configured helper (`git config --global credential.helper` and/or system helper on macOS) + +When operations fail with auth errors, surface remediation suggestions that match the error category: +- “Repo URL uses HTTPS; switch to SSH URL” (if the repo source is HTTPS and user expects SSH) +- “Reload login environment” (if PATH/SSH_AUTH_SOCK mismatch) +- “Open GitHub auth” (if gh is missing auth) + +### 7.7 workset CLI health + versioning +Diagnostics should show: +- `workset` binary path and version +- ability to run `workset ls` (or equivalent non-destructive command) +- ability to run workspace-scoped commands against the selected workspace + +If the CLI is missing or outdated: +- show clear action: “Install/Update Workset CLI” with a link/instructions (do not auto-install) + +### 7.8 sessiond health + recovery +Because PTY tabs depend on `workset-sessiond`, diagnostics must provide: +- status: running/not running, version (if available), socket path (if applicable) +- `Restart sessiond` action +- last error and last restart time (if tracked) + +If sessiond is down: +- terminal/agent tabs show a disconnected state with a “Reconnect” and “Restart sessiond” CTA +- never show “loading forever” + +### 7.9 Workspace-scoped diagnostics (in Command Center) +When a specific workset/workspace is selected, Command Center diagnostics should add: +- workspace path exists + is writable +- repo worktree paths exist for each repo +- migration status (if a repo add/rm migration is running or failed) +- diff watcher status per repo + +Provide “Fix” actions: +- Retry failed migration for a workspace +- Restart diff watchers +- Reveal workspace folder + +### 7.10 Debug bundle (supportability) +Add “Export debug bundle” that produces a zip containing: +- redacted diagnostics snapshot (no tokens, no private keys) +- recent backend logs +- recent sessiond logs (if accessible) +- migration job history (app-local) and last error envelopes +- UI layout state (for reproduction) + +This bundle is for humans and future agents to debug issues quickly. + +--- + +## Section 8: Scope Boundaries + Phased Delivery Plan (Build the Core First) + +This section prevents scope creep and defines an implementation sequence that de-risks the two hardest parts early: +- durable terminals/agents (PTY + sessiond) +- workspace/repo provisioning and migrations (CLI-backed, long-running jobs) + +### 8.1 Non-negotiable outcomes (what “v1 works” means) +The product is “real” when a user can: +1) Create a Workset (app-owned config) and add a few repos. +2) Create a Workspace (CLI-backed) for that Workset, see progress, recover from partial failure. +3) Open multiple terminal/agent tabs within that Workspace and have them survive detach/reattach. +4) See a per-repo diff navigator and open file diffs as tabs. +5) Add/remove repos on the Workset and have that apply to all existing Workspaces via migration jobs. + +### 8.2 Explicit non-goals (out of scope for v1) +To avoid turning this into “GitHub Desktop + an IDE + a PR tool”, v1 does not include: +- PR creation, PR review, inline comments, approvals, or GitHub API integration. +- “Branch management UI” beyond Workspace semantics; user is not managing per-repo branches directly. +- Merge/conflict resolution UI. (If `workset`/git reports conflicts, we surface the error and provide a terminal.) +- Repo browsing, file tree editing, or a built-in code editor. +- LSP features, symbol search, or deep IDE-like navigation. +- Multi-user collaboration, sharing worksets, or remote sync of app-owned configs. +- Secrets storage: no storing SSH keys, GitHub tokens, or credential helper secrets inside the app. + +If a feature is proposed that overlaps these, it must be justified against the v1 outcomes above. + +### 8.3 Source-of-truth boundaries (what owns what) +This is a boundary contract, not a suggestion: +- Workset profiles (workset name + repo sources) are **app-owned** and persisted by the app. +- Workspace lifecycle and repo membership inside a workspace are **CLI-owned** operations. +- Terminals/agents are **sessiond-owned** runtime sessions that the app attaches to. +- Diffs are **derived** from repo worktrees and updated via watcher events. + +If UI behavior and CLI behavior appear to conflict, treat it as a design issue: +- surface the mismatch explicitly +- decide whether we change UI semantics or add a new CLI affordance +- do not “paper over” with silent heuristics + +### 8.4 App structure (Tauri shape, at a product level) +The Tauri app is split into three cooperating systems: +1) **Frontend** (web UI): Workset selector, Spaces, tabs, diff navigator, settings/diagnostics. +2) **Backend** (Tauri Rust): typed command/event API, job engine, file watchers, orchestrates CLI + sessiond. +3) **External tools**: + - `workset` CLI for workspace/repo operations + - `workset-sessiond` for PTYs (terminals/agents) + - `git` for diff computation (or backend wrappers over git) + +The backend is responsible for ensuring the UI never “hangs loading forever”. + +### 8.5 Phased delivery plan (recommended) +The goal is to get a working vertical slice quickly, then harden and expand. + +#### Phase 0: Skeleton + persistence +Deliverables: +- Workset selector in top chrome (list/create/rename/delete). +- Local persistence for workset profiles and active workset selection. +- Empty states for “no worksets”, “no repos”, “no workspaces”. + +Acceptance criteria: +- App can restart and preserve active workset selection and workset list. + +#### Phase 1: Workspace list + create job (CLI-backed) +Deliverables: +- Spaces: workspace list for active workset. +- “New Workspace” flow triggers a create job with progress and per-repo status. +- Partial failure state + retry. + +Acceptance criteria: +- A workspace can be created successfully with multiple repos. +- If one repo fails, the UI shows a repair path and does not lose the workspace. + +#### Phase 2: sessiond integration (PTY tabs) +Deliverables: +- Tab strip in Spaces with “New Terminal” and “New Agent”. +- PTY session lifecycle and streaming via sessiond attach/bootstrap/ack. +- Layout persistence keyed by workspace path (tabs at minimum; splits optional in v1). + +Acceptance criteria: +- Open 2+ agent/terminal tabs; switch away and back; tabs reconnect without losing output. +- No more “WebSocket HMR” dependency (native app runtime must be stable). + +#### Phase 3: Diff navigator + diff tabs (derived state) +Deliverables: +- Right panel diff navigator: repo-grouped file list with `+/-`. +- Background diff watchers for active workspace repos. +- Clicking a file opens a diff tab; diff tabs support refresh and large/binary states. + +Acceptance criteria: +- Editing a file updates the right panel shortly after. +- Clicking a file shows a readable diff and does not freeze the UI for large repos. + +#### Phase 4: Workset repo membership migrations (apply-to-all) +Deliverables: +- Add repo to workset profile triggers migration job across all workspaces. +- Remove repo triggers migration job across all workspaces (with confirmation + options). +- Per-workspace migration status and retry tooling. + +Acceptance criteria: +- Add a repo and observe it appear in every existing workspace (or show per-workspace failure + retry). +- Remove a repo and observe it removed from every existing workspace (or show per-workspace failure + retry). + +#### Phase 5: Diagnostics + debug bundle (operational polish) +Deliverables: +- Settings + Diagnostics views per Section 7. +- “Reload login environment”, sessiond status/restart, CLI status/version, auth checks. +- Export debug bundle (redacted). + +Acceptance criteria: +- Common auth/env failures produce actionable diagnostics rather than vague errors. + +### 8.6 Performance + safety constraints (must be enforced) +Constraints: +- Do not compute full diffs for all repos on every UI render; use watchers and caching. +- Cap patch size and file list sizes to avoid UI stalls; show truncation explicitly. +- Never store secrets; redact tokens from logs and debug bundles. +- All destructive operations (repo removal with deletions) require explicit confirmation and defaults must be conservative. + +### 8.7 Migration strategy (from the existing Wails app) +This rewrite is a product restart, but it should still reuse what we already know: +- session durability semantics from `workset-sessiond` +- layout persistence keyed by workspace path +- diff watcher event model +- error envelopes and “loading never” avoidance + +The goal is not to reproduce the old UI; it is to preserve the proven runtime behaviors while implementing the new product IA. diff --git a/internal/git/cli.go b/internal/git/cli.go index 43645711..e9546048 100644 --- a/internal/git/cli.go +++ b/internal/git/cli.go @@ -43,7 +43,7 @@ func (c CLIClient) run(ctx context.Context, repoPath string, args ...string) (gi } cmdArgs = append(cmdArgs, args...) cmd := exec.CommandContext(ctx, c.gitPath, cmdArgs...) - cmd.Env = os.Environ() + cmd.Env = withGitCommandEnv(os.Environ()) var stdout bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &stdout @@ -75,6 +75,27 @@ func (c CLIClient) run(ctx context.Context, repoPath string, args ...string) (gi return result, err } +func withGitCommandEnv(base []string) []string { + env := make([]string, 0, len(base)+1) + hasPrompt := false + + for _, item := range base { + switch { + case strings.HasPrefix(item, "GIT_TERMINAL_PROMPT="): + env = append(env, "GIT_TERMINAL_PROMPT=0") + hasPrompt = true + default: + env = append(env, item) + } + } + + if !hasPrompt { + env = append(env, "GIT_TERMINAL_PROMPT=0") + } + + return env +} + func (c CLIClient) Clone(ctx context.Context, url, path, remoteName string) error { if remoteName == "" { remoteName = "origin" diff --git a/internal/git/cli_test.go b/internal/git/cli_test.go index 31da5dcf..6d26a111 100644 --- a/internal/git/cli_test.go +++ b/internal/git/cli_test.go @@ -258,3 +258,29 @@ func gitRevParse(t *testing.T, dir, ref string) string { } return string(bytes.TrimSpace(stdout.Bytes())) } + +func TestRunForcesNonInteractiveGitEnv(t *testing.T) { + dir := t.TempDir() + scriptPath := filepath.Join(dir, "fake-git.sh") + script := `#!/bin/sh +if [ "$GIT_TERMINAL_PROMPT" != "0" ]; then + echo "GIT_TERMINAL_PROMPT=$GIT_TERMINAL_PROMPT" >&2 + exit 41 +fi +if [ "$GCM_INTERACTIVE" != "Always" ]; then + echo "GCM_INTERACTIVE=$GCM_INTERACTIVE" >&2 + exit 42 +fi +exit 0 +` + if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil { + t.Fatalf("write fake git: %v", err) + } + t.Setenv("GIT_TERMINAL_PROMPT", "1") + t.Setenv("GCM_INTERACTIVE", "Always") + + client := CLIClient{gitPath: scriptPath} + if _, err := client.run(context.Background(), "", "version"); err != nil { + t.Fatalf("run: %v", err) + } +} diff --git a/pkg/worksetapi/env_bootstrap.go b/pkg/worksetapi/env_bootstrap.go index 62f0662c..8070bbce 100644 --- a/pkg/worksetapi/env_bootstrap.go +++ b/pkg/worksetapi/env_bootstrap.go @@ -22,6 +22,8 @@ var ( errLoginEnvLast error ) +const loginEnvCommandTimeout = 5 * time.Second + // EnsureLoginEnv loads the login-shell environment once per process. func EnsureLoginEnv(ctx context.Context) (EnvSnapshotResultJSON, error) { return loadLoginEnv(ctx, false) @@ -92,11 +94,34 @@ func envMap(env []string) map[string]string { return out } -var clearableEnvKeys = []string{"SSH_AUTH_SOCK", "SSH_AGENT_PID", "SSH_ASKPASS", "GIT_SSH_COMMAND"} +var clearableEnvKeys = []string{ + "SSH_AUTH_SOCK", + "SSH_AGENT_PID", + "SSH_ASKPASS", + "GIT_ASKPASS", + "GIT_SSH_COMMAND", + "XDG_CONFIG_HOME", + "XDG_STATE_HOME", + "XDG_CACHE_HOME", +} func shouldOverrideEnvKey(key string) bool { switch key { - case "PATH", "SSH_AUTH_SOCK", "SSH_AGENT_PID", "SSH_ASKPASS", "GIT_SSH_COMMAND", "LANG", "LC_ALL", "LC_CTYPE": + case "PATH", + "HOME", + "USER", + "LOGNAME", + "SSH_AUTH_SOCK", + "SSH_AGENT_PID", + "SSH_ASKPASS", + "GIT_ASKPASS", + "GIT_SSH_COMMAND", + "XDG_CONFIG_HOME", + "XDG_STATE_HOME", + "XDG_CACHE_HOME", + "LANG", + "LC_ALL", + "LC_CTYPE": return true default: return strings.HasPrefix(key, "LC_") @@ -153,7 +178,7 @@ func loadLoginEnv(ctx context.Context, force bool) (EnvSnapshotResultJSON, error shellBase := strings.ToLower(filepath.Base(shell)) command := "env" args := shellArgsForMode(shellBase, command, agentShellModeLogin) - ctx, cancel := context.WithTimeout(ctx, 2*time.Second) + ctx, cancel := context.WithTimeout(ctx, loginEnvCommandTimeout) defer cancel() cmd := exec.CommandContext(ctx, shell, args...) cmd.Env = os.Environ() diff --git a/pkg/worksetapi/env_bootstrap_test.go b/pkg/worksetapi/env_bootstrap_test.go index d7fa6f91..445cd153 100644 --- a/pkg/worksetapi/env_bootstrap_test.go +++ b/pkg/worksetapi/env_bootstrap_test.go @@ -65,6 +65,38 @@ func TestApplyEnvSnapshotKeepsExistingNonOverride(t *testing.T) { } } +func TestApplyEnvSnapshotOverridesHomeAndXDG(t *testing.T) { + t.Setenv("HOME", "/tmp/old-home") + t.Setenv("XDG_CONFIG_HOME", "/tmp/old-xdg-config") + t.Setenv("XDG_STATE_HOME", "/tmp/old-xdg-state") + t.Setenv("XDG_CACHE_HOME", "/tmp/old-xdg-cache") + + changed := applyEnvSnapshot(map[string]string{ + "HOME": "/Users/tester", + "XDG_CONFIG_HOME": "/Users/tester/.config", + "XDG_STATE_HOME": "/Users/tester/.local/state", + "XDG_CACHE_HOME": "/Users/tester/.cache", + }) + + if got := os.Getenv("HOME"); got != "/Users/tester" { + t.Fatalf("expected HOME override, got %q", got) + } + if got := os.Getenv("XDG_CONFIG_HOME"); got != "/Users/tester/.config" { + t.Fatalf("expected XDG_CONFIG_HOME override, got %q", got) + } + if got := os.Getenv("XDG_STATE_HOME"); got != "/Users/tester/.local/state" { + t.Fatalf("expected XDG_STATE_HOME override, got %q", got) + } + if got := os.Getenv("XDG_CACHE_HOME"); got != "/Users/tester/.cache" { + t.Fatalf("expected XDG_CACHE_HOME override, got %q", got) + } + for _, key := range []string{"HOME", "XDG_CONFIG_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"} { + if !containsKey(changed, key) { + t.Fatalf("expected %s in changed keys, got %v", key, changed) + } + } +} + func TestEnvSnapshotDisabledParsing(t *testing.T) { t.Setenv("WORKSET_ENV_SNAPSHOT", "0") if !envSnapshotDisabled() {