diff --git a/.gitignore b/.gitignore index f56ee4b..0e177c5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ build/ # Tauri src-tauri/target/ -src-tauri/Cargo.lock # TypeScript *.tsbuildinfo diff --git a/package-lock.json b/package-lock.json index 6318084..b08d7d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "duckcoding-setup", - "version": "1.0.2", + "version": "1.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "duckcoding-setup", - "version": "1.0.2", + "version": "1.0.9", "license": "MIT", "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -42,16 +42,16 @@ "@tauri-apps/cli": "^2.9.2", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", - "@vitejs/plugin-react": "^4.2.1", + "@vitejs/plugin-react": "^5.1.0", "autoprefixer": "^10.4.16", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", "typescript": "^5.3.3", - "vite": "^5.0.8" + "vite": "^7.1.12" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.19.0" } }, "node_modules/@alloc/quick-lru": { @@ -403,9 +403,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -416,13 +416,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -433,13 +433,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -450,13 +450,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -467,13 +467,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -484,13 +484,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -501,13 +501,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -518,13 +518,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -535,13 +535,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -552,13 +552,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -569,13 +569,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -586,13 +586,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -603,13 +603,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -620,13 +620,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -637,13 +637,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -654,13 +654,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -671,13 +671,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -688,13 +688,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -705,13 +722,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -722,13 +756,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -739,13 +790,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -756,13 +807,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -773,13 +824,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -790,7 +841,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@floating-ui/core": { @@ -1768,9 +1819,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "version": "1.0.0-beta.43", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz", + "integrity": "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==", "dev": true, "license": "MIT" }, @@ -2480,21 +2531,21 @@ "license": "MIT" }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz", + "integrity": "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", + "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", + "@rolldown/pluginutils": "1.0.0-beta.43", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "react-refresh": "^0.18.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" @@ -3200,9 +3251,9 @@ ] }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3210,32 +3261,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -4308,9 +4362,9 @@ } }, "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "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": { @@ -4900,6 +4954,54 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "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/tinyglobby/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/tinyglobby/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/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5064,21 +5166,24 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.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": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5087,19 +5192,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.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 }, @@ -5120,9 +5231,46 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/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/vite/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/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index 23db0ce..e739730 100644 --- a/package.json +++ b/package.json @@ -56,15 +56,15 @@ "@tauri-apps/cli": "^2.9.2", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", - "@vitejs/plugin-react": "^4.2.1", + "@vitejs/plugin-react": "^5.1.0", "autoprefixer": "^10.4.16", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", "typescript": "^5.3.3", - "vite": "^5.0.8" + "vite": "^7.1.12" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.19.0" } } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..ffcf3ef --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,5389 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[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" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[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.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.3", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +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.17", +] + +[[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.8", +] + +[[package]] +name = "cc" +version = "1.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + +[[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 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "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 0.10.1", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +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.108", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.108", +] + +[[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.108", +] + +[[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.108", +] + +[[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.108", +] + +[[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 = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[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 0.5.2", + "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 0.6.3", +] + +[[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.108", +] + +[[package]] +name = "dlopen2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "duckcoding" +version = "1.0.9" +dependencies = [ + "cocoa", + "dirs 5.0.1", + "objc", + "regex", + "reqwest", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-shell", + "thiserror 1.0.69", + "tokio", + "toml_edit 0.23.7", + "urlencoding", +] + +[[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.8", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.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", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +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.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[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 0.3.1", +] + +[[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.108", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[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-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +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.108", +] + +[[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.108", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.12.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "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.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[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.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "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.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +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 = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.12.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.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[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.108", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "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 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "png", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[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 = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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.108", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[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 0.6.2", + "libc", + "objc2 0.6.3", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[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 0.6.3", + "objc2-foundation 0.3.2", +] + +[[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 0.6.3", + "objc2-foundation 0.3.2", +] + +[[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 0.6.3", +] + +[[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 0.6.3", + "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 0.6.3", + "objc2-foundation 0.3.2", +] + +[[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 0.6.3", + "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 0.6.3", + "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.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.10.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[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 0.6.2", + "libc", + "objc2 0.6.3", + "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 0.6.3", + "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 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.10.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.10.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[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 0.6.3", + "objc2-foundation 0.3.2", +] + +[[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 0.6.3", + "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 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[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 0.6.2", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "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 = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_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.108", +] + +[[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.1", +] + +[[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 = "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.12.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 = "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.7", +] + +[[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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.38.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +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.16", +] + +[[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_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[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.108", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[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 = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" +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.108", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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.108", +] + +[[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.108", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[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.108", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.0", + "schemars 0.9.0", + "schemars 1.0.5", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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.108", +] + +[[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_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[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 = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +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.108", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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 0.6.2", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "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.108", +] + +[[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.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs 6.0.0", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "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.17", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a924b6c50fe83193f0f8b14072afa7c25b7a72752a2a73d9549b463f5fe91a38" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs 6.0.0", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.8", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.108", + "tauri-utils", + "thiserror 2.0.17", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.108", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076c78a474a7247c90cad0b6e87e593c4c620ed4efdb79cbe0214f0021f6c39d" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.8", + "walkdir", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c374b6db45f2a8a304f0273a15080d98c70cde86178855fc24653ba657a1144c" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9368f09358496f2229313fccb37682ad116b7f46fa76981efe116994a0628926" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.3", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929f5df216f5c02a9e894554401bcdab6eec3e39ec6a4a7731c7067fc8688a93" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b8bbe426abdbf52d050e52ed693130dbd68375b9ad82a3fb17efb4c8d85673" +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.17", + "toml 0.9.8", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" +dependencies = [ + "embed-resource", + "toml 0.9.8", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +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 = "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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[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.108", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +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.108", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap 2.12.0", + "serde_core", + "serde_spanned 1.0.3", + "toml_datetime 0.7.3", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +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.12.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.12.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap 2.12.0", + "toml_datetime 0.7.3", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow 0.7.13", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" +dependencies = [ + "crossbeam-channel", + "dirs 6.0.0", + "libappindicator", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "once_cell", + "png", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.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.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.108", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.17", + "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 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "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.108", +] + +[[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.108", +] + +[[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-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wry" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" +dependencies = [ + "base64 0.22.1", + "block2 0.6.2", + "cookie", + "crossbeam-channel", + "dirs 6.0.0", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.17", + "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.108", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[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.108", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[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.108", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b47595a..a3e9124 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,8 +13,9 @@ tauri = { version = "2", features = ["tray-icon"] } tauri-plugin-shell = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +thiserror = "1" dirs = "5" -toml = "0.8" +toml_edit = "0.23.7" reqwest = { version = "0.12", features = ["json", "blocking"] } tokio = { version = "1", features = ["full"] } urlencoding = "2.1" diff --git a/src-tauri/src/commands/config_ops.rs b/src-tauri/src/commands/config_ops.rs new file mode 100644 index 0000000..c967884 --- /dev/null +++ b/src-tauri/src/commands/config_ops.rs @@ -0,0 +1,666 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +use serde_json::{json, Map, Value}; +use toml_edit::DocumentMut; + +use crate::error::{AppError, AppResult}; +use crate::models::{ActiveConfig, GlobalConfig}; +use crate::services::{list_profiles as list_profiles_in_dir, profile_file, JsonStore, TomlStore}; + +fn home_dir() -> AppResult { + dirs::home_dir().ok_or_else(|| AppError::config("无法获取用户目录")) +} + +fn ensure_dir(path: &Path) -> AppResult<()> { + fs::create_dir_all(path).map_err(AppError::from) +} + +#[tauri::command] +pub async fn configure_api( + tool: String, + _provider: String, + api_key: String, + base_url: Option, + profile_name: Option, +) -> Result<(), String> { + configure_api_impl(tool, api_key, base_url, profile_name).map_err(|e| e.to_string()) +} + +fn configure_api_impl( + tool: String, + api_key: String, + base_url: Option, + profile_name: Option, +) -> AppResult<()> { + let home_dir = home_dir()?; + let base_url_str = base_url.unwrap_or_else(|| "https://jp.duckcoding.com".to_string()); + + match tool.as_str() { + "claude-code" => update_claude_settings(&home_dir, &api_key, &base_url_str, profile_name), + "codex" => update_codex_settings(&home_dir, &api_key, &base_url_str, profile_name), + "gemini-cli" => update_gemini_settings(&home_dir, &api_key, &base_url_str, profile_name), + _ => Err(AppError::config(format!("未知工具: {}", tool))), + } +} + +fn update_claude_settings( + home_dir: &Path, + api_key: &str, + base_url: &str, + profile_name: Option, +) -> AppResult<()> { + let config_dir = home_dir.join(".claude"); + ensure_dir(&config_dir)?; + let settings_path = config_dir.join("settings.json"); + let store = JsonStore::new(&settings_path); + + store.update(|doc| { + if !doc.is_object() { + *doc = Value::Object(Map::new()); + } + let obj = doc.as_object_mut().unwrap(); + let env_entry = obj + .entry("env") + .or_insert_with(|| Value::Object(Map::new())); + let env_obj = env_entry + .as_object_mut() + .ok_or_else(|| AppError::config("env 字段必须是对象"))?; + env_obj.insert( + "ANTHROPIC_AUTH_TOKEN".into(), + Value::String(api_key.to_string()), + ); + env_obj.insert( + "ANTHROPIC_BASE_URL".into(), + Value::String(base_url.to_string()), + ); + Ok(()) + })?; + + if let Some(profile) = profile_name.filter(|p| !p.is_empty()) { + let backup_path = config_dir.join(format!("settings.{}.json", profile)); + let backup_data = json!({ + "env": { + "ANTHROPIC_AUTH_TOKEN": api_key, + "ANTHROPIC_BASE_URL": base_url + } + }); + fs::write(&backup_path, serde_json::to_string_pretty(&backup_data)?) + .map_err(AppError::from)?; + } + + Ok(()) +} + +fn update_codex_settings( + home_dir: &Path, + api_key: &str, + base_url: &str, + profile_name: Option, +) -> AppResult<()> { + let config_dir = home_dir.join(".codex"); + ensure_dir(&config_dir)?; + + let config_path = config_dir.join("config.toml"); + let auth_path = config_dir.join("auth.json"); + + let toml_store = TomlStore::new(&config_path); + toml_store.update(|doc| { + let table = doc.as_table_mut(); + table["model_provider"] = toml_edit::value("duckcoding"); + table["model"] = toml_edit::value("gpt-5-codex"); + table["model_reasoning_effort"] = toml_edit::value("high"); + table["network_access"] = toml_edit::value("enabled"); + table["disable_response_storage"] = toml_edit::value(true); + + let provider_key = if base_url.contains("duckcoding") { + "duckcoding" + } else { + "custom" + }; + + let provider_base_url = if base_url.ends_with("/v1") { + base_url.to_string() + } else { + format!("{}/v1", base_url) + }; + + let providers_table = table + .entry("model_providers") + .or_insert(toml_edit::table()) + .as_table_mut() + .ok_or_else(|| AppError::config("model_providers 必须是 table"))?; + + let provider_entry = providers_table + .entry(provider_key) + .or_insert(toml_edit::table()); + let provider_table = provider_entry + .as_table_mut() + .ok_or_else(|| AppError::config("provider 必须是 table"))?; + + provider_table["name"] = toml_edit::value(provider_key); + provider_table["base_url"] = toml_edit::value(provider_base_url); + provider_table["wire_api"] = toml_edit::value("responses"); + provider_table["requires_openai_auth"] = toml_edit::value(true); + + Ok(()) + })?; + + let auth_store = JsonStore::new(&auth_path); + auth_store.update(|doc| { + if !doc.is_object() { + *doc = Value::Object(Map::new()); + } + let obj = doc.as_object_mut().unwrap(); + obj.insert("OPENAI_API_KEY".into(), Value::String(api_key.to_string())); + Ok(()) + })?; + + if let Some(profile) = profile_name.filter(|p| !p.is_empty()) { + let backup_config_path = config_dir.join(format!("config.{}.toml", profile)); + fs::copy(&config_path, &backup_config_path).map_err(AppError::from)?; + + let backup_auth_path = config_dir.join(format!("auth.{}.json", profile)); + fs::copy(&auth_path, &backup_auth_path).map_err(AppError::from)?; + } + + Ok(()) +} + +fn update_gemini_settings( + home_dir: &Path, + api_key: &str, + base_url: &str, + profile_name: Option, +) -> AppResult<()> { + let config_dir = home_dir.join(".gemini"); + ensure_dir(&config_dir)?; + + let env_path = config_dir.join(".env"); + let mut existing_env = std::collections::BTreeMap::new(); + + if env_path.exists() { + let content = fs::read_to_string(&env_path)?; + for line in content.lines() { + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + if let Some((key, value)) = trimmed.split_once('=') { + existing_env.insert(key.trim().to_string(), value.trim().to_string()); + } + } + } + + existing_env.insert("GOOGLE_GEMINI_BASE_URL".into(), base_url.to_string()); + existing_env.insert("GEMINI_API_KEY".into(), api_key.to_string()); + existing_env + .entry("GEMINI_MODEL".into()) + .or_insert_with(|| "gemini-2.5-pro".to_string()); + + let env_content = existing_env + .iter() + .map(|(k, v)| format!("{}={}", k, v)) + .collect::>() + .join("\n") + + "\n"; + fs::write(&env_path, env_content).map_err(AppError::from)?; + + let settings_path = config_dir.join("settings.json"); + let settings_store = JsonStore::new(&settings_path); + settings_store.update(|doc| { + if !doc.is_object() { + *doc = Value::Object(Map::new()); + } + let obj = doc.as_object_mut().unwrap(); + obj.entry("ide") + .or_insert_with(|| json!({"enabled": true })); + if !obj.contains_key("security") { + obj.insert( + "security".into(), + json!({ "auth": { "selectedType": "gemini-api-key" } }), + ); + } + Ok(()) + })?; + + if let Some(profile) = profile_name.filter(|p| !p.is_empty()) { + let backup_env_path = config_dir.join(format!(".env.{}", profile)); + let backup_content = format!( + "GOOGLE_GEMINI_BASE_URL={}\nGEMINI_API_KEY={}\nGEMINI_MODEL=gemini-2.5-pro\n", + base_url, api_key + ); + fs::write(&backup_env_path, backup_content).map_err(AppError::from)?; + + let backup_settings_path = config_dir.join(format!("settings.{}.json", profile)); + let backup_settings = json!({ + "ide": { "enabled": true }, + "security": { "auth": { "selectedType": "gemini-api-key" } } + }); + fs::write( + &backup_settings_path, + serde_json::to_string_pretty(&backup_settings)?, + ) + .map_err(AppError::from)?; + } + + Ok(()) +} + +#[tauri::command] +pub async fn list_profiles(tool: String) -> Result, String> { + list_profiles_impl(tool).map_err(|e| e.to_string()) +} + +fn list_profiles_impl(tool: String) -> AppResult> { + let home_dir = home_dir()?; + let (dir, prefix, suffix) = match tool.as_str() { + "claude-code" => (home_dir.join(".claude"), "settings.", ".json"), + "codex" => (home_dir.join(".codex"), "config.", ".toml"), + "gemini-cli" => (home_dir.join(".gemini"), ".env.", ""), + _ => return Err(AppError::config(format!("未知工具: {}", tool))), + }; + + list_profiles_in_dir(&dir, prefix, suffix) +} + +#[tauri::command] +pub async fn switch_profile(tool: String, profile: String) -> Result<(), String> { + switch_profile_impl(tool, profile).map_err(|e| e.to_string()) +} + +fn switch_profile_impl(tool: String, profile: String) -> AppResult<()> { + let home_dir = home_dir()?; + + match tool.as_str() { + "claude-code" => { + let config_dir = home_dir.join(".claude"); + let backup_path = profile_file(&config_dir, "settings.", &profile, ".json"); + if !backup_path.exists() { + return Err(AppError::config(format!("找不到备份: {:?}", backup_path))); + } + + let data = fs::read_to_string(&backup_path)?; + let backup: Value = serde_json::from_str(&data)?; + let active_path = config_dir.join("settings.json"); + let store = JsonStore::new(&active_path); + store.update(|doc| { + if !doc.is_object() { + *doc = Value::Object(Map::new()); + } + let obj = doc.as_object_mut().unwrap(); + let env = backup + .get("env") + .cloned() + .unwrap_or_else(|| Value::Object(Map::new())); + obj.insert("env".into(), env); + Ok(()) + })?; + } + "codex" => { + let config_dir = home_dir.join(".codex"); + let backup_config_path = profile_file(&config_dir, "config.", &profile, ".toml"); + let backup_auth_path = profile_file(&config_dir, "auth.", &profile, ".json"); + + if !backup_config_path.exists() { + return Err(AppError::config(format!( + "找不到备份: {:?}", + backup_config_path + ))); + } + + let config_doc = fs::read_to_string(&backup_config_path)?.parse::()?; + let active_config_path = config_dir.join("config.toml"); + TomlStore::new(&active_config_path).write(&config_doc)?; + + if backup_auth_path.exists() { + let auth_value: Value = + serde_json::from_str(&fs::read_to_string(&backup_auth_path)?)?; + JsonStore::new(config_dir.join("auth.json")).write(&auth_value)?; + } + } + "gemini-cli" => { + let config_dir = home_dir.join(".gemini"); + let backup_env_path = profile_file(&config_dir, ".env.", &profile, ""); + if !backup_env_path.exists() { + return Err(AppError::config(format!( + "找不到备份: {:?}", + backup_env_path + ))); + } + + let env_content = fs::read_to_string(&backup_env_path)?; + fs::write(config_dir.join(".env"), env_content).map_err(AppError::from)?; + + let backup_settings_path = profile_file(&config_dir, "settings.", &profile, ".json"); + if backup_settings_path.exists() { + let settings_value: Value = + serde_json::from_str(&fs::read_to_string(&backup_settings_path)?)?; + JsonStore::new(config_dir.join("settings.json")).write(&settings_value)?; + } + } + _ => return Err(AppError::config(format!("未知工具: {}", tool))), + } + + Ok(()) +} + +#[tauri::command] +pub async fn delete_profile(tool: String, profile: String) -> Result<(), String> { + delete_profile_impl(tool, profile).map_err(|e| e.to_string()) +} + +fn delete_profile_impl(tool: String, profile: String) -> AppResult<()> { + let home_dir = home_dir()?; + let config_dir = match tool.as_str() { + "claude-code" => home_dir.join(".claude"), + "codex" => home_dir.join(".codex"), + "gemini-cli" => home_dir.join(".gemini"), + _ => return Err(AppError::config(format!("未知工具: {}", tool))), + }; + + let candidates = match tool.as_str() { + "claude-code" => vec![profile_file(&config_dir, "settings.", &profile, ".json")], + "codex" => vec![ + profile_file(&config_dir, "config.", &profile, ".toml"), + profile_file(&config_dir, "auth.", &profile, ".json"), + ], + "gemini-cli" => vec![ + profile_file(&config_dir, ".env.", &profile, ""), + profile_file(&config_dir, "settings.", &profile, ".json"), + ], + _ => vec![], + }; + + let mut removed = false; + for file in candidates { + if file.exists() { + fs::remove_file(&file).map_err(AppError::from)?; + removed = true; + } + } + + if removed { + Ok(()) + } else { + Err(AppError::config("未找到匹配的备份文件")) + } +} + +#[tauri::command] +pub async fn get_active_config(tool: String) -> Result { + get_active_config_impl(tool).map_err(|e| e.to_string()) +} + +fn get_active_config_impl(tool: String) -> AppResult { + let home = home_dir()?; + + match tool.as_str() { + "claude-code" => { + let settings = JsonStore::new(home.join(".claude/settings.json")).read()?; + let env = settings + .get("env") + .and_then(|v| v.as_object()) + .cloned() + .unwrap_or_default(); + + let raw_key = env + .get("ANTHROPIC_AUTH_TOKEN") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let base_url = env + .get("ANTHROPIC_BASE_URL") + .and_then(|v| v.as_str()) + .unwrap_or("未配置"); + + let profile_name = if !raw_key.is_empty() && base_url != "未配置" { + detect_profile_name("claude-code", raw_key, base_url, &home) + } else { + None + }; + + Ok(ActiveConfig { + api_key: if raw_key.is_empty() { + "未配置".into() + } else { + mask_api_key(raw_key) + }, + base_url: base_url.to_string(), + profile_name, + }) + } + "codex" => { + let auth = JsonStore::new(home.join(".codex/auth.json")).read()?; + let raw_key = auth + .get("OPENAI_API_KEY") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let doc = TomlStore::new(home.join(".codex/config.toml")).read()?; + let base_url = doc + .as_table() + .get("model_providers") + .and_then(|item| item.as_table()) + .and_then(|providers| { + providers.iter().find_map(|(_, provider)| { + provider.as_table().and_then(|table| { + table + .get("base_url") + .and_then(|item| item.as_value()) + .and_then(|value| value.as_str()) + }) + }) + }) + .unwrap_or("未配置") + .to_string(); + + let profile_name = if !raw_key.is_empty() && base_url != "未配置" { + detect_profile_name("codex", raw_key, &base_url, &home) + } else { + None + }; + + Ok(ActiveConfig { + api_key: if raw_key.is_empty() { + "未配置".into() + } else { + mask_api_key(raw_key) + }, + base_url, + profile_name, + }) + } + "gemini-cli" => { + let env_path = home.join(".gemini/.env"); + let mut api_key = String::new(); + let mut base_url = String::from("未配置"); + + if env_path.exists() { + for line in fs::read_to_string(&env_path)?.lines() { + let trimmed = line.trim(); + if trimmed.starts_with("GEMINI_API_KEY=") { + api_key = trimmed + .strip_prefix("GEMINI_API_KEY=") + .unwrap_or("") + .to_string(); + } else if trimmed.starts_with("GOOGLE_GEMINI_BASE_URL=") { + base_url = trimmed + .strip_prefix("GOOGLE_GEMINI_BASE_URL=") + .unwrap_or("未配置") + .to_string(); + } + } + } + + let profile_name = if !api_key.is_empty() && base_url != "未配置" { + detect_profile_name("gemini-cli", &api_key, &base_url, &home) + } else { + None + }; + + Ok(ActiveConfig { + api_key: if api_key.is_empty() { + "未配置".into() + } else { + mask_api_key(&api_key) + }, + base_url, + profile_name, + }) + } + _ => Err(AppError::config(format!("未知工具: {}", tool))), + } +} + +#[tauri::command] +pub async fn save_global_config(user_id: String, system_token: String) -> Result<(), String> { + save_global_config_impl(user_id, system_token).map_err(|e| e.to_string()) +} + +fn save_global_config_impl(user_id: String, system_token: String) -> AppResult<()> { + let path = global_config_path()?; + let store = JsonStore::new(&path); + store.write(&json!({ + "user_id": user_id, + "system_token": system_token + }))?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + let metadata = std::fs::metadata(&path)?; + let mut perms = metadata.permissions(); + perms.set_mode(0o600); + std::fs::set_permissions(&path, perms)?; + } + + Ok(()) +} + +#[tauri::command] +pub async fn get_global_config() -> Result, String> { + load_global_config().map_err(|e| e.to_string()) +} + +fn global_config_path() -> AppResult { + let home_dir = home_dir()?; + let config_dir = home_dir.join(".duckcoding"); + ensure_dir(&config_dir)?; + Ok(config_dir.join("config.json")) +} + +pub fn load_global_config() -> AppResult> { + let path = global_config_path()?; + let value = JsonStore::new(path).read()?; + if value.is_null() || value.as_object().map(|o| o.is_empty()).unwrap_or(false) { + Ok(None) + } else { + Ok(Some(serde_json::from_value(value)?)) + } +} + +fn detect_profile_name( + tool: &str, + api_key: &str, + base_url: &str, + home_dir: &Path, +) -> Option { + let profiles = list_profiles_impl(tool.to_string()).ok()?; + for profile in profiles { + match tool { + "claude-code" => { + let path = home_dir + .join(".claude") + .join(format!("settings.{}.json", profile)); + if let Ok(content) = fs::read_to_string(&path) { + if let Ok(value) = serde_json::from_str::(&content) { + if let Some(env) = value.get("env").and_then(|v| v.as_object()) { + let backup_key = env + .get("ANTHROPIC_AUTH_TOKEN") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let backup_base = env + .get("ANTHROPIC_BASE_URL") + .and_then(|v| v.as_str()) + .unwrap_or(""); + if backup_key == api_key && backup_base == base_url { + return Some(profile); + } + } + } + } + } + "codex" => { + let backup_config = home_dir + .join(".codex") + .join(format!("config.{}.toml", profile)); + let backup_auth = home_dir + .join(".codex") + .join(format!("auth.{}.json", profile)); + + if let (Ok(config_content), Ok(auth_content)) = ( + fs::read_to_string(&backup_config), + fs::read_to_string(&backup_auth), + ) { + if let (Ok(doc), Ok(auth)) = ( + config_content.parse::(), + serde_json::from_str::(&auth_content), + ) { + let backup_key = auth + .get("OPENAI_API_KEY") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let backup_base = doc + .as_table() + .get("model_providers") + .and_then(|item| item.as_table()) + .and_then(|providers| { + providers.iter().find_map(|(_, provider)| { + provider.as_table().and_then(|table| { + table + .get("base_url") + .and_then(|item| item.as_value()) + .and_then(|value| value.as_str()) + }) + }) + }) + .unwrap_or(""); + + if backup_key == api_key && backup_base == base_url { + return Some(profile); + } + } + } + } + "gemini-cli" => { + let backup_env = home_dir.join(".gemini").join(format!(".env.{}", profile)); + if let Ok(content) = fs::read_to_string(&backup_env) { + let mut backup_key = ""; + let mut backup_base = ""; + for line in content.lines() { + if let Some(val) = line.strip_prefix("GEMINI_API_KEY=") { + backup_key = val; + } else if let Some(val) = line.strip_prefix("GOOGLE_GEMINI_BASE_URL=") { + backup_base = val; + } + } + if backup_key == api_key && backup_base == base_url { + return Some(profile); + } + } + } + _ => {} + } + } + None +} + +fn mask_api_key(key: &str) -> String { + if key.len() <= 8 { + "****".into() + } else { + format!("{}...{}", &key[..4], &key[key.len() - 4..]) + } +} diff --git a/src-tauri/src/commands/install.rs b/src-tauri/src/commands/install.rs new file mode 100644 index 0000000..ed84f31 --- /dev/null +++ b/src-tauri/src/commands/install.rs @@ -0,0 +1,643 @@ +use std::process::Command; + +use crate::error::{AppError, AppResult}; +use crate::models::{InstallResult, NodeEnvironment, ToolStatus, UpdateResult}; +#[cfg(target_os = "windows")] +use crate::services::CREATE_NO_WINDOW; +use crate::services::{extended_path, CommandRunner}; +use serde::Deserialize; +#[cfg(target_os = "windows")] +use std::os::windows::process::CommandExt; + +#[tauri::command] +pub async fn check_installations() -> Result, String> { + check_installations_impl().map_err(|e| e.to_string()) +} + +fn check_installations_impl() -> AppResult> { + let mut tools = vec![ + ToolStatus { + id: "claude-code".to_string(), + name: "Claude Code".to_string(), + installed: false, + version: None, + }, + ToolStatus { + id: "codex".to_string(), + name: "CodeX".to_string(), + installed: false, + version: None, + }, + ToolStatus { + id: "gemini-cli".to_string(), + name: "Gemini CLI".to_string(), + installed: false, + version: None, + }, + ]; + + let runner = CommandRunner::new(); + + check_tool_installed(&runner, "claude", "claude-code", &mut tools)?; + check_tool_installed(&runner, "codex", "codex", &mut tools)?; + check_tool_installed(&runner, "gemini", "gemini-cli", &mut tools)?; + + Ok(tools) +} + +fn check_tool_installed( + runner: &CommandRunner, + command: &str, + tool_id: &str, + tools: &mut [ToolStatus], +) -> AppResult<()> { + let cmd = format!("{} --version 2>&1", command); + if let Ok(output) = runner.run(&cmd) { + if output.status.success() { + let stdout_str = String::from_utf8_lossy(&output.stdout); + let stderr_str = String::from_utf8_lossy(&output.stderr); + let version_output = if !stdout_str.trim().is_empty() { + stdout_str.trim().to_string() + } else { + stderr_str.trim().to_string() + }; + let clean_version = extract_version(&version_output).unwrap_or(version_output); + + if let Some(tool) = tools.iter_mut().find(|t| t.id == tool_id) { + tool.installed = true; + tool.version = Some(clean_version); + } + } + } + Ok(()) +} + +fn permission_denied_error(action: &str) -> AppError { + AppError::Other(format!( + "{}权限不足。请以管理员身份重新执行,或参考 npm 官方文档修复权限问题:https://docs.npmjs.com/getting-started/fixing-npm-permissions", + action + )) +} + +fn output_indicates_permission_denied(output: &std::process::Output) -> bool { + let stderr = String::from_utf8_lossy(&output.stderr).to_ascii_lowercase(); + stderr.contains("eacces") || stderr.contains("permission denied") +} + +#[tauri::command] +pub async fn check_node_environment() -> Result { + check_node_environment_impl().map_err(|e| e.to_string()) +} + +fn check_node_environment_impl() -> AppResult { + let runner = CommandRunner::new(); + + let (node_available, node_version) = match runner.run("node --version 2>&1") { + Ok(output) if output.status.success() => { + let stdout_str = String::from_utf8_lossy(&output.stdout); + let stderr_str = String::from_utf8_lossy(&output.stderr); + let version_output = if !stdout_str.trim().is_empty() { + stdout_str.trim().to_string() + } else { + stderr_str.trim().to_string() + }; + (true, Some(version_output)) + } + _ => (false, None), + }; + + let (npm_available, npm_version) = match runner.run("npm --version 2>&1") { + Ok(output) if output.status.success() => { + let stdout_str = String::from_utf8_lossy(&output.stdout); + let stderr_str = String::from_utf8_lossy(&output.stderr); + let version_output = if !stdout_str.trim().is_empty() { + stdout_str.trim().to_string() + } else { + stderr_str.trim().to_string() + }; + (true, Some(version_output)) + } + _ => (false, None), + }; + + Ok(NodeEnvironment { + node_available, + node_version, + npm_available, + npm_version, + }) +} + +#[tauri::command] +pub async fn install_tool(tool: String, method: String) -> Result { + install_tool_impl(tool, method).map_err(|e| e.to_string()) +} + +fn install_tool_impl(tool: String, method: String) -> AppResult { + match (tool.as_str(), method.as_str()) { + ("claude-code", "npm") => install_claude_via_npm(), + ("claude-code", "mirror") => install_claude_via_mirror(), + ("codex", "npm") => install_codex_via_npm(), + ("codex", "mirror") => install_codex_via_mirror(), + ("gemini-cli", "npm") => install_gemini_via_npm(), + _ => Err(AppError::config("不支持的工具或安装方式")), + } +} + +fn install_claude_via_npm() -> AppResult { + execute_npm_install("@anthropic-ai/claude-code") +} + +fn install_codex_via_npm() -> AppResult { + execute_npm_install("@openai/codex") +} + +fn install_gemini_via_npm() -> AppResult { + execute_npm_install("@google/gemini-cli") +} + +fn execute_npm_install(package: &str) -> AppResult { + #[cfg(target_os = "windows")] + let output = Command::new("npm") + .env("PATH", extended_path()) + .args(["install", "-g", package]) + .creation_flags(CREATE_NO_WINDOW) + .output() + .map_err(|e| { + if e.kind() == std::io::ErrorKind::PermissionDenied { + permission_denied_error("npm 安装") + } else { + AppError::from(e) + } + })?; + + #[cfg(not(target_os = "windows"))] + let output = Command::new("npm") + .env("PATH", extended_path()) + .args(["install", "-g", package]) + .output() + .map_err(|e| { + if e.kind() == std::io::ErrorKind::PermissionDenied { + permission_denied_error("npm 安装") + } else { + AppError::from(e) + } + })?; + + if output.status.success() { + Ok(InstallResult { + success: true, + message: format!("{} 安装成功", package), + output: String::from_utf8_lossy(&output.stdout).to_string(), + }) + } else { + if output_indicates_permission_denied(&output) { + Err(permission_denied_error("npm 安装")) + } else { + Err(AppError::command(format!( + "npm 安装失败: {}", + String::from_utf8_lossy(&output.stderr) + ))) + } + } +} + +fn install_claude_via_mirror() -> AppResult { + #[cfg(target_os = "windows")] + let command = ( + "powershell", + vec![ + "-Command", + "irm https://mirror.duckcoding.com/claude-code/install.ps1 | iex", + ], + ); + + #[cfg(not(target_os = "windows"))] + let command = ( + "sh", + vec![ + "-c", + "curl -fsSL https://mirror.duckcoding.com/claude-code/install.sh | bash", + ], + ); + + execute_shell_command(command.0, &command.1) +} + +#[derive(Deserialize)] +pub struct CheckUpdateArgs { + tool: String, + #[serde(rename = "currentVersion")] + current_version: Option, +} + +#[tauri::command] +pub async fn check_update(args: CheckUpdateArgs) -> Result { + check_update_impl(args.tool, args.current_version) + .await + .map_err(|e| e.to_string()) +} + +async fn check_update_impl( + tool: String, + provided_version: Option, +) -> AppResult { + let runner = CommandRunner::new(); + let detected_version = current_version(&runner, &tool)?; + let current_version_opt = detected_version.or(provided_version); + + let package_name = match tool.as_str() { + "claude-code" => "@anthropic-ai/claude-code", + "codex" => "@openai/codex", + "gemini-cli" => "@google/gemini-cli", + _ => return Err(AppError::config(format!("Unknown tool: {}", tool))), + }; + + #[cfg(debug_assertions)] + println!("[update] checking package {}", package_name); + + let latest_version_str = fetch_latest_version_from_npm(package_name).await?; + + #[cfg(debug_assertions)] + println!( + "[update] current={:?}, latest={}", + current_version_opt, latest_version_str + ); + + let has_update = current_version_opt + .as_ref() + .map(|current| compare_versions(current, &latest_version_str)) + .unwrap_or(false); + + Ok(UpdateResult { + success: true, + message: "检查完成".to_string(), + has_update, + current_version: current_version_opt, + latest_version: Some(latest_version_str), + }) +} + +#[tauri::command] +pub async fn update_tool(tool: String) -> Result { + update_tool_impl(tool).await.map_err(|e| e.to_string()) +} + +async fn update_tool_impl(tool: String) -> AppResult { + let runner = CommandRunner::new(); + let current_version_opt = current_version(&runner, &tool)?; + + let (cmd, args, description) = match tool.as_str() { + "claude-code" => detect_claude_update_command()?, + "codex" => detect_codex_update_command()?, + "gemini-cli" => ( + "npm", + vec!["update", "-g", "@google/gemini-cli"], + "npm更新".to_string(), + ), + _ => return Err(AppError::config(format!("Unknown tool: {}", tool))), + }; + + #[cfg(debug_assertions)] + println!("[update] executing {} {:?}", cmd, args); + + let output = run_update_command(cmd, &args).await?; + + if !output.status.success() { + #[cfg(debug_assertions)] + println!( + "[update] command failed stdout={} stderr={}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + if output_indicates_permission_denied(&output) { + return Err(permission_denied_error("更新操作")); + } + return Err(AppError::command(format!( + "{} 失败: {}", + description, + String::from_utf8_lossy(&output.stderr) + ))); + } + + let new_runner = CommandRunner::new(); + let new_version = current_version(&new_runner, tool.as_str())?; + + Ok(UpdateResult { + success: true, + message: format!("{} 已完成", description), + has_update: false, + current_version: new_version.clone().or(current_version_opt), + latest_version: new_version, + }) +} + +fn detect_claude_update_command() -> AppResult<(&'static str, Vec<&'static str>, String)> { + #[cfg(target_os = "windows")] + let check_npm = Command::new("npm") + .env("PATH", extended_path()) + .args(["list", "-g", "@anthropic-ai/claude-code", "--depth=0"]) + .creation_flags(CREATE_NO_WINDOW) + .output(); + + #[cfg(not(target_os = "windows"))] + let check_npm = Command::new("npm") + .env("PATH", extended_path()) + .args(["list", "-g", "@anthropic-ai/claude-code", "--depth=0"]) + .output(); + + if let Ok(output) = check_npm { + let stdout_str = String::from_utf8_lossy(&output.stdout); + if output.status.success() && stdout_str.contains("@anthropic-ai/claude-code") { + return Ok(( + "npm", + vec!["update", "-g", "@anthropic-ai/claude-code"], + "npm更新".to_string(), + )); + } + } + + #[cfg(target_os = "windows")] + { + Ok(( + "powershell", + vec!["-Command", "irm https://claude.ai/install.ps1 | iex"], + "官方安装脚本更新".to_string(), + )) + } + + #[cfg(not(target_os = "windows"))] + { + Ok(( + "sh", + vec!["-c", "curl -fsSL https://claude.ai/install.sh | bash"], + "官方安装脚本更新".to_string(), + )) + } +} + +fn detect_codex_update_command() -> AppResult<(&'static str, Vec<&'static str>, String)> { + #[cfg(target_os = "windows")] + let check_npm = Command::new("npm") + .env("PATH", extended_path()) + .args(["list", "-g", "@openai/codex", "--depth=0"]) + .creation_flags(CREATE_NO_WINDOW) + .output(); + + #[cfg(not(target_os = "windows"))] + let check_npm = Command::new("npm") + .env("PATH", extended_path()) + .args(["list", "-g", "@openai/codex", "--depth=0"]) + .output(); + + if let Ok(output) = check_npm { + let stdout_str = String::from_utf8_lossy(&output.stdout); + if output.status.success() && stdout_str.contains("@openai/codex") { + return Ok(( + "npm", + vec!["update", "-g", "@openai/codex"], + "npm更新".to_string(), + )); + } + } + + #[cfg(target_os = "windows")] + { + Ok(( + "powershell", + vec!["-Command", "irm https://codex.openai.com/install.ps1 | iex"], + "官方安装脚本更新".to_string(), + )) + } + + #[cfg(not(target_os = "windows"))] + { + Ok(( + "sh", + vec![ + "-c", + "curl -fsSL https://codex.openai.com/install.sh | bash", + ], + "官方安装脚本更新".to_string(), + )) + } +} + +fn current_version(runner: &CommandRunner, tool: &str) -> AppResult> { + let cmd = match tool { + "claude-code" => "claude --version 2>&1", + "codex" => "codex --version 2>&1", + "gemini-cli" => "gemini --version 2>&1", + _ => return Ok(None), + }; + + match runner.run(cmd) { + Ok(output) => { + #[cfg(debug_assertions)] + println!( + "[update] {} command status {:?} stdout={} stderr={}", + tool, + output.status.code(), + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + + if output.status.success() { + let stdout_str = String::from_utf8_lossy(&output.stdout); + let stderr_str = String::from_utf8_lossy(&output.stderr); + let version_output = if !stdout_str.trim().is_empty() { + stdout_str.trim().to_string() + } else { + stderr_str.trim().to_string() + }; + return Ok(extract_version(&version_output)); + } + } + Err(err) => { + #[cfg(debug_assertions)] + println!("[update] {} command error: {}", tool, err); + } + } + + Ok(None) +} + +async fn fetch_latest_version_from_npm(package_name: &str) -> AppResult { + #[cfg(target_os = "windows")] + let npm_view_output = { + let mut command = Command::new("npm"); + command.env("PATH", extended_path()); + command.args(["view", package_name, "version"]); + command.creation_flags(CREATE_NO_WINDOW); + command.output() + }; + + #[cfg(not(target_os = "windows"))] + let npm_view_output = { + let mut command = Command::new("npm"); + command.env("PATH", extended_path()); + command.args(["view", package_name, "version"]); + command.output() + }; + + if let Ok(output) = npm_view_output { + if output.status.success() { + let version = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !version.is_empty() { + #[cfg(debug_assertions)] + println!("[update] npm view {} -> {}", package_name, version); + return Ok(version); + } + } + #[cfg(debug_assertions)] + println!( + "[update] npm view failed: stdout={} stderr={}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + if output_indicates_permission_denied(&output) { + return Err(permission_denied_error("npm 查询")); + } + } + + let client = reqwest::Client::new(); + let mirrors = vec![ + format!("https://registry.npmmirror.com/{}", package_name), + format!("https://registry.npmjs.org/{}", package_name), + ]; + + for url in mirrors { + #[cfg(debug_assertions)] + println!("[update] requesting {}", url); + + let response = client + .get(&url) + .header("User-Agent", "DuckCoding-Desktop-App") + .timeout(std::time::Duration::from_secs(10)) + .send() + .await?; + + if response.status().is_success() { + let info = response.json::().await?; + #[cfg(debug_assertions)] + println!("[update] mirror {} -> {}", url, info.dist_tags.latest); + return Ok(info.dist_tags.latest); + } + #[cfg(debug_assertions)] + println!("[update] mirror {} status {:?}", url, response.status()); + } + + Err(AppError::Other("所有npm镜像源均无法访问".to_string())) +} + +fn extract_version(text: &str) -> Option { + let re = regex::Regex::new(r"(\d+\.\d+\.\d+)").ok()?; + re.captures(text) + .and_then(|caps| caps.get(1)) + .map(|m| m.as_str().to_string()) +} + +fn compare_versions(current: &str, latest: &str) -> bool { + let current_parts: Vec = current.split('.').filter_map(|s| s.parse().ok()).collect(); + let latest_parts: Vec = latest.split('.').filter_map(|s| s.parse().ok()).collect(); + + for i in 0..3 { + let c = current_parts.get(i).copied().unwrap_or(0); + let l = latest_parts.get(i).copied().unwrap_or(0); + + if l > c { + return true; + } else if l < c { + return false; + } + } + + false +} + +async fn run_update_command(cmd: &str, args: &[&str]) -> AppResult { + use tokio::process::Command as AsyncCommand; + use tokio::time::{timeout, Duration}; + + let mut command = AsyncCommand::new(cmd); + command.env("PATH", extended_path()); + command.args(args); + + #[cfg(target_os = "windows")] + { + use std::os::windows::process::CommandExt; + command.creation_flags(CREATE_NO_WINDOW); + } + + let output = timeout(Duration::from_secs(120), command.output()) + .await + .map_err(|_| AppError::Other("更新操作超时,请稍后重试".to_string()))? + .map_err(|e| { + if e.kind() == std::io::ErrorKind::PermissionDenied { + permission_denied_error("更新操作") + } else { + AppError::from(e) + } + })?; + + Ok(output) +} + +fn install_codex_via_mirror() -> AppResult { + #[cfg(target_os = "windows")] + let command = ( + "powershell", + vec![ + "-Command", + "irm https://mirror.duckcoding.com/codex/install.ps1 | iex", + ], + ); + + #[cfg(not(target_os = "windows"))] + let command = ( + "sh", + vec![ + "-c", + "curl -fsSL https://mirror.duckcoding.com/codex/install.sh | bash", + ], + ); + + execute_shell_command(command.0, &command.1) +} + +fn execute_shell_command(cmd: &str, args: &[&str]) -> AppResult { + let mut command = Command::new(cmd); + command.env("PATH", extended_path()); + command.args(args); + + #[cfg(target_os = "windows")] + { + use std::os::windows::process::CommandExt; + command.creation_flags(CREATE_NO_WINDOW); + } + + let output = command.output().map_err(|e| { + if e.kind() == std::io::ErrorKind::PermissionDenied { + permission_denied_error("命令执行") + } else { + AppError::from(e) + } + })?; + + if output.status.success() { + Ok(InstallResult { + success: true, + message: format!("{} 执行成功", cmd), + output: String::from_utf8_lossy(&output.stdout).to_string(), + }) + } else { + if output_indicates_permission_denied(&output) { + Err(permission_denied_error("命令执行")) + } else { + Err(AppError::command(format!( + "命令执行失败: {}", + String::from_utf8_lossy(&output.stderr) + ))) + } + } +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..19da47a --- /dev/null +++ b/src-tauri/src/commands/mod.rs @@ -0,0 +1,12 @@ +pub mod config_ops; +pub mod install; +pub mod usage; + +pub use config_ops::{ + configure_api, delete_profile, get_active_config, get_global_config, list_profiles, + save_global_config, switch_profile, +}; +pub use install::{ + check_installations, check_node_environment, check_update, install_tool, update_tool, +}; +pub use usage::{generate_api_key_for_tool, get_usage_stats, get_user_quota}; diff --git a/src-tauri/src/commands/usage.rs b/src-tauri/src/commands/usage.rs new file mode 100644 index 0000000..65536e1 --- /dev/null +++ b/src-tauri/src/commands/usage.rs @@ -0,0 +1,242 @@ +use std::time::Duration; + +use crate::commands::config_ops::load_global_config; +use crate::error::{AppError, AppResult}; +use crate::models::{ + ApiResponse, GenerateApiKeyResult, UsageApiResponse, UsageStatsResult, UserApiResponse, + UserQuotaResult, +}; + +#[tauri::command] +pub async fn generate_api_key_for_tool(tool: String) -> Result { + generate_api_key_impl(tool).await.map_err(|e| e.to_string()) +} + +async fn generate_api_key_impl(tool: String) -> AppResult { + let global_config = + load_global_config()?.ok_or_else(|| AppError::config("请先配置用户ID和系统访问令牌"))?; + + let (name, group) = match tool.as_str() { + "claude-code" => ("Claude Code一键创建", "Claude Code专用"), + "codex" => ("CodeX一键创建", "CodeX专用"), + "gemini-cli" => ("Gemini CLI一键创建", "Gemini CLI专用"), + _ => return Err(AppError::config(format!("Unknown tool: {}", tool))), + }; + + let client = reqwest::Client::new(); + let create_url = "https://duckcoding.com/api/token"; + + let create_body = serde_json::json!({ + "remain_quota": 500000, + "expired_time": -1, + "unlimited_quota": true, + "model_limits_enabled": false, + "model_limits": "", + "name": name, + "group": group, + "allow_ips": "" + }); + + let create_response = client + .post(create_url) + .header( + "Authorization", + format!("Bearer {}", global_config.system_token), + ) + .header("New-Api-User", &global_config.user_id) + .header("Content-Type", "application/json") + .json(&create_body) + .send() + .await + .map_err(AppError::from)?; + + if !create_response.status().is_success() { + let status = create_response.status(); + let error_text = create_response.text().await.unwrap_or_default(); + return Ok(GenerateApiKeyResult { + success: false, + message: format!("创建token失败 ({}): {}", status, error_text), + api_key: None, + }); + } + + tokio::time::sleep(Duration::from_millis(500)).await; + + let search_url = format!( + "https://duckcoding.com/api/token/search?keyword={}", + urlencoding::encode(name) + ); + + let search_response = client + .get(&search_url) + .header( + "Authorization", + format!("Bearer {}", global_config.system_token), + ) + .header("New-Api-User", &global_config.user_id) + .header("Content-Type", "application/json") + .send() + .await + .map_err(AppError::from)?; + + if !search_response.status().is_success() { + return Ok(GenerateApiKeyResult { + success: false, + message: "创建成功但获取API Key失败,请稍后在DuckCoding控制台查看".to_string(), + api_key: None, + }); + } + + let api_response: ApiResponse = search_response.json().await.map_err(AppError::from)?; + + if !api_response.success { + return Ok(GenerateApiKeyResult { + success: false, + message: format!("API返回错误: {}", api_response.message), + api_key: None, + }); + } + + if let Some(mut data) = api_response.data { + if !data.is_empty() { + data.sort_by(|a, b| b.id.cmp(&a.id)); + let token = &data[0]; + let api_key = format!("sk-{}", token.key); + return Ok(GenerateApiKeyResult { + success: true, + message: "API Key 创建成功".to_string(), + api_key: Some(api_key), + }); + } + } + + Ok(GenerateApiKeyResult { + success: false, + message: "未获取到新创建的 token".to_string(), + api_key: None, + }) +} + +#[tauri::command] +pub async fn get_usage_stats() -> Result { + get_usage_stats_impl().await.map_err(|e| e.to_string()) +} + +async fn get_usage_stats_impl() -> AppResult { + let global_config = + load_global_config()?.ok_or_else(|| AppError::config("请先配置用户ID和系统访问令牌"))?; + + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() as i64; + + let beijing_offset = 8 * 3600; + let today_end = (now + beijing_offset) / 86400 * 86400 + 86400 - beijing_offset; + let start_timestamp = today_end - 30 * 86400; + let end_timestamp = today_end; + + let client = reqwest::Client::new(); + let url = format!( + "https://duckcoding.com/api/data/self?start_timestamp={}&end_timestamp={}", + start_timestamp, end_timestamp + ); + + let response = client + .get(&url) + .header( + "Authorization", + format!("Bearer {}", global_config.system_token), + ) + .header("New-Api-User", &global_config.user_id) + .header("Content-Type", "application/json") + .send() + .await + .map_err(AppError::from)?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Ok(UsageStatsResult { + success: false, + message: format!("获取用量统计失败 ({}): {}", status, error_text), + data: vec![], + }); + } + + let api_response: UsageApiResponse = response.json().await.map_err(AppError::from)?; + + if !api_response.success { + return Ok(UsageStatsResult { + success: false, + message: format!("API返回错误: {}", api_response.message), + data: vec![], + }); + } + + Ok(UsageStatsResult { + success: true, + message: "获取成功".to_string(), + data: api_response.data.unwrap_or_default(), + }) +} + +#[tauri::command] +pub async fn get_user_quota() -> Result { + get_user_quota_impl().await.map_err(|e| e.to_string()) +} + +async fn get_user_quota_impl() -> AppResult { + let global_config = + load_global_config()?.ok_or_else(|| AppError::config("请先配置用户ID和系统访问令牌"))?; + + let client = reqwest::Client::new(); + let url = "https://duckcoding.com/api/user/self"; + + let response = client + .get(url) + .header( + "Authorization", + format!("Bearer {}", global_config.system_token), + ) + .header("New-Api-User", &global_config.user_id) + .header("Content-Type", "application/json") + .send() + .await + .map_err(AppError::from)?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(AppError::Other(format!( + "获取用户信息失败 ({}): {}", + status, error_text + ))); + } + + let api_response: UserApiResponse = response.json().await.map_err(AppError::from)?; + + if !api_response.success { + return Err(AppError::Other(format!( + "API返回错误: {}", + api_response.message + ))); + } + + let user_info = api_response + .data + .ok_or_else(|| AppError::Other("未获取到用户信息".into()))?; + + let remaining_quota = user_info.quota as f64 / 500000.0; + let used_quota = user_info.used_quota as f64 / 500000.0; + let total_quota = remaining_quota + used_quota; + + Ok(UserQuotaResult { + success: true, + message: "获取成功".to_string(), + total_quota, + used_quota, + remaining_quota, + request_count: user_info.request_count, + }) +} diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs new file mode 100644 index 0000000..22e5997 --- /dev/null +++ b/src-tauri/src/error.rs @@ -0,0 +1,35 @@ +use std::fmt::Display; + +use thiserror::Error; + +pub type AppResult = Result; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("IO错误: {0}")] + Io(#[from] std::io::Error), + #[error("JSON 解析错误: {0}")] + Json(#[from] serde_json::Error), + #[error("TOML 解析错误: {0}")] + Toml(#[from] toml_edit::TomlError), + #[error("HTTP 请求错误: {0}")] + Http(#[from] reqwest::Error), + #[error("Tauri 错误: {0}")] + Tauri(#[from] tauri::Error), + #[error("命令执行失败: {0}")] + Command(String), + #[error("配置错误: {0}")] + Config(String), + #[error("{0}")] + Other(String), +} + +impl AppError { + pub fn command(err: E) -> Self { + Self::Command(err.to_string()) + } + + pub fn config(err: E) -> Self { + Self::Config(err.to_string()) + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b992213..c5d4843 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,2418 +1,141 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +mod commands; +mod error; +mod models; +mod services; + +use std::env; + +use commands::{ + check_installations, check_node_environment, check_update, configure_api, delete_profile, + generate_api_key_for_tool, get_active_config, get_global_config, get_usage_stats, + get_user_quota, install_tool, list_profiles, save_global_config, switch_profile, update_tool, +}; +use error::AppResult; use tauri::{ menu::{Menu, MenuItem, PredefinedMenuItem}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, - Manager, Runtime, AppHandle, + AppHandle, Manager, Runtime, }; -use std::process::Command; -use std::env; -use std::fs; -use std::path::PathBuf; -use serde_json::{Value, Map}; -use serde::{Deserialize, Serialize}; - -// Windows特定:隐藏命令行窗口 -#[cfg(target_os = "windows")] -use std::os::windows::process::CommandExt; - -// 辅助函数:获取扩展的PATH环境变量 -fn get_extended_path() -> String { - #[cfg(target_os = "windows")] - { - let user_profile = env::var("USERPROFILE").unwrap_or_else(|_| "C:\\Users\\Default".to_string()); - - let system_paths = vec![ - // Claude Code 可能的安装路径 - format!("{}\\AppData\\Local\\Programs\\claude-code", user_profile), - format!("{}\\AppData\\Roaming\\npm", user_profile), - format!("{}\\AppData\\Local\\Programs\\Python\\Python312", user_profile), - format!("{}\\AppData\\Local\\Programs\\Python\\Python312\\Scripts", user_profile), - - // 常见安装路径 - "C:\\Program Files\\nodejs".to_string(), - "C:\\Program Files\\Git\\cmd".to_string(), - - // 系统路径 - "C:\\Windows\\System32".to_string(), - "C:\\Windows".to_string(), - ]; - - let current_path = env::var("PATH").unwrap_or_default(); - format!("{};{}", system_paths.join(";"), current_path) - } - - #[cfg(not(target_os = "windows"))] - { - let home_dir = env::var("HOME").unwrap_or_else(|_| "/Users/default".to_string()); - - let mut system_paths = vec![ - // Claude Code 可能的安装路径 - format!("{}/.local/bin", home_dir), - format!("{}/.claude/bin", home_dir), - format!("{}/.claude/local", home_dir), // Claude Code local安装 - - // Homebrew - "/opt/homebrew/bin".to_string(), - "/usr/local/bin".to_string(), - - // 系统路径 - "/usr/bin".to_string(), - "/bin".to_string(), - "/usr/sbin".to_string(), - "/sbin".to_string(), - ]; - - // 添加nvm路径(如果存在) - let nvm_dir = format!("{}/.nvm/versions/node", home_dir); - if let Ok(entries) = fs::read_dir(&nvm_dir) { - for entry in entries.flatten() { - if let Ok(file_type) = entry.file_type() { - if file_type.is_dir() { - let bin_path = entry.path().join("bin"); - if bin_path.exists() { - system_paths.push(bin_path.to_string_lossy().to_string()); - } - } - } - } - } - - format!("{}:{}", system_paths.join(":"), env::var("PATH").unwrap_or_default()) - } -} - -//定义 Tauri Commands -#[tauri::command] -async fn check_installations() -> Result, String> { - let mut tools = vec![ - ToolStatus { - id: "claude-code".to_string(), - name: "Claude Code".to_string(), - installed: false, - version: None, - }, - ToolStatus { - id: "codex".to_string(), - name: "CodeX".to_string(), - installed: false, - version: None, - }, - ToolStatus { - id: "gemini-cli".to_string(), - name: "Gemini CLI".to_string(), - installed: false, - version: None, - }, - ]; - - // 跨平台命令执行辅助函数 - let run_command = |cmd: &str| -> Result { - #[cfg(target_os = "windows")] - { - Command::new("cmd") - .env("PATH", get_extended_path()) - .arg("/C") - .arg(cmd) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - } - - #[cfg(not(target_os = "windows"))] - { - Command::new("sh") - .env("PATH", get_extended_path()) - .arg("-c") - .arg(cmd) - .output() - } - }; - - // 检测 Claude Code - if let Ok(output) = run_command("claude --version 2>&1") { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - - #[cfg(debug_assertions)] - println!("Claude Code detection - status: {}, stdout: {}, stderr: {}", output.status.success(), stdout_str.trim(), stderr_str.trim()); - - // 只有命令成功执行才认为已安装 - if output.status.success() { - if let Some(tool) = tools.iter_mut().find(|t| t.id == "claude-code") { - tool.installed = true; - // 尝试从stdout或stderr获取版本 - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - tool.version = Some(version_output); - } - } - } - - // 检测 CodeX - if let Ok(output) = run_command("codex --version 2>&1") { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - - #[cfg(debug_assertions)] - println!("CodeX detection - status: {}, stdout: {}, stderr: {}", output.status.success(), stdout_str.trim(), stderr_str.trim()); - - if output.status.success() { - if let Some(tool) = tools.iter_mut().find(|t| t.id == "codex") { - tool.installed = true; - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - tool.version = Some(version_output); - } - } - } - - // 检测 Gemini CLI - if let Ok(output) = run_command("gemini --version 2>&1") { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - - #[cfg(debug_assertions)] - println!("Gemini CLI detection - status: {}, stdout: {}, stderr: {}", output.status.success(), stdout_str.trim(), stderr_str.trim()); - - if output.status.success() { - if let Some(tool) = tools.iter_mut().find(|t| t.id == "gemini-cli") { - tool.installed = true; - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - tool.version = Some(version_output); - } - } - } - - Ok(tools) -} - -// 检测node环境 -#[tauri::command] -async fn check_node_environment() -> Result { - let run_command = |cmd: &str| -> Result { - #[cfg(target_os = "windows")] - { - Command::new("cmd") - .env("PATH", get_extended_path()) - .arg("/C") - .arg(cmd) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - } - #[cfg(not(target_os = "windows"))] - { - Command::new("sh") - .env("PATH", get_extended_path()) - .arg("-c") - .arg(cmd) - .output() - } - }; - - // 检测node - let (node_available, node_version) = if let Ok(output) = run_command("node --version 2>&1") { - if output.status.success() { - let version = String::from_utf8_lossy(&output.stdout).trim().to_string(); - (true, Some(version)) - } else { - (false, None) - } - } else { - (false, None) - }; - - // 检测npm - let (npm_available, npm_version) = if let Ok(output) = run_command("npm --version 2>&1") { - if output.status.success() { - let version = String::from_utf8_lossy(&output.stdout).trim().to_string(); - (true, Some(version)) - } else { - (false, None) - } - } else { - (false, None) - }; - - Ok(NodeEnvironment { - node_available, - node_version, - npm_available, - npm_version, - }) -} - -#[tauri::command] -async fn install_tool(tool: String, method: String) -> Result { - #[cfg(debug_assertions)] - println!("Installing {} via {} (pure Rust implementation)", tool, method); - - match tool.as_str() { - "claude-code" => { - if method == "npm" { - // npm 安装 - #[cfg(target_os = "windows")] - let output = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["install", "-g", "@anthropic-ai/claude-code"]) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - "npm 未安装或未找到\n\n请先安装 Node.js (包含 npm):\n1. 访问 https://nodejs.org 下载安装\n2. 或使用官方安装方式(无需 npm)".to_string() - } else { - format!("执行 npm 失败: {}", e) - } - })?; - #[cfg(not(target_os = "windows"))] - let output = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["install", "-g", "@anthropic-ai/claude-code"]) - .output() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - "npm 未安装或未找到\n\n请先安装 Node.js (包含 npm):\n1. 访问 https://nodejs.org 下载安装\n2. 或使用官方安装方式(无需 npm)".to_string() - } else { - format!("执行 npm 失败: {}", e) - } - })?; - - if output.status.success() { - Ok(InstallResult { - success: true, - message: "Claude Code installed successfully via npm".to_string(), - output: String::from_utf8_lossy(&output.stdout).to_string(), - }) - } else { - Err(format!("npm installation failed: {}", String::from_utf8_lossy(&output.stderr))) - } - } else { - // official: 使用DuckCoding镜像安装脚本 - #[cfg(target_os = "windows")] - { - // Windows: irm https://mirror.duckcoding.com/claude-code/install.ps1 | iex - let output = Command::new("powershell") - .env("PATH", get_extended_path()) - .args(&[ - "-Command", - "irm https://mirror.duckcoding.com/claude-code/install.ps1 | iex" - ]) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - .map_err(|e| format!("Failed to execute installation: {}", e))?; - - if output.status.success() { - Ok(InstallResult { - success: true, - message: "Claude Code installed successfully".to_string(), - output: String::from_utf8_lossy(&output.stdout).to_string(), - }) - } else { - Err(format!("Installation failed: {}", String::from_utf8_lossy(&output.stderr))) - } - } - - #[cfg(not(target_os = "windows"))] - { - // macOS/Linux: curl -fsSL https://mirror.duckcoding.com/claude-code/install.sh | bash - let output = Command::new("sh") - .env("PATH", get_extended_path()) - .args(&[ - "-c", - "curl -fsSL https://mirror.duckcoding.com/claude-code/install.sh | bash" - ]) - .output() - .map_err(|e| format!("Failed to execute installation: {}", e))?; - - if output.status.success() { - Ok(InstallResult { - success: true, - message: "Claude Code installed successfully".to_string(), - output: String::from_utf8_lossy(&output.stdout).to_string(), - }) - } else { - Err(format!("Installation failed: {}", String::from_utf8_lossy(&output.stderr))) - } - } - } - }, - "codex" => { - // CodeX 安装 - if method == "brew" { - #[cfg(target_os = "macos")] - { - let output = Command::new("brew") - .env("PATH", get_extended_path()) - .args(&["install", "--cask", "codex"]) - .output() - .map_err(|e| format!("Failed to execute brew: {}", e))?; - - if output.status.success() { - Ok(InstallResult { - success: true, - message: "CodeX installed successfully via Homebrew".to_string(), - output: String::from_utf8_lossy(&output.stdout).to_string(), - }) - } else { - Err(format!("Homebrew installation failed: {}", String::from_utf8_lossy(&output.stderr))) - } - } - #[cfg(not(target_os = "macos"))] - { - Err("Homebrew is only available on macOS".to_string()) - } - } else { - // npm 安装(跨平台) - #[cfg(target_os = "windows")] - let output = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["install", "-g", "@openai/codex"]) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - "npm 未安装或未找到\n\n请先安装 Node.js (包含 npm):\n访问 https://nodejs.org 下载安装".to_string() - } else { - format!("执行 npm 失败: {}", e) - } - })?; - - #[cfg(not(target_os = "windows"))] - let output = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["install", "-g", "@openai/codex"]) - .output() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - "npm 未安装或未找到\n\n请先安装 Node.js (包含 npm):\n访问 https://nodejs.org 下载安装".to_string() - } else { - format!("执行 npm 失败: {}", e) - } - })?; - - if output.status.success() { - Ok(InstallResult { - success: true, - message: "CodeX installed successfully via npm".to_string(), - output: String::from_utf8_lossy(&output.stdout).to_string(), - }) - } else { - Err(format!("npm installation failed: {}", String::from_utf8_lossy(&output.stderr))) - } - } - }, - "gemini-cli" => { - // Gemini CLI 使用 npm 安装 - #[cfg(target_os = "windows")] - let output = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["install", "-g", "@google/gemini-cli"]) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - "npm 未安装或未找到\n\n请先安装 Node.js (包含 npm):\n访问 https://nodejs.org 下载安装".to_string() - } else { - format!("执行 npm 失败: {}", e) - } - })?; - - #[cfg(not(target_os = "windows"))] - let output = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["install", "-g", "@google/gemini-cli"]) - .output() - .map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - "npm 未安装或未找到\n\n请先安装 Node.js (包含 npm):\n访问 https://nodejs.org 下载安装".to_string() - } else { - format!("执行 npm 失败: {}", e) - } - })?; - - if output.status.success() { - Ok(InstallResult { - success: true, - message: "Gemini CLI installed successfully via npm".to_string(), - output: String::from_utf8_lossy(&output.stdout).to_string(), - }) - } else { - Err(format!("npm installation failed: {}", String::from_utf8_lossy(&output.stderr))) - } - }, - _ => Err(format!("Unknown tool: {}", tool)) - } -} - -// npm Registry API 响应结构 -#[derive(Deserialize, Debug)] -struct NpmPackageInfo { - #[serde(rename = "dist-tags")] - dist_tags: NpmDistTags, -} - -#[derive(Deserialize, Debug)] -struct NpmDistTags { - latest: String, -} - -// 从npm镜像源获取最新版本 -async fn fetch_latest_version_from_npm(package_name: &str) -> Result { - let client = reqwest::Client::new(); - - // 优先使用国内镜像(淘宝npm镜像) - let mirrors = vec![ - format!("https://registry.npmmirror.com/{}", package_name), - format!("https://registry.npmjs.org/{}", package_name), - ]; - - for mirror_url in mirrors { - println!("Trying to fetch version from: {}", mirror_url); - - match client - .get(&mirror_url) - .header("User-Agent", "DuckCoding-Desktop-App") - .timeout(std::time::Duration::from_secs(10)) - .send() - .await - { - Ok(response) => { - if response.status().is_success() { - match response.json::().await { - Ok(package_info) => { - println!("Successfully fetched version: {}", package_info.dist_tags.latest); - return Ok(package_info.dist_tags.latest); - } - Err(e) => { - println!("Failed to parse response from {}: {}", mirror_url, e); - continue; - } - } - } else { - println!("Failed to fetch from {}: status {}", mirror_url, response.status()); - continue; - } - } - Err(e) => { - println!("Request to {} failed: {}", mirror_url, e); - continue; - } - } - } - - Err("所有npm镜像源均无法访问".to_string()) -} - -// 只检查更新,不执行 -#[tauri::command] -async fn check_update(tool: String) -> Result { - #[cfg(debug_assertions)] - println!("Checking updates for {} (pure Rust + npm mirror)", tool); - - // 跨平台命令执行辅助函数 - let run_command = |cmd: &str| -> Result { - #[cfg(target_os = "windows")] - { - Command::new("cmd") - .env("PATH", get_extended_path()) - .arg("/C") - .arg(cmd) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - } - - #[cfg(not(target_os = "windows"))] - { - Command::new("sh") - .env("PATH", get_extended_path()) - .arg("-c") - .arg(cmd) - .output() - } - }; - - // 获取当前安装的版本 - let current_version = match tool.as_str() { - "claude-code" => { - if let Ok(output) = run_command("claude --version 2>&1") { - if output.status.success() { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - extract_version(&version_output) - } else { - None - } - } else { - None - } - }, - "codex" => { - if let Ok(output) = run_command("codex --version 2>&1") { - if output.status.success() { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - extract_version(&version_output) - } else { - None - } - } else { - None - } - }, - "gemini-cli" => { - if let Ok(output) = run_command("gemini --version 2>&1") { - if output.status.success() { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - extract_version(&version_output) - } else { - None - } - } else { - None - } - }, - _ => None, - }; - - #[cfg(debug_assertions)] - println!("Current version: {:?}", current_version); - - // 根据工具类型获取npm包名 - let package_name = match tool.as_str() { - "claude-code" => "@anthropic-ai/claude-code", - "codex" => "@openai/codex", - "gemini-cli" => "@google/gemini-cli", - _ => { - return Err(format!("Unknown tool: {}", tool)); - } - }; - - // 从npm镜像源获取最新版本 - let latest_version_result = fetch_latest_version_from_npm(package_name).await; - - match latest_version_result { - Ok(latest_version_str) => { - #[cfg(debug_assertions)] - println!("Latest version from npm: {}", latest_version_str); - - // 比较版本 - let has_update = if let Some(ref current) = current_version { - compare_versions(current, &latest_version_str) - } else { - false - }; - - Ok(UpdateResult { - success: true, - message: "检查完成".to_string(), - has_update, - current_version, - latest_version: Some(latest_version_str), - }) - }, - Err(e) => { - println!("Failed to fetch latest version: {}", e); - // 降级:如果npm镜像源失败,返回无法检查但不报错 - Ok(UpdateResult { - success: true, - message: format!("无法检查更新: {}", e), - has_update: false, - current_version, - latest_version: None, - }) - } - } +fn main() { + tauri::Builder::default() + .setup(|app| { + setup_working_directory(app)?; + setup_tray(app)?; + Ok(()) + }) + .plugin(tauri_plugin_shell::init()) + .invoke_handler(tauri::generate_handler![ + check_installations, + check_node_environment, + install_tool, + check_update, + update_tool, + configure_api, + list_profiles, + switch_profile, + delete_profile, + get_active_config, + save_global_config, + get_global_config, + generate_api_key_for_tool, + get_usage_stats, + get_user_quota + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } -#[tauri::command] -async fn update_tool(tool: String) -> Result { - #[cfg(debug_assertions)] - println!("Updating {} (pure Rust implementation)", tool); - - // 根据工具类型获取更新命令 - let (update_command, update_args, description) = match tool.as_str() { - "claude-code" => { - // Claude Code 检测安装方式 - // 首先检查是否通过 npm 安装 - #[cfg(target_os = "windows")] - let check_npm = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["list", "-g", "@anthropic-ai/claude-code", "--depth=0"]) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output(); - - #[cfg(not(target_os = "windows"))] - let check_npm = Command::new("npm") - .env("PATH", get_extended_path()) - .args(&["list", "-g", "@anthropic-ai/claude-code", "--depth=0"]) - .output(); - - if let Ok(output) = check_npm { - let stdout_str = String::from_utf8_lossy(&output.stdout); - // 如果 npm list 输出包含包名,说明是 npm 安装的 - if output.status.success() && stdout_str.contains("@anthropic-ai/claude-code") { - #[cfg(debug_assertions)] - println!("Claude Code detected as npm installation, using npm update"); - ("npm", vec!["update", "-g", "@anthropic-ai/claude-code"], "npm更新") - } else { - // 使用官方安装脚本更新(更稳定) - #[cfg(debug_assertions)] - println!("Claude Code detected as native installation, using official installer"); - #[cfg(target_os = "windows")] - { - ("powershell", vec!["-Command", "irm https://claude.ai/install.ps1 | iex"], "官方安装脚本更新") - } - #[cfg(not(target_os = "windows"))] - { - ("sh", vec!["-c", "curl -fsSL https://claude.ai/install.sh | bash"], "官方安装脚本更新") - } - } - } else { - // npm 命令失败,默认使用官方安装脚本 - #[cfg(debug_assertions)] - println!("npm check failed, defaulting to official installer"); - #[cfg(target_os = "windows")] - { - ("powershell", vec!["-Command", "irm https://claude.ai/install.ps1 | iex"], "官方安装脚本更新") - } - #[cfg(not(target_os = "windows"))] - { - ("sh", vec!["-c", "curl -fsSL https://claude.ai/install.sh | bash"], "官方安装脚本更新") - } - } - }, - "codex" => { - // CodeX 更新策略:根据平台选择最佳方式 - #[cfg(target_os = "macos")] - { - // macOS: 优先使用 Homebrew Cask(无需 sudo,版本 0.53.0) - // 如果用户想要最新的 npm 版本(0.54.0),需要手动切换 - - // 检查是否已通过 Homebrew 安装 - let check_brew_cask = Command::new("brew") - .env("PATH", get_extended_path()) - .args(&["list", "--cask", "codex"]) - .output(); - - let is_brew_installed = if let Ok(output) = check_brew_cask { - output.status.success() - } else { - false - }; - - if is_brew_installed { - // 已经是 Homebrew 安装,直接升级 - ("brew", vec!["upgrade", "--cask", "codex"], "Homebrew Cask更新") - } else { - // 不是 Homebrew,切换到 Homebrew(推荐) - ("sh", vec!["-c", "rm -f /opt/homebrew/bin/codex /usr/local/bin/codex ~/.local/bin/codex ~/.codex/bin/codex && brew install --cask codex"], "切换到 Homebrew Cask") - } - } - #[cfg(target_os = "windows")] +fn setup_working_directory(app: &tauri::App) -> AppResult<()> { + if let Ok(resource_dir) = app.path().resource_dir() { + if cfg!(debug_assertions) { + if let Some(project_root) = resource_dir + .parent() + .and_then(|p| p.parent()) + .and_then(|p| p.parent()) { - // Windows: 使用 npm(主要方式) - ("npm", vec!["update", "-g", "@openai/codex"], "npm更新") + let _ = env::set_current_dir(project_root); } - #[cfg(target_os = "linux")] - { - // Linux: 使用 npm(主要方式) - ("npm", vec!["update", "-g", "@openai/codex"], "npm更新") - } - }, - "gemini-cli" => { - // Gemini CLI 使用 npm 更新(跨平台) - ("npm", vec!["update", "-g", "@google/gemini-cli"], "npm更新") - }, - _ => { - return Err(format!("Unknown tool: {}", tool)); - } - }; - - println!("使用{}方式更新: {} {:?}", description, update_command, update_args); - - // 执行更新,使用tokio::time::timeout添加超时(120秒) - use tokio::time::{timeout, Duration}; - - let update_task = tokio::task::spawn_blocking(move || { - #[cfg(target_os = "windows")] - { - Command::new(update_command) - .env("PATH", get_extended_path()) - .args(&update_args) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - } - #[cfg(not(target_os = "windows"))] - { - Command::new(update_command) - .env("PATH", get_extended_path()) - .args(&update_args) - .output() - } - }); - - let output = match timeout(Duration::from_secs(120), update_task).await { - Ok(Ok(Ok(output))) => output, - Ok(Ok(Err(e))) => { - return Err(format!("更新失败: {}", e)); - }, - Ok(Err(e)) => { - return Err(format!("更新任务错误: {}", e)); - }, - Err(_) => { - let timeout_msg = if description.contains("DuckCoding镜像") { - "更新超时(120秒)。\n\n可能的原因:\n1. 镜像服务器响应慢\n2. 网络连接不稳定\n\n建议:\n1. 检查网络连接\n2. 重试更新\n3. 或使用 npm 方式:先卸载后重装\n npm uninstall -g @anthropic-ai/claude-code\n npm install -g @anthropic-ai/claude-code" + } else { + let parent_dir = if cfg!(target_os = "macos") { + resource_dir + .parent() + .and_then(|p| p.parent()) + .unwrap_or(&resource_dir) } else { - "更新超时(120秒)。\n\n请检查网络连接或稍后重试。" + resource_dir.parent().unwrap_or(&resource_dir) }; - return Err(timeout_msg.to_string()); - } - }; - - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - #[cfg(debug_assertions)] - { - println!("Update stdout: {}", stdout); - println!("Update stderr: {}", stderr); - } - - // 检查是否是 npm 缓存权限错误 - if !output.status.success() && stderr.contains("EACCES") && stderr.contains(".npm") { - return Err(format!( - "npm 权限问题\n\n这是因为之前使用 sudo npm 安装导致的。\n\n解决方案(任选其一):\n\n方案1 - 修复 npm 权限(推荐):\n在终端运行:\nsudo chown -R $(id -u):$(id -g) \"$HOME/.npm\"\n\n方案2 - 配置 npm 使用用户目录:\nnpm config set prefix ~/.npm-global\nexport PATH=~/.npm-global/bin:$PATH\n\n方案3 - macOS 用户切换到 Homebrew(无需 sudo):\nbrew uninstall --cask codex\nbrew install --cask codex\n\n然后重试更新。" - )); - } - - if output.status.success() { - // 获取更新后的版本 - let run_command = |cmd: &str| -> Result { - #[cfg(target_os = "windows")] - { - Command::new("cmd") - .env("PATH", get_extended_path()) - .arg("/C") - .arg(cmd) - .creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏终端窗口 - .output() - } - - #[cfg(not(target_os = "windows"))] - { - Command::new("sh") - .env("PATH", get_extended_path()) - .arg("-c") - .arg(cmd) - .output() - } - }; - - let new_version = match tool.as_str() { - "claude-code" => { - if let Ok(output) = run_command("claude --version 2>&1") { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - extract_version(&version_output) - } else { - None - } - }, - "codex" => { - if let Ok(output) = run_command("codex --version 2>&1") { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - extract_version(&version_output) - } else { - None - } - }, - "gemini-cli" => { - if let Ok(output) = run_command("gemini --version 2>&1") { - let stdout_str = String::from_utf8_lossy(&output.stdout); - let stderr_str = String::from_utf8_lossy(&output.stderr); - let version_output = if !stdout_str.trim().is_empty() { - stdout_str.trim().to_string() - } else { - stderr_str.trim().to_string() - }; - extract_version(&version_output) - } else { - None - } - }, - _ => None, - }; - - Ok(UpdateResult { - success: true, - message: "更新成功".to_string(), - has_update: false, - current_version: new_version.clone(), - latest_version: new_version, - }) - } else { - Err(format!("更新失败: {}", stderr)) - } -} - -// 从字符串中提取版本号 -fn extract_version(text: &str) -> Option { - // 匹配类似 "1.2.3" 的版本号 - let re = regex::Regex::new(r"(\d+\.\d+\.\d+)").ok()?; - re.captures(text)?.get(1).map(|m| m.as_str().to_string()) -} - -// 比较版本号 (简单比较,返回 true 如果 latest > current) -fn compare_versions(current: &str, latest: &str) -> bool { - let current_parts: Vec = current.split('.').filter_map(|s| s.parse().ok()).collect(); - let latest_parts: Vec = latest.split('.').filter_map(|s| s.parse().ok()).collect(); - - for i in 0..3 { - let c = current_parts.get(i).copied().unwrap_or(0); - let l = latest_parts.get(i).copied().unwrap_or(0); - - if l > c { - return true; - } else if l < c { - return false; + let _ = env::set_current_dir(parent_dir); } } - false -} - -#[tauri::command] -async fn configure_api(tool: String, _provider: String, api_key: String, base_url: Option, profile_name: Option) -> Result<(), String> { - let home_dir = dirs::home_dir().ok_or("Failed to get home directory")?; - let base_url_str = base_url.unwrap_or_else(|| "https://jp.duckcoding.com".to_string()); - - match tool.as_str() { - "claude-code" => { - let config_dir = home_dir.join(".claude"); - let config_path = config_dir.join("settings.json"); - - // 确保目录存在 - fs::create_dir_all(&config_dir).map_err(|e| format!("Failed to create directory: {}", e))?; - - // 读取现有配置 - let mut config: Value = if config_path.exists() { - let content = fs::read_to_string(&config_path) - .map_err(|e| format!("Failed to read config: {}", e))?; - serde_json::from_str(&content).unwrap_or(Value::Object(Map::new())) - } else { - Value::Object(Map::new()) - }; - - // 确保有 env 对象 - if !config.is_object() { - config = Value::Object(Map::new()); - } - let config_obj = config.as_object_mut().unwrap(); - if !config_obj.contains_key("env") { - config_obj.insert("env".to_string(), Value::Object(Map::new())); - } - - // 更新 API 配置 - let env_obj = config_obj.get_mut("env").unwrap().as_object_mut().unwrap(); - env_obj.insert("ANTHROPIC_AUTH_TOKEN".to_string(), Value::String(api_key.clone())); - env_obj.insert("ANTHROPIC_BASE_URL".to_string(), Value::String(base_url_str.clone())); - - // 写入配置 - fs::write(&config_path, serde_json::to_string_pretty(&config).unwrap()) - .map_err(|e| format!("Failed to write config: {}", e))?; - - // 如果有 profile_name,保存备份 - if let Some(profile) = profile_name { - if !profile.is_empty() { - let backup_path = config_dir.join(format!("settings.{}.json", profile)); - let backup_config = serde_json::json!({ - "env": { - "ANTHROPIC_AUTH_TOKEN": api_key, - "ANTHROPIC_BASE_URL": base_url_str - } - }); - fs::write(&backup_path, serde_json::to_string_pretty(&backup_config).unwrap()) - .map_err(|e| format!("Failed to write backup: {}", e))?; - } - } - }, - "codex" => { - println!("Configuring CodeX directly in Rust (no cli.js)..."); - let config_dir = home_dir.join(".codex"); - let config_path = config_dir.join("config.toml"); - let auth_path = config_dir.join("auth.json"); - - // 确保目录存在 - fs::create_dir_all(&config_dir).map_err(|e| format!("Failed to create directory: {}", e))?; - - // 读取现有config.toml - let mut config_table: toml::map::Map = if config_path.exists() { - let content = fs::read_to_string(&config_path) - .map_err(|e| format!("Failed to read config.toml: {}", e))?; - toml::from_str(&content).unwrap_or_else(|_| toml::map::Map::new()) - } else { - toml::map::Map::new() - }; - - // 设置基本配置 - config_table.insert("model_provider".to_string(), toml::Value::String("duckcoding".to_string())); - config_table.insert("model".to_string(), toml::Value::String("gpt-5-codex".to_string())); - config_table.insert("model_reasoning_effort".to_string(), toml::Value::String("high".to_string())); - config_table.insert("network_access".to_string(), toml::Value::String("enabled".to_string())); - config_table.insert("disable_response_storage".to_string(), toml::Value::Boolean(true)); - - // 设置model_providers - let mut providers_table = toml::map::Map::new(); - let mut duckcoding_provider = toml::map::Map::new(); - duckcoding_provider.insert("name".to_string(), toml::Value::String("duckcoding".to_string())); - duckcoding_provider.insert("base_url".to_string(), toml::Value::String( - if base_url_str.ends_with("/v1") { - base_url_str.clone() - } else { - format!("{}/v1", base_url_str) - } - )); - duckcoding_provider.insert("wire_api".to_string(), toml::Value::String("responses".to_string())); - duckcoding_provider.insert("requires_openai_auth".to_string(), toml::Value::Boolean(true)); - - providers_table.insert("duckcoding".to_string(), toml::Value::Table(duckcoding_provider)); - config_table.insert("model_providers".to_string(), toml::Value::Table(providers_table)); - - // 写入config.toml - let toml_string = toml::to_string_pretty(&config_table) - .map_err(|e| format!("Failed to serialize TOML: {}", e))?; - fs::write(&config_path, toml_string) - .map_err(|e| format!("Failed to write config.toml: {}", e))?; - println!("CodeX config.toml written successfully"); - - // 写入auth.json - let auth_data = serde_json::json!({ - "OPENAI_API_KEY": api_key - }); - fs::write(&auth_path, serde_json::to_string_pretty(&auth_data).unwrap()) - .map_err(|e| format!("Failed to write auth.json: {}", e))?; - println!("CodeX auth.json written successfully"); - - // 如果有profile_name,保存备份 - if let Some(profile) = &profile_name { - if !profile.is_empty() { - println!("Saving CodeX backup for profile: {}", profile); - - // 备份config - let backup_config_path = config_dir.join(format!("config.{}.toml", profile)); - fs::write(&backup_config_path, toml::to_string_pretty(&config_table).unwrap()) - .map_err(|e| format!("Failed to write backup config: {}", e))?; - - // 备份auth - let backup_auth_path = config_dir.join(format!("auth.{}.json", profile)); - fs::write(&backup_auth_path, serde_json::to_string_pretty(&auth_data).unwrap()) - .map_err(|e| format!("Failed to write backup auth: {}", e))?; - - println!("CodeX backup saved: config.{}.toml, auth.{}.json", profile, profile); - } - } - }, - "gemini-cli" => { - let config_dir = home_dir.join(".gemini"); - let env_path = config_dir.join(".env"); - - // 确保目录存在 - fs::create_dir_all(&config_dir).map_err(|e| format!("Failed to create directory: {}", e))?; - - // 读取现有 .env 文件 - let mut env_vars = std::collections::HashMap::new(); - if env_path.exists() { - let content = fs::read_to_string(&env_path) - .map_err(|e| format!("Failed to read .env: {}", e))?; - for line in content.lines() { - let line = line.trim(); - if !line.is_empty() && !line.starts_with('#') { - if let Some((key, value)) = line.split_once('=') { - env_vars.insert(key.trim().to_string(), value.trim().to_string()); - } - } - } - } - - // 更新 API 相关的环境变量 - env_vars.insert("GOOGLE_GEMINI_BASE_URL".to_string(), base_url_str.clone()); - env_vars.insert("GEMINI_API_KEY".to_string(), api_key.clone()); - if !env_vars.contains_key("GEMINI_MODEL") { - env_vars.insert("GEMINI_MODEL".to_string(), "gemini-2.5-pro".to_string()); - } - - // 写入 .env 文件 - let env_content: String = env_vars.iter() - .map(|(k, v)| format!("{}={}", k, v)) - .collect::>() - .join("\n") + "\n"; - - fs::write(&env_path, env_content) - .map_err(|e| format!("Failed to write .env: {}", e))?; - - // 如果有 profile_name,保存备份 - if let Some(profile) = profile_name { - if !profile.is_empty() { - let backup_env_path = config_dir.join(format!(".env.{}", profile)); - let backup_content = format!( - "GOOGLE_GEMINI_BASE_URL={}\nGEMINI_API_KEY={}\nGEMINI_MODEL=gemini-2.5-pro\n", - base_url_str, api_key - ); - fs::write(&backup_env_path, backup_content) - .map_err(|e| format!("Failed to write backup .env: {}", e))?; - } - } - }, - _ => return Err(format!("Unknown tool: {}", tool)) - } - Ok(()) } -#[tauri::command] -async fn list_profiles(tool: String) -> Result, String> { - let home_dir = dirs::home_dir().ok_or("Failed to get home directory")?; - let mut profiles = Vec::new(); - - match tool.as_str() { - "claude-code" => { - let config_dir = home_dir.join(".claude"); - if !config_dir.exists() { - return Ok(profiles); - } - - // 查找 settings.{profile}.json 文件 - if let Ok(entries) = fs::read_dir(&config_dir) { - for entry in entries.flatten() { - let file_name = entry.file_name(); - let file_name_str = file_name.to_string_lossy(); - - // 匹配 settings.{profile}.json 格式 - if file_name_str.starts_with("settings.") && file_name_str.ends_with(".json") { - let profile_name = file_name_str - .strip_prefix("settings.") - .and_then(|s| s.strip_suffix(".json")); - if let Some(name) = profile_name { - profiles.push(name.to_string()); - } - } - } - } - }, - "codex" => { - let config_dir = home_dir.join(".codex"); - if !config_dir.exists() { - return Ok(profiles); - } - - // 查找 config.{profile}.toml 文件 - if let Ok(entries) = fs::read_dir(&config_dir) { - for entry in entries.flatten() { - let file_name = entry.file_name(); - let file_name_str = file_name.to_string_lossy(); - - // 匹配 config.{profile}.toml 格式 - if file_name_str.starts_with("config.") && file_name_str.ends_with(".toml") { - let profile_name = file_name_str - .strip_prefix("config.") - .and_then(|s| s.strip_suffix(".toml")); - if let Some(name) = profile_name { - profiles.push(name.to_string()); - } - } - } - } - }, - "gemini-cli" => { - let config_dir = home_dir.join(".gemini"); - if !config_dir.exists() { - return Ok(profiles); - } - - // 查找 .env.{profile} 文件 - if let Ok(entries) = fs::read_dir(&config_dir) { - for entry in entries.flatten() { - let file_name = entry.file_name(); - let file_name_str = file_name.to_string_lossy(); +fn setup_tray(app: &tauri::App) -> AppResult<()> { + let tray_menu = create_tray_menu(app.handle())?; + let app_handle = app.handle().clone(); - // 匹配 .env.{profile} 格式 - if file_name_str.starts_with(".env.") { - let profile_name = file_name_str.strip_prefix(".env."); - if let Some(name) = profile_name { - profiles.push(name.to_string()); - } - } - } - } - }, - _ => return Err(format!("Unknown tool: {}", tool)) - } - - Ok(profiles) -} - -#[tauri::command] -async fn switch_profile(tool: String, profile: String) -> Result<(), String> { - let home_dir = dirs::home_dir().ok_or("Failed to get home directory")?; - - match tool.as_str() { - "claude-code" => { - let config_dir = home_dir.join(".claude"); - let backup_path = config_dir.join(format!("settings.{}.json", profile)); - let active_path = config_dir.join("settings.json"); - - if !backup_path.exists() { - return Err(format!("Backup config not found: {:?}", backup_path)); - } - - // 读取备份配置 - let backup_content = fs::read_to_string(&backup_path) - .map_err(|e| format!("Failed to read backup config: {}", e))?; - let backup_config: Value = serde_json::from_str(&backup_content) - .map_err(|e| format!("Failed to parse backup config: {}", e))?; - - // 读取当前配置 - let mut active_config: Value = if active_path.exists() { - let content = fs::read_to_string(&active_path) - .map_err(|e| format!("Failed to read active config: {}", e))?; - serde_json::from_str(&content).unwrap_or(Value::Object(Map::new())) - } else { - Value::Object(Map::new()) - }; - - // 合并配置:只更新 API 相关字段 - if let Some(backup_env) = backup_config.get("env") { - if !active_config.is_object() { - active_config = Value::Object(Map::new()); - } - let active_obj = active_config.as_object_mut().unwrap(); - if !active_obj.contains_key("env") { - active_obj.insert("env".to_string(), Value::Object(Map::new())); - } - - let active_env = active_obj.get_mut("env").unwrap().as_object_mut().unwrap(); - if let Some(backup_env_obj) = backup_env.as_object() { - if let Some(token) = backup_env_obj.get("ANTHROPIC_AUTH_TOKEN") { - active_env.insert("ANTHROPIC_AUTH_TOKEN".to_string(), token.clone()); - } - if let Some(base_url) = backup_env_obj.get("ANTHROPIC_BASE_URL") { - active_env.insert("ANTHROPIC_BASE_URL".to_string(), base_url.clone()); - } - } - } - - // 写入配置 - fs::write(&active_path, serde_json::to_string_pretty(&active_config).unwrap()) - .map_err(|e| format!("Failed to write active config: {}", e))?; - }, - "codex" => { - let config_dir = home_dir.join(".codex"); - let backup_config_path = config_dir.join(format!("config.{}.toml", profile)); - let active_config_path = config_dir.join("config.toml"); - let backup_auth_path = config_dir.join(format!("auth.{}.json", profile)); - let active_auth_path = config_dir.join("auth.json"); - - if !backup_config_path.exists() { - return Err(format!("Backup config not found: {:?}", backup_config_path)); - } - - // 读取备份的 config.toml - let backup_config_content = fs::read_to_string(&backup_config_path) - .map_err(|e| format!("Failed to read backup config: {}", e))?; - let backup_config: toml::Value = toml::from_str(&backup_config_content) - .map_err(|e| format!("Failed to parse backup TOML: {}", e))?; - - // 读取当前的 config.toml - let mut active_config: toml::Value = if active_config_path.exists() { - let content = fs::read_to_string(&active_config_path) - .map_err(|e| format!("Failed to read active config: {}", e))?; - toml::from_str(&content).unwrap_or(toml::Value::Table(toml::map::Map::new())) - } else { - toml::Value::Table(toml::map::Map::new()) - }; - - // 合并配置:只更新必要字段 - if let toml::Value::Table(ref backup_table) = backup_config { - if let toml::Value::Table(ref mut active_table) = active_config { - // 更新顶层字段 - if let Some(provider) = backup_table.get("model_provider") { - active_table.insert("model_provider".to_string(), provider.clone()); - } - if let Some(model) = backup_table.get("model") { - active_table.insert("model".to_string(), model.clone()); - } - if let Some(effort) = backup_table.get("model_reasoning_effort") { - active_table.insert("model_reasoning_effort".to_string(), effort.clone()); - } - if let Some(network) = backup_table.get("network_access") { - active_table.insert("network_access".to_string(), network.clone()); - } - if let Some(storage) = backup_table.get("disable_response_storage") { - active_table.insert("disable_response_storage".to_string(), storage.clone()); - } - - // 更新 model_providers - if let Some(backup_providers) = backup_table.get("model_providers") { - if !active_table.contains_key("model_providers") { - active_table.insert("model_providers".to_string(), toml::Value::Table(toml::map::Map::new())); - } - if let Some(toml::Value::Table(active_providers)) = active_table.get_mut("model_providers") { - if let toml::Value::Table(bp) = backup_providers { - for (key, value) in bp { - active_providers.insert(key.clone(), value.clone()); - } - } - } - } - } - } - - // 写入 config.toml - let toml_string = toml::to_string_pretty(&active_config) - .map_err(|e| format!("Failed to serialize TOML: {}", e))?; - fs::write(&active_config_path, toml_string) - .map_err(|e| format!("Failed to write active config: {}", e))?; - - // 更新 auth.json - if backup_auth_path.exists() { - let backup_auth_content = fs::read_to_string(&backup_auth_path) - .map_err(|e| format!("Failed to read backup auth: {}", e))?; - let backup_auth: Value = serde_json::from_str(&backup_auth_content) - .map_err(|e| format!("Failed to parse backup auth: {}", e))?; - - let mut active_auth: Value = if active_auth_path.exists() { - let content = fs::read_to_string(&active_auth_path) - .map_err(|e| format!("Failed to read active auth: {}", e))?; - serde_json::from_str(&content).unwrap_or(Value::Object(Map::new())) - } else { - Value::Object(Map::new()) - }; - - if let Some(backup_key) = backup_auth.get("OPENAI_API_KEY") { - if let Value::Object(ref mut active_obj) = active_auth { - active_obj.insert("OPENAI_API_KEY".to_string(), backup_key.clone()); - } - } - - fs::write(&active_auth_path, serde_json::to_string_pretty(&active_auth).unwrap()) - .map_err(|e| format!("Failed to write active auth: {}", e))?; - } - }, - "gemini-cli" => { - let config_dir = home_dir.join(".gemini"); - let backup_env_path = config_dir.join(format!(".env.{}", profile)); - let active_env_path = config_dir.join(".env"); - - if !backup_env_path.exists() { - return Err(format!("Backup .env not found: {:?}", backup_env_path)); - } - - // 读取备份的环境变量 - let backup_content = fs::read_to_string(&backup_env_path) - .map_err(|e| format!("Failed to read backup .env: {}", e))?; - let mut backup_env = std::collections::HashMap::new(); - for line in backup_content.lines() { - let line = line.trim(); - if !line.is_empty() && !line.starts_with('#') { - if let Some((key, value)) = line.split_once('=') { - backup_env.insert(key.trim().to_string(), value.trim().to_string()); - } - } - } - - // 读取当前的环境变量 - let mut active_env = std::collections::HashMap::new(); - if active_env_path.exists() { - let content = fs::read_to_string(&active_env_path) - .map_err(|e| format!("Failed to read active .env: {}", e))?; - for line in content.lines() { - let line = line.trim(); - if !line.is_empty() && !line.starts_with('#') { - if let Some((key, value)) = line.split_once('=') { - active_env.insert(key.trim().to_string(), value.trim().to_string()); - } - } - } - } - - // 合并:只更新 API 相关字段 - if let Some(base_url) = backup_env.get("GOOGLE_GEMINI_BASE_URL") { - active_env.insert("GOOGLE_GEMINI_BASE_URL".to_string(), base_url.clone()); - } - if let Some(api_key) = backup_env.get("GEMINI_API_KEY") { - active_env.insert("GEMINI_API_KEY".to_string(), api_key.clone()); - } - if let Some(model) = backup_env.get("GEMINI_MODEL") { - active_env.insert("GEMINI_MODEL".to_string(), model.clone()); - } - - // 写回 .env - let env_content: String = active_env.iter() - .map(|(k, v)| format!("{}={}", k, v)) - .collect::>() - .join("\n") + "\n"; - - fs::write(&active_env_path, env_content) - .map_err(|e| format!("Failed to write active .env: {}", e))?; - }, - _ => return Err(format!("Unknown tool: {}", tool)) - } - - Ok(()) -} - -#[tauri::command] -async fn delete_profile(tool: String, profile: String) -> Result<(), String> { - println!("Deleting profile: tool={}, profile={}", tool, profile); - let home_dir = dirs::home_dir().ok_or("Failed to get home directory")?; - - match tool.as_str() { - "claude-code" => { - let config_dir = home_dir.join(".claude"); - let backup_path = config_dir.join(format!("settings.{}.json", profile)); - println!("Claude Code backup path: {:?}", backup_path); - - if !backup_path.exists() { - let err = format!("配置文件不存在: {}", profile); - println!("Error: {}", err); - return Err(err); - } - - fs::remove_file(&backup_path) - .map_err(|e| { - let err = format!("删除配置失败: {}", e); - println!("Error: {}", err); - err - })?; - println!("Successfully deleted Claude Code profile: {}", profile); - }, - "codex" => { - let config_dir = home_dir.join(".codex"); - let backup_config_path = config_dir.join(format!("config.{}.toml", profile)); - let backup_auth_path = config_dir.join(format!("auth.{}.json", profile)); - println!("CodeX config path: {:?}", backup_config_path); - println!("CodeX auth path: {:?}", backup_auth_path); - - if !backup_config_path.exists() { - let err = format!("配置文件不存在: {}", profile); - println!("Error: {}", err); - return Err(err); - } - - fs::remove_file(&backup_config_path) - .map_err(|e| { - let err = format!("删除配置失败: {}", e); - println!("Error: {}", err); - err - })?; - println!("Deleted config.toml for profile: {}", profile); - - if backup_auth_path.exists() { - fs::remove_file(&backup_auth_path) - .map_err(|e| { - let err = format!("删除认证文件失败: {}", e); - println!("Error: {}", err); - err - })?; - println!("Deleted auth.json for profile: {}", profile); - } - println!("Successfully deleted CodeX profile: {}", profile); - }, - "gemini-cli" => { - let config_dir = home_dir.join(".gemini"); - let backup_env_path = config_dir.join(format!(".env.{}", profile)); - let backup_settings_path = config_dir.join(format!("settings.{}.json", profile)); - - if !backup_env_path.exists() { - return Err(format!("配置文件不存在: {}", profile)); - } - - fs::remove_file(&backup_env_path) - .map_err(|e| format!("删除配置失败: {}", e))?; + TrayIconBuilder::new() + .icon(app.default_window_icon().unwrap().clone()) + .menu(&tray_menu) + .show_menu_on_left_click(false) + .on_menu_event(move |app, event| match event.id.as_ref() { + "show" => show_main_window(app), + "quit" => app.exit(0), + _ => {} + }) + .on_tray_icon_event(move |_tray, event| match event { + TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } => show_main_window(&app_handle), + _ => {} + }) + .build(app)?; - if backup_settings_path.exists() { - fs::remove_file(&backup_settings_path) - .map_err(|e| format!("删除设置文件失败: {}", e))?; + if let Some(window) = app.get_webview_window("main") { + let window_clone = window.clone(); + window.on_window_event(move |event| { + if let tauri::WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + let _ = window_clone.hide(); } - }, - _ => return Err(format!("Unknown tool: {}", tool)) - } - - Ok(()) -} - -// 数据结构定义 -#[derive(serde::Serialize, serde::Deserialize, Clone)] -struct ToolStatus { - id: String, - name: String, - installed: bool, - version: Option, -} - -#[derive(serde::Serialize, serde::Deserialize)] -struct NodeEnvironment { - node_available: bool, - node_version: Option, - npm_available: bool, - npm_version: Option, -} - -#[derive(serde::Serialize, serde::Deserialize)] -struct InstallResult { - success: bool, - message: String, - output: String, -} - -#[derive(serde::Serialize, serde::Deserialize)] -struct UpdateResult { - success: bool, - message: String, - has_update: bool, - current_version: Option, - latest_version: Option, -} - -#[derive(serde::Serialize, serde::Deserialize)] -struct ActiveConfig { - api_key: String, - base_url: String, - profile_name: Option, // 当前配置的名称 -} - -// 全局配置结构 -#[derive(Serialize, Deserialize, Clone)] -struct GlobalConfig { - user_id: String, - system_token: String, -} - -// DuckCoding API 响应结构 -#[derive(Deserialize, Debug)] -struct TokenData { - id: i64, - key: String, - #[allow(dead_code)] - name: String, - #[allow(dead_code)] - group: String, -} - -#[derive(Deserialize, Debug)] -struct ApiResponse { - success: bool, - message: String, - data: Option>, -} - -#[derive(Serialize)] -struct GenerateApiKeyResult { - success: bool, - message: String, - api_key: Option, -} - -// 用量统计数据结构 -#[derive(Deserialize, Serialize, Debug, Clone)] -struct UsageData { - id: i64, - user_id: i64, - username: String, - model_name: String, - created_at: i64, - token_used: i64, - count: i64, - quota: i64, -} - -#[derive(Deserialize, Debug)] -struct UsageApiResponse { - success: bool, - message: String, - data: Option>, -} - -#[derive(Serialize)] -struct UsageStatsResult { - success: bool, - message: String, - data: Vec, -} - -// 用户信息数据结构 -#[derive(Deserialize, Serialize, Debug)] -struct UserInfo { - id: i64, - username: String, - quota: i64, - used_quota: i64, - request_count: i64, -} - -#[derive(Deserialize, Debug)] -struct UserApiResponse { - success: bool, - message: String, - data: Option, -} - -#[derive(Serialize)] -struct UserQuotaResult { - success: bool, - message: String, - total_quota: f64, - used_quota: f64, - remaining_quota: f64, - request_count: i64, -} - -// 全局配置辅助函数 -fn get_global_config_path() -> Result { - let home_dir = dirs::home_dir().ok_or("Failed to get home directory")?; - let config_dir = home_dir.join(".duckcoding"); - if !config_dir.exists() { - fs::create_dir_all(&config_dir) - .map_err(|e| format!("Failed to create config directory: {}", e))?; - } - Ok(config_dir.join("config.json")) -} - -// Tauri命令:保存全局配置 -#[tauri::command] -async fn save_global_config(user_id: String, system_token: String) -> Result<(), String> { - println!("save_global_config called with user_id: {}", user_id); - - let config = GlobalConfig { user_id, system_token }; - let config_path = get_global_config_path()?; - - println!("Config path: {:?}", config_path); - - let json = serde_json::to_string_pretty(&config) - .map_err(|e| format!("Failed to serialize config: {}", e))?; - - fs::write(&config_path, json) - .map_err(|e| format!("Failed to write config: {}", e))?; - - println!("Config saved successfully"); - - // 设置文件权限为仅所有者可读写(Unix系统) - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let metadata = fs::metadata(&config_path) - .map_err(|e| format!("Failed to get file metadata: {}", e))?; - let mut perms = metadata.permissions(); - perms.set_mode(0o600); // -rw------- - fs::set_permissions(&config_path, perms) - .map_err(|e| format!("Failed to set file permissions: {}", e))?; - } - - Ok(()) -} - -// Tauri命令:读取全局配置 -#[tauri::command] -async fn get_global_config() -> Result, String> { - let config_path = get_global_config_path()?; - - if !config_path.exists() { - return Ok(None); - } - - let content = fs::read_to_string(&config_path) - .map_err(|e| format!("Failed to read config: {}", e))?; - - let config: GlobalConfig = serde_json::from_str(&content) - .map_err(|e| format!("Failed to parse config: {}", e))?; - - Ok(Some(config)) -} - -// 生成API Key的主函数 -#[tauri::command] -async fn generate_api_key_for_tool(tool: String) -> Result { - // 读取全局配置 - let global_config = get_global_config().await? - .ok_or("请先配置用户ID和系统访问令牌")?; - - // 根据工具名称获取配置 - let (name, group) = match tool.as_str() { - "claude-code" => ("Claude Code一键创建", "Claude Code专用"), - "codex" => ("CodeX一键创建", "CodeX专用"), - "gemini-cli" => ("Gemini CLI一键创建", "Gemini CLI专用"), - _ => return Err(format!("Unknown tool: {}", tool)), - }; - - // 创建token - let client = reqwest::Client::new(); - let create_url = "https://duckcoding.com/api/token"; - - let create_body = serde_json::json!({ - "remain_quota": 500000, - "expired_time": -1, - "unlimited_quota": true, - "model_limits_enabled": false, - "model_limits": "", - "name": name, - "group": group, - "allow_ips": "" - }); - - let create_response = client - .post(create_url) - .header("Authorization", format!("Bearer {}", global_config.system_token)) - .header("New-Api-User", &global_config.user_id) - .header("Content-Type", "application/json") - .json(&create_body) - .send() - .await - .map_err(|e| format!("创建token失败: {}", e))?; - - if !create_response.status().is_success() { - let status = create_response.status(); - let error_text = create_response.text().await.unwrap_or_default(); - return Ok(GenerateApiKeyResult { - success: false, - message: format!("创建token失败 ({}): {}", status, error_text), - api_key: None, - }); - } - - // 等待一小段时间让服务器处理 - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - - // 搜索刚创建的token - let search_url = format!("https://duckcoding.com/api/token/search?keyword={}", - urlencoding::encode(name)); - - let search_response = client - .get(&search_url) - .header("Authorization", format!("Bearer {}", global_config.system_token)) - .header("New-Api-User", &global_config.user_id) - .header("Content-Type", "application/json") - .send() - .await - .map_err(|e| format!("搜索token失败: {}", e))?; - - if !search_response.status().is_success() { - return Ok(GenerateApiKeyResult { - success: false, - message: "创建成功但获取API Key失败,请稍后在DuckCoding控制台查看".to_string(), - api_key: None, - }); - } - - let api_response: ApiResponse = search_response - .json() - .await - .map_err(|e| format!("解析响应失败: {}", e))?; - - if !api_response.success { - return Ok(GenerateApiKeyResult { - success: false, - message: format!("API返回错误: {}", api_response.message), - api_key: None, - }); - } - - // 获取id最大的token(最新创建的) - if let Some(mut data) = api_response.data { - if !data.is_empty() { - // 按id降序排序,取第一个(id最大的) - data.sort_by(|a, b| b.id.cmp(&a.id)); - let token = &data[0]; - let api_key = format!("sk-{}", token.key); - return Ok(GenerateApiKeyResult { - success: true, - message: "API Key生成成功".to_string(), - api_key: Some(api_key), - }); - } - } - - Ok(GenerateApiKeyResult { - success: false, - message: "未找到生成的token".to_string(), - api_key: None, - }) -} - -// 获取用户用量统计(近30天) -#[tauri::command] -async fn get_usage_stats() -> Result { - // 读取全局配置 - let global_config = get_global_config().await? - .ok_or("请先配置用户ID和系统访问令牌")?; - - // 计算时间戳(北京时间) - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() as i64; - - // 今天的24:00:00(加上8小时时区偏移,然后取第二天的0点) - let beijing_offset = 8 * 3600; - let today_end = (now + beijing_offset) / 86400 * 86400 + 86400 - beijing_offset; - - // 30天前的00:00:00 - let start_timestamp = today_end - 30 * 86400; - let end_timestamp = today_end; - - // 调用API - let client = reqwest::Client::new(); - let url = format!( - "https://duckcoding.com/api/data/self?start_timestamp={}&end_timestamp={}", - start_timestamp, end_timestamp - ); - - let response = client - .get(&url) - .header("Authorization", format!("Bearer {}", global_config.system_token)) - .header("New-Api-User", &global_config.user_id) - .header("Content-Type", "application/json") - .send() - .await - .map_err(|e| format!("获取用量统计失败: {}", e))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Ok(UsageStatsResult { - success: false, - message: format!("获取用量统计失败 ({}): {}", status, error_text), - data: vec![], - }); - } - - let api_response: UsageApiResponse = response - .json() - .await - .map_err(|e| format!("解析响应失败: {}", e))?; - - if !api_response.success { - return Ok(UsageStatsResult { - success: false, - message: format!("API返回错误: {}", api_response.message), - data: vec![], }); } - Ok(UsageStatsResult { - success: true, - message: "获取成功".to_string(), - data: api_response.data.unwrap_or_default(), - }) -} - -// 获取用户额度信息 -#[tauri::command] -async fn get_user_quota() -> Result { - // 读取全局配置 - let global_config = get_global_config().await? - .ok_or("请先配置用户ID和系统访问令牌")?; - - // 调用API - let client = reqwest::Client::new(); - let url = "https://duckcoding.com/api/user/self"; - - let response = client - .get(url) - .header("Authorization", format!("Bearer {}", global_config.system_token)) - .header("New-Api-User", &global_config.user_id) - .header("Content-Type", "application/json") - .send() - .await - .map_err(|e| format!("获取用户信息失败: {}", e))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(format!("获取用户信息失败 ({}): {}", status, error_text)); - } - - let api_response: UserApiResponse = response - .json() - .await - .map_err(|e| format!("解析响应失败: {}", e))?; - - if !api_response.success { - return Err(format!("API返回错误: {}", api_response.message)); - } - - let user_info = api_response.data.ok_or("未获取到用户信息")?; - - // 修正:API返回的quota是剩余额度,不是总额度 - // 正确计算:总额度 = 剩余额度 + 已用额度 - let remaining_quota = user_info.quota as f64 / 500000.0; - let used_quota = user_info.used_quota as f64 / 500000.0; - let total_quota = remaining_quota + used_quota; - - #[cfg(debug_assertions)] - { - println!("Raw remaining: {}, converted: {}", user_info.quota, remaining_quota); - println!("Raw used: {}, converted: {}", user_info.used_quota, used_quota); - println!("Total quota: {}", total_quota); - } - - Ok(UserQuotaResult { - success: true, - message: "获取成功".to_string(), - total_quota, - used_quota, - remaining_quota, - request_count: user_info.request_count, - }) -} - -// 辅助函数:检测当前配置匹配哪个profile -fn detect_profile_name(tool: &str, active_api_key: &str, active_base_url: &str, home_dir: &std::path::Path) -> Option { - let config_dir = match tool { - "claude-code" => home_dir.join(".claude"), - "codex" => home_dir.join(".codex"), - "gemini-cli" => home_dir.join(".gemini"), - _ => return None, - }; - - if !config_dir.exists() { - return None; - } - - // 遍历配置目录,查找匹配的备份文件 - if let Ok(entries) = fs::read_dir(&config_dir) { - for entry in entries.flatten() { - let file_name = entry.file_name(); - let file_name_str = file_name.to_string_lossy(); - - // 根据工具类型匹配不同的备份文件格式 - let profile_name = match tool { - "claude-code" => { - // 匹配 settings.{profile}.json - if file_name_str.starts_with("settings.") && file_name_str.ends_with(".json") && file_name_str != "settings.json" { - file_name_str.strip_prefix("settings.").and_then(|s| s.strip_suffix(".json")) - } else { - None - } - }, - "codex" => { - // 匹配 config.{profile}.toml - if file_name_str.starts_with("config.") && file_name_str.ends_with(".toml") && file_name_str != "config.toml" { - file_name_str.strip_prefix("config.").and_then(|s| s.strip_suffix(".toml")) - } else { - None - } - }, - "gemini-cli" => { - // 匹配 .env.{profile} - if file_name_str.starts_with(".env.") && file_name_str != ".env" { - file_name_str.strip_prefix(".env.") - } else { - None - } - }, - _ => None, - }; - - if let Some(profile) = profile_name { - // 读取备份文件并比较内容 - let is_match = match tool { - "claude-code" => { - if let Ok(content) = fs::read_to_string(entry.path()) { - if let Ok(config) = serde_json::from_str::(&content) { - let backup_api_key = config.get("env") - .and_then(|env| env.get("ANTHROPIC_AUTH_TOKEN")) - .and_then(|v| v.as_str()) - .unwrap_or(""); - let backup_base_url = config.get("env") - .and_then(|env| env.get("ANTHROPIC_BASE_URL")) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - backup_api_key == active_api_key && backup_base_url == active_base_url - } else { - false - } - } else { - false - } - }, - "codex" => { - // 需要同时检查 config.toml 和 auth.json - let auth_backup = config_dir.join(format!("auth.{}.json", profile)); - - let mut api_key_matches = false; - if let Ok(auth_content) = fs::read_to_string(&auth_backup) { - if let Ok(auth) = serde_json::from_str::(&auth_content) { - let backup_api_key = auth.get("OPENAI_API_KEY") - .and_then(|v| v.as_str()) - .unwrap_or(""); - - api_key_matches = backup_api_key == active_api_key; - } - } - - if !api_key_matches { - false - } else { - // API Key 匹配,继续检查 base_url - if let Ok(config_content) = fs::read_to_string(entry.path()) { - if let Ok(config) = toml::from_str::(&config_content) { - if let toml::Value::Table(table) = config { - if let Some(toml::Value::Table(providers)) = table.get("model_providers") { - let mut url_matches = false; - for (_, provider) in providers { - if let toml::Value::Table(p) = provider { - if let Some(toml::Value::String(url)) = p.get("base_url") { - if url == active_base_url { - url_matches = true; - break; - } - } - } - } - url_matches - } else { - false - } - } else { - false - } - } else { - false - } - } else { - false - } - } - }, - "gemini-cli" => { - if let Ok(content) = fs::read_to_string(entry.path()) { - let mut backup_api_key = ""; - let mut backup_base_url = ""; - - for line in content.lines() { - let line = line.trim(); - if line.is_empty() || line.starts_with('#') { - continue; - } - - if let Some((key, value)) = line.split_once('=') { - match key.trim() { - "GEMINI_API_KEY" => backup_api_key = value.trim(), - "GOOGLE_GEMINI_BASE_URL" => backup_base_url = value.trim(), - _ => {} - } - } - } - - backup_api_key == active_api_key && backup_base_url == active_base_url - } else { - false - } - }, - _ => false, - }; - - if is_match { - return Some(profile.to_string()); - } - } - } - } - - None -} - -#[tauri::command] -async fn get_active_config(tool: String) -> Result { - let home_dir = dirs::home_dir().ok_or("Failed to get home directory")?; - - match tool.as_str() { - "claude-code" => { - let config_path = home_dir.join(".claude").join("settings.json"); - if !config_path.exists() { - return Ok(ActiveConfig { - api_key: "未配置".to_string(), - base_url: "未配置".to_string(), - profile_name: None, - }); - } - - let content = fs::read_to_string(&config_path) - .map_err(|e| format!("Failed to read config: {}", e))?; - let config: Value = serde_json::from_str(&content) - .map_err(|e| format!("Failed to parse config: {}", e))?; - - let raw_api_key = config.get("env") - .and_then(|env| env.get("ANTHROPIC_AUTH_TOKEN")) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - let api_key = if raw_api_key.is_empty() { - "未配置".to_string() - } else { - mask_api_key(raw_api_key) - }; - - let base_url = config.get("env") - .and_then(|env| env.get("ANTHROPIC_BASE_URL")) - .and_then(|v| v.as_str()) - .unwrap_or("未配置"); - - // 检测配置名称 - let profile_name = if !raw_api_key.is_empty() && base_url != "未配置" { - detect_profile_name("claude-code", raw_api_key, base_url, &home_dir) - } else { - None - }; - - Ok(ActiveConfig { - api_key, - base_url: base_url.to_string(), - profile_name, - }) - }, - "codex" => { - let auth_path = home_dir.join(".codex").join("auth.json"); - let config_path = home_dir.join(".codex").join("config.toml"); - - let mut raw_api_key = String::new(); - let mut api_key = "未配置".to_string(); - let mut base_url = "未配置".to_string(); - - // 读取 auth.json - if auth_path.exists() { - let content = fs::read_to_string(&auth_path) - .map_err(|e| format!("Failed to read auth: {}", e))?; - let auth: Value = serde_json::from_str(&content) - .map_err(|e| format!("Failed to parse auth: {}", e))?; - - if let Some(key) = auth.get("OPENAI_API_KEY").and_then(|v| v.as_str()) { - raw_api_key = key.to_string(); - api_key = mask_api_key(key); - } - } - - // 读取 config.toml - if config_path.exists() { - let content = fs::read_to_string(&config_path) - .map_err(|e| format!("Failed to read config: {}", e))?; - let config: toml::Value = toml::from_str(&content) - .map_err(|e| format!("Failed to parse TOML: {}", e))?; - - if let toml::Value::Table(table) = config { - if let Some(toml::Value::Table(providers)) = table.get("model_providers") { - // 尝试获取 duckcoding 或 custom provider 的 base_url - for (_, provider) in providers { - if let toml::Value::Table(p) = provider { - if let Some(toml::Value::String(url)) = p.get("base_url") { - base_url = url.clone(); - break; - } - } - } - } - } - } - - // 检测配置名称 - let profile_name = if !raw_api_key.is_empty() && base_url != "未配置" { - detect_profile_name("codex", &raw_api_key, &base_url, &home_dir) - } else { - None - }; - - Ok(ActiveConfig { api_key, base_url, profile_name }) - }, - "gemini-cli" => { - let env_path = home_dir.join(".gemini").join(".env"); - if !env_path.exists() { - return Ok(ActiveConfig { - api_key: "未配置".to_string(), - base_url: "未配置".to_string(), - profile_name: None, - }); - } - - let content = fs::read_to_string(&env_path) - .map_err(|e| format!("Failed to read .env: {}", e))?; - - let mut raw_api_key = String::new(); - let mut api_key = "未配置".to_string(); - let mut base_url = "未配置".to_string(); - - for line in content.lines() { - let line = line.trim(); - if line.is_empty() || line.starts_with('#') { - continue; - } - - if let Some((key, value)) = line.split_once('=') { - match key.trim() { - "GEMINI_API_KEY" => { - raw_api_key = value.trim().to_string(); - api_key = mask_api_key(value.trim()); - }, - "GOOGLE_GEMINI_BASE_URL" => base_url = value.trim().to_string(), - _ => {} - } - } - } - - // 检测配置名称 - let profile_name = if !raw_api_key.is_empty() && base_url != "未配置" { - detect_profile_name("gemini-cli", &raw_api_key, &base_url, &home_dir) - } else { - None - }; - - Ok(ActiveConfig { api_key, base_url, profile_name }) - }, - _ => Err(format!("Unknown tool: {}", tool)) - } -} - -fn mask_api_key(key: &str) -> String { - if key.len() <= 8 { - return "****".to_string(); - } - let prefix = &key[..4]; - let suffix = &key[key.len() - 4..]; - format!("{}...{}", prefix, suffix) + Ok(()) } fn create_tray_menu(app: &AppHandle) -> tauri::Result> { let show_item = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?; let quit_item = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?; - let menu = Menu::with_items( + Menu::with_items( app, - &[ - &show_item, - &PredefinedMenuItem::separator(app)?, - &quit_item, - ], - )?; - - Ok(menu) + &[&show_item, &PredefinedMenuItem::separator(app)?, &quit_item], + ) } +fn show_main_window(app: &AppHandle) { + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.unminimize(); + let _ = window.set_focus(); -fn main() { - tauri::Builder::default() - .setup(|app| { - // 设置工作目录到项目根目录(跨平台支持) - if let Ok(resource_dir) = app.path().resource_dir() { - println!("Resource dir: {:?}", resource_dir); - - if cfg!(debug_assertions) { - // 开发模式:resource_dir 是 src-tauri/target/debug - // 需要回到项目根目录(上三级) - let project_root = resource_dir - .parent() // target - .and_then(|p| p.parent()) // src-tauri - .and_then(|p| p.parent()) // 项目根目录 - .unwrap_or(&resource_dir); - - println!("Development mode, setting dir to: {:?}", project_root); - let _ = env::set_current_dir(project_root); - } else { - // 生产模式:跨平台支持 - let parent_dir = if cfg!(target_os = "macos") { - // macOS: .app/Contents/Resources/ - resource_dir.parent().and_then(|p| p.parent()).unwrap_or(&resource_dir) - } else if cfg!(target_os = "windows") { - // Windows: 通常在应用程序目录 - resource_dir.parent().unwrap_or(&resource_dir) - } else { - // Linux: 通常在 /usr/share/appname 或类似位置 - resource_dir.parent().unwrap_or(&resource_dir) - }; - println!("Production mode, setting dir to: {:?}", parent_dir); - let _ = env::set_current_dir(parent_dir); - } - } - - println!("Working directory: {:?}", env::current_dir()); - - // 创建系统托盘菜单 - let tray_menu = create_tray_menu(app.handle())?; - let app_handle2 = app.handle().clone(); - - let _tray = TrayIconBuilder::new() - .icon(app.default_window_icon().unwrap().clone()) - .menu(&tray_menu) - .show_menu_on_left_click(false) - .on_menu_event(move |app, event| { - println!("Tray menu event: {:?}", event.id); - match event.id.as_ref() { - "show" => { - println!("Show window requested from tray menu"); - if let Some(window) = app.get_webview_window("main") { - println!("Window is_visible: {:?}", window.is_visible()); - println!("Window is_minimized: {:?}", window.is_minimized()); - - // 显示并激活窗口 - if let Err(e) = window.show() { - println!("Error showing window: {:?}", e); - } - if let Err(e) = window.unminimize() { - println!("Error unminimizing window: {:?}", e); - } - if let Err(e) = window.set_focus() { - println!("Error setting focus: {:?}", e); - } - - // macOS: 强制激活应用到前台 - #[cfg(target_os = "macos")] - { - use cocoa::appkit::NSApplication; - use cocoa::base::nil; - use objc::runtime::YES; - - unsafe { - let ns_app = NSApplication::sharedApplication(nil); - ns_app.activateIgnoringOtherApps_(YES); - } - println!("macOS app activated"); - } - - println!("After show - is_visible: {:?}", window.is_visible()); - } else { - println!("Window not found!"); - } - } - "quit" => { - println!("Quit requested from tray menu"); - app.exit(0); - } - _ => {} - } - }) - .on_tray_icon_event(move |_tray, event| { - println!("Tray icon event received: {:?}", event); - match event { - TrayIconEvent::Click { - button: MouseButton::Left, - button_state: MouseButtonState::Up, - .. - } => { - println!("Tray icon LEFT click detected"); - // 单击左键显示主窗口 - if let Some(window) = app_handle2.get_webview_window("main") { - println!("Window found, is_visible: {:?}", window.is_visible()); - println!("Window is_minimized: {:?}", window.is_minimized()); - - // 显示并激活窗口 - if let Err(e) = window.show() { - println!("Error showing window: {:?}", e); - } - if let Err(e) = window.unminimize() { - println!("Error unminimizing window: {:?}", e); - } - if let Err(e) = window.set_focus() { - println!("Error setting focus: {:?}", e); - } - - // macOS: 强制激活应用到前台 - #[cfg(target_os = "macos")] - { - use cocoa::appkit::NSApplication; - use cocoa::base::nil; - use objc::runtime::YES; - - unsafe { - let ns_app = NSApplication::sharedApplication(nil); - ns_app.activateIgnoringOtherApps_(YES); - } - println!("macOS app activated"); - } - - println!("After show - is_visible: {:?}", window.is_visible()); - } else { - println!("Window not found from tray click!"); - } - } - _ => { - // 不打印太多日志 - } - } - }) - .build(app)?; + #[cfg(target_os = "macos")] + { + use cocoa::appkit::NSApplication; + use cocoa::base::nil; + use objc::runtime::YES; - // 处理窗口关闭事件 - 最小化到托盘而不是退出 - if let Some(window) = app.get_webview_window("main") { - let window_clone = window.clone(); - window.on_window_event(move |event| { - if let tauri::WindowEvent::CloseRequested { api, .. } = event { - println!("Window close requested - hiding to tray"); - // 阻止默认关闭行为 - api.prevent_close(); - // 隐藏窗口到托盘 - let _ = window_clone.hide(); - println!("Window hidden"); - } - }); + unsafe { + let ns_app = NSApplication::sharedApplication(nil); + ns_app.activateIgnoringOtherApps_(YES); } - - Ok(()) - }) - .plugin(tauri_plugin_shell::init()) - .invoke_handler(tauri::generate_handler![ - check_installations, - check_node_environment, - install_tool, - check_update, - update_tool, - configure_api, - list_profiles, - switch_profile, - delete_profile, - get_active_config, - save_global_config, - get_global_config, - generate_api_key_for_tool, - get_usage_stats, - get_user_quota - ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + } + } } diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs new file mode 100644 index 0000000..4e24bf2 --- /dev/null +++ b/src-tauri/src/models.rs @@ -0,0 +1,133 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Debug)] +pub struct NpmPackageInfo { + #[serde(rename = "dist-tags")] + pub dist_tags: NpmDistTags, +} + +#[derive(Deserialize, Debug)] +pub struct NpmDistTags { + pub latest: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ToolStatus { + pub id: String, + pub name: String, + pub installed: bool, + pub version: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct NodeEnvironment { + pub node_available: bool, + pub node_version: Option, + pub npm_available: bool, + pub npm_version: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct InstallResult { + pub success: bool, + pub message: String, + pub output: String, +} + +#[derive(Serialize, Deserialize)] +pub struct UpdateResult { + pub success: bool, + pub message: String, + pub has_update: bool, + pub current_version: Option, + pub latest_version: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct ActiveConfig { + pub api_key: String, + pub base_url: String, + pub profile_name: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct GlobalConfig { + pub user_id: String, + pub system_token: String, +} + +#[derive(Deserialize, Debug)] +pub struct TokenData { + pub id: i64, + pub key: String, + #[allow(dead_code)] + pub name: String, + #[allow(dead_code)] + pub group: String, +} + +#[derive(Deserialize, Debug)] +pub struct ApiResponse { + pub success: bool, + pub message: String, + pub data: Option>, +} + +#[derive(Serialize)] +pub struct GenerateApiKeyResult { + pub success: bool, + pub message: String, + pub api_key: Option, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct UsageData { + pub id: i64, + pub user_id: i64, + pub username: String, + pub model_name: String, + pub created_at: i64, + pub token_used: i64, + pub count: i64, + pub quota: i64, +} + +#[derive(Deserialize, Debug)] +pub struct UsageApiResponse { + pub success: bool, + pub message: String, + pub data: Option>, +} + +#[derive(Serialize)] +pub struct UsageStatsResult { + pub success: bool, + pub message: String, + pub data: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct UserInfo { + pub id: i64, + pub username: String, + pub quota: i64, + pub used_quota: i64, + pub request_count: i64, +} + +#[derive(Deserialize, Debug)] +pub struct UserApiResponse { + pub success: bool, + pub message: String, + pub data: Option, +} + +#[derive(Serialize)] +pub struct UserQuotaResult { + pub success: bool, + pub message: String, + pub total_quota: f64, + pub used_quota: f64, + pub remaining_quota: f64, + pub request_count: i64, +} diff --git a/src-tauri/src/services/backup.rs b/src-tauri/src/services/backup.rs new file mode 100644 index 0000000..3bc199c --- /dev/null +++ b/src-tauri/src/services/backup.rs @@ -0,0 +1,58 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::error::AppResult; + +pub fn backup_json(path: &Path) -> AppResult> { + if !path.exists() { + return Ok(None); + } + + create_backup_with_validator(path, |backup| { + let data = fs::read(backup)?; + let _: serde_json::Value = serde_json::from_slice(&data)?; + Ok(()) + }) + .map(Some) +} + +pub fn backup_toml(path: &Path) -> AppResult> { + if !path.exists() { + return Ok(None); + } + + create_backup_with_validator(path, |backup| { + let content = fs::read_to_string(backup)?; + let _: toml_edit::DocumentMut = content.parse()?; + Ok(()) + }) + .map(Some) +} + +fn create_backup_with_validator(source: &Path, validator: F) -> AppResult +where + F: Fn(&Path) -> AppResult<()>, +{ + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + + let file_name = source + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("config"); + + let backup_name = format!("{}.{}.bak", file_name, timestamp); + let backup_path = source.with_file_name(backup_name); + + if let Some(parent) = backup_path.parent() { + fs::create_dir_all(parent)?; + } + + fs::copy(source, &backup_path)?; + validator(&backup_path)?; + + Ok(backup_path) +} diff --git a/src-tauri/src/services/config_store.rs b/src-tauri/src/services/config_store.rs new file mode 100644 index 0000000..d1a79d0 --- /dev/null +++ b/src-tauri/src/services/config_store.rs @@ -0,0 +1,140 @@ +use std::fs; +use std::path::PathBuf; + +use serde_json::{Map, Value}; +use toml_edit::DocumentMut; + +use crate::error::{AppError, AppResult}; + +use super::{backup_json, backup_toml}; + +pub struct JsonStore { + path: PathBuf, +} + +impl JsonStore { + pub fn new(path: impl Into) -> Self { + Self { path: path.into() } + } + + pub fn update(&self, mutator: F) -> AppResult + where + F: FnOnce(&mut Value) -> AppResult<()>, + { + let mut doc = self.read()?; + + if self.path.exists() { + backup_json(&self.path)?; + } + + mutator(&mut doc)?; + self.write(&doc)?; + Ok(doc) + } + + pub fn read(&self) -> AppResult { + if !self.path.exists() { + return Ok(Value::Object(Map::new())); + } + + let content = fs::read_to_string(&self.path)?; + let value = if content.trim().is_empty() { + Value::Object(Map::new()) + } else { + serde_json::from_str(&content)? + }; + + Ok(value) + } + + pub fn write(&self, value: &Value) -> AppResult<()> { + self.ensure_parent()?; + let tmp_path = self.tmp_path(); + let content = serde_json::to_string_pretty(value)?; + fs::write(&tmp_path, content)?; + self.replace_with_tmp(tmp_path) + } + + fn ensure_parent(&self) -> AppResult<()> { + if let Some(parent) = self.path.parent() { + fs::create_dir_all(parent)?; + } + Ok(()) + } + + fn tmp_path(&self) -> PathBuf { + self.path.with_extension("tmp") + } + + fn replace_with_tmp(&self, tmp_path: PathBuf) -> AppResult<()> { + if self.path.exists() { + fs::remove_file(&self.path)?; + } + fs::rename(tmp_path, &self.path)?; + Ok(()) + } +} + +pub struct TomlStore { + path: PathBuf, +} + +impl TomlStore { + pub fn new(path: impl Into) -> Self { + Self { path: path.into() } + } + + pub fn update(&self, mutator: F) -> AppResult + where + F: FnOnce(&mut DocumentMut) -> AppResult<()>, + { + let mut doc = self.read()?; + + if self.path.exists() { + backup_toml(&self.path)?; + } + + mutator(&mut doc)?; + self.write(&doc)?; + Ok(doc) + } + + pub fn read(&self) -> AppResult { + if !self.path.exists() { + return Ok(DocumentMut::new()); + } + + let content = fs::read_to_string(&self.path)?; + if content.trim().is_empty() { + Ok(DocumentMut::new()) + } else { + content.parse::().map_err(AppError::from) + } + } + + pub fn write(&self, doc: &DocumentMut) -> AppResult<()> { + self.ensure_parent()?; + let tmp_path = self.tmp_path(); + fs::write(&tmp_path, doc.to_string())?; + self.replace_with_tmp(tmp_path) + } + + fn ensure_parent(&self) -> AppResult<()> { + if let Some(parent) = self.path.parent() { + fs::create_dir_all(parent)?; + } + Ok(()) + } + + fn tmp_path(&self) -> PathBuf { + self.path.with_extension("tmp") + } + + fn replace_with_tmp(&self, tmp_path: PathBuf) -> AppResult<()> { + if self.path.exists() { + fs::remove_file(&self.path)?; + } + fs::rename(tmp_path, &self.path)?; + Ok(()) + } +} diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs new file mode 100644 index 0000000..8c90d06 --- /dev/null +++ b/src-tauri/src/services/mod.rs @@ -0,0 +1,9 @@ +pub mod backup; +pub mod config_store; +pub mod shell; +pub mod tool_profiles; + +pub use backup::*; +pub use config_store::*; +pub use shell::*; +pub use tool_profiles::*; diff --git a/src-tauri/src/services/shell.rs b/src-tauri/src/services/shell.rs new file mode 100644 index 0000000..aa9322b --- /dev/null +++ b/src-tauri/src/services/shell.rs @@ -0,0 +1,104 @@ +use std::env; +use std::fs; +use std::process::{Command, Output}; + +#[cfg(target_os = "windows")] +pub const CREATE_NO_WINDOW: u32 = 0x0800_0000; + +use crate::error::{AppError, AppResult}; + +pub struct CommandRunner; + +impl CommandRunner { + pub fn new() -> Self { + Self + } + + pub fn run(&self, cmd: &str) -> AppResult { + #[cfg(target_os = "windows")] + { + Command::new("cmd") + .env("PATH", extended_path()) + .arg("/C") + .arg(cmd) + .creation_flags(CREATE_NO_WINDOW) + .output() + .map_err(AppError::from) + } + + #[cfg(not(target_os = "windows"))] + { + Command::new("sh") + .env("PATH", extended_path()) + .arg("-c") + .arg(cmd) + .output() + .map_err(AppError::from) + } + } +} + +pub fn extended_path() -> String { + #[cfg(target_os = "windows")] + { + let user_profile = + env::var("USERPROFILE").unwrap_or_else(|_| "C:\\Users\\Default".to_string()); + + let system_paths = vec![ + format!("{}\\AppData\\Local\\Programs\\claude-code", user_profile), + format!("{}\\AppData\\Roaming\\npm", user_profile), + format!( + "{}\\AppData\\Local\\Programs\\Python\\Python312", + user_profile + ), + format!( + "{}\\AppData\\Local\\Programs\\Python\\Python312\\Scripts", + user_profile + ), + "C:\\Program Files\\nodejs".to_string(), + "C:\\Program Files\\Git\\cmd".to_string(), + "C:\\Windows\\System32".to_string(), + "C:\\Windows".to_string(), + ]; + + let current_path = env::var("PATH").unwrap_or_default(); + format!("{};{}", system_paths.join(";"), current_path) + } + + #[cfg(not(target_os = "windows"))] + { + let home_dir = env::var("HOME").unwrap_or_else(|_| "/Users/default".to_string()); + + let mut system_paths = vec![ + format!("{}/.local/bin", home_dir), + format!("{}/.claude/bin", home_dir), + format!("{}/.claude/local", home_dir), + "/opt/homebrew/bin".to_string(), + "/usr/local/bin".to_string(), + "/usr/bin".to_string(), + "/bin".to_string(), + "/usr/sbin".to_string(), + "/sbin".to_string(), + ]; + + let nvm_dir = format!("{}/.nvm/versions/node", home_dir); + if let Ok(entries) = fs::read_dir(&nvm_dir) { + for entry in entries.flatten() { + if let Ok(file_type) = entry.file_type() { + if file_type.is_dir() { + let bin_path = entry.path().join("bin"); + if bin_path.exists() { + system_paths.push(bin_path.to_string_lossy().to_string()); + } + } + } + } + } + + format!( + "{}:{}", + system_paths.join(":"), + env::var("PATH").unwrap_or_default() + ) + } +} diff --git a/src-tauri/src/services/tool_profiles.rs b/src-tauri/src/services/tool_profiles.rs new file mode 100644 index 0000000..65a626a --- /dev/null +++ b/src-tauri/src/services/tool_profiles.rs @@ -0,0 +1,41 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +use crate::error::AppResult; + +pub fn list_profiles(dir: &Path, prefix: &str, suffix: &str) -> AppResult> { + if !dir.exists() { + return Ok(vec![]); + } + + let mut profiles = vec![]; + for entry in fs::read_dir(dir)? { + let entry = entry?; + if !entry.file_type()?.is_file() { + continue; + } + let name = match entry.file_name().into_string() { + Ok(name) => name, + Err(_) => continue, + }; + + if !name.starts_with(prefix) || !name.ends_with(suffix) { + continue; + } + + let trimmed = name + .strip_prefix(prefix) + .and_then(|n| n.strip_suffix(suffix)); + + if let Some(profile) = trimmed { + profiles.push(profile.to_string()); + } + } + + profiles.sort(); + Ok(profiles) +} + +pub fn profile_file(dir: &Path, prefix: &str, profile: &str, suffix: &str) -> PathBuf { + dir.join(format!("{}{}{}", prefix, profile, suffix)) +} diff --git a/src/App.tsx b/src/App.tsx index e02e803..68253e5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -382,7 +382,7 @@ function App() { try { const updatePromises = installedTools.map(async (tool) => { try { - const result = await checkUpdate(tool.id); + const result = await checkUpdate(tool.id, tool.version); return { toolId: tool.id, result }; } catch (error) { console.error(`Failed to check update for ${tool.id}:`, error); @@ -599,7 +599,7 @@ function App() { tools.map(async (tool) => { if (tool.installed) { try { - const updateInfo: UpdateResult = await checkUpdate(tool.id); + const updateInfo: UpdateResult = await checkUpdate(tool.id, tool.version); return { ...tool, hasUpdate: updateInfo.has_update, diff --git a/src/lib/tauri-commands.ts b/src/lib/tauri-commands.ts index 6adf5a1..c7f171c 100644 --- a/src/lib/tauri-commands.ts +++ b/src/lib/tauri-commands.ts @@ -83,8 +83,8 @@ export async function installTool(tool: string, method: string): Promise("install_tool", { tool, method }); } -export async function checkUpdate(tool: string): Promise { - return await invoke("check_update", { tool }); +export async function checkUpdate(tool: string, currentVersion?: string | null): Promise { + return await invoke("check_update", { tool, currentVersion }); } export async function updateTool(tool: string): Promise {