├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── feature.yml └── workflows │ └── release.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── generate_usage.ps1 ├── rustfmt.toml ├── src ├── config.rs ├── main.rs ├── polling.rs └── util.rs ├── test └── test.toml └── usage.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: 8LWXpg # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report a bug in the application 3 | 4 | labels: 5 | - bug 6 | 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Description 12 | description: Please provide a clear and concise description of the bug you are experiencing. 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: reproduction 17 | attributes: 18 | label: Steps to reproduce 19 | description: Provide a clear and concise description of the steps to reproduce the bug. 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature request 2 | description: Suggest a new feature or enhancement for the extension 3 | 4 | labels: 5 | - enhancement 6 | 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Description 12 | description: Provide a clear and concise description of the feature you are requesting. 13 | validations: 14 | required: true 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build-upload: 10 | name: release ${{ matrix.target }} 11 | runs-on: ${{ matrix.os }} 12 | permissions: 13 | contents: write 14 | strategy: 15 | matrix: 16 | include: 17 | - target: x86_64-pc-windows-msvc 18 | os: windows-latest 19 | - target: aarch64-pc-windows-msvc 20 | os: windows-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: dtolnay/rust-toolchain@stable 25 | with: 26 | target: ${{ matrix.target }} 27 | 28 | - name: Run Cargo 29 | run: cargo build --release --target "${{ matrix.target }}" 30 | 31 | - name: Archive 32 | run: 7z a "ptr-${{ matrix.target }}.zip" README.md LICENSE ./target/${{ matrix.target }}/release/ptr.exe 33 | 34 | - uses: ncipollo/release-action@v1 35 | with: 36 | artifacts: "ptr-${{ matrix.target }}.zip" 37 | allowUpdates: true 38 | omitNameDuringUpdate: true 39 | omitBodyDuringUpdate: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "cppvsdbg", 9 | "request": "launch", 10 | "name": "Debug executable 'ptr'", 11 | "program": "${workspaceRoot}/target/debug/${workspaceFolderBasename}.exe", 12 | "args": [ 13 | "init", 14 | ], 15 | "console": "integratedTerminal", 16 | "cwd": "${workspaceFolder}", 17 | "preLaunchTask": "rust: cargo build" 18 | }, 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "indicatif", 4 | "reqwest", 5 | "tabwriter" 6 | ], 7 | "rust-analyzer.cargo.features": [] 8 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Unreleased 4 | 5 | ### Changed 6 | 7 | - Optimized for binary size. 8 | 9 | ## [0.15.0] 10 | 11 | ### Added 12 | 13 | - Added `--no-restart` top level option. 14 | - Added `no_restart` field in config. 15 | 16 | ## [0.14.0] 17 | 18 | ### Added 19 | 20 | - Added `token` field in config that used when sending request to GitHub. 21 | 22 | ## [0.13.0] 23 | 24 | ### Added 25 | 26 | - Added `edit` subcommand that opens `version.toml` in default editor. 27 | - Added `init` subcommand that try to find and add existing plugins to config 28 | 29 | ### Fixed 30 | 31 | - Fixed `pin remove`. 32 | 33 | ## [0.12.0] 34 | 35 | ### Added 36 | 37 | - Added new optional argument `--pattern` for `add` subcommand (rust regex). 38 | 39 | ### Changed 40 | 41 | - Skip unexpected file on zip extraction. 42 | - Changed self replace directory to `%TEMP%`. 43 | 44 | ## [0.11.0] 45 | 46 | ### Added 47 | 48 | - Added `self-update` subcommand. 49 | 50 | ## [0.10.0] 51 | 52 | ### Changed 53 | 54 | - Allow GitHub url as repository identifier. 55 | 56 | ## [0.9.0] 57 | 58 | ### Added 59 | 60 | - Added new field `admin` in `version.toml` set to `false` to disable killing as admin. **(Breaking Change)** 61 | - Added new subcommand `pin`, pinned plugins will not be updated with `update --all`. 62 | - Added new subcommand `completion` that generates PowerShell completion. 63 | 64 | ## [0.8.0] 65 | 66 | ### Changed 67 | 68 | - Support different zip structure 69 | - Use `%ProgramFiles%` in PowerToys executable lookup. 70 | 71 | ## [0.7.1] 72 | 73 | ### Fixed 74 | 75 | - Prompt for path if PowerToys installation path is not found. 76 | 77 | ## [0.7.0] 78 | 79 | ### Added 80 | 81 | - Added manual select fallback if assets matching failed. 82 | 83 | ### Fixed 84 | 85 | - Fixed extracting plugin zip file with backslashes in the path. 86 | - Ignore version when importing plugin. 87 | 88 | ## [0.6.0] 89 | 90 | ### Added 91 | 92 | - Added `--version` flag in `update` command to specify the version of the plugin to update. 93 | 94 | ## [0.5.0] 95 | 96 | ### Added 97 | 98 | - Added `pt_path` field in the configuration file to specify the path to PowerToys installation. **(Breaking Change)** 99 | - Added `--dry-run (-d)` flag in `import` command to only update the configuration file without downloading the plugin, useful when config file spec is changed. 100 | 101 | ### Changed 102 | 103 | - Support `ARM64` along with `arm64` in the archive name. 104 | - Check for `.zip` file extension in the archive name. 105 | - Only check plugins field on `import` command. 106 | 107 | ## [0.4.2] 108 | 109 | ### Fixed 110 | 111 | - Fixed extracting plugin with different folder name. 112 | 113 | ## [0.4.1] 114 | 115 | ### Fixed 116 | 117 | - Fixed extracting plugin with different folder name. 118 | 119 | ## [0.4.0] 120 | 121 | ### Added 122 | 123 | - Support for renaming the downloaded plugin folder to the provided name. 124 | 125 | ### Changed 126 | 127 | - Default to using `winapi` to elevate the process, as there's no major difference between `sudo` and `winapi`. 128 | 129 | ## [0.3.0] 130 | 131 | ### Added 132 | 133 | - Now polling for file access after killing PowerToys, with interval of 50ms and max retries of 10. 134 | - Create feature `winapi` that uses Windows API to elevate the process, the default is using `sudo`. 135 | 136 | ## [0.2.0] 137 | 138 | ### Added 139 | 140 | - Support for killing and restarting PowerToys, this will pop 2 UAC prompts. 141 | 142 | ## [0.1.1] 143 | 144 | ### Changed 145 | 146 | - Removed the progress bar, as most plugins are too small to display a meaningful download progress. 147 | - Replaced asynchronous code with `reqwest::blocking` for simplicity. 148 | 149 | ## [0.1.0] 150 | 151 | First release -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "anstream" 22 | version = "0.6.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is_terminal_polyfill", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.10" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.6" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.1.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 55 | dependencies = [ 56 | "windows-sys 0.59.0", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "3.0.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 64 | dependencies = [ 65 | "anstyle", 66 | "windows-sys 0.59.0", 67 | ] 68 | 69 | [[package]] 70 | name = "anyhow" 71 | version = "1.0.95" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 74 | 75 | [[package]] 76 | name = "arbitrary" 77 | version = "1.4.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 80 | dependencies = [ 81 | "derive_arbitrary", 82 | ] 83 | 84 | [[package]] 85 | name = "autocfg" 86 | version = "1.4.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 89 | 90 | [[package]] 91 | name = "backtrace" 92 | version = "0.3.74" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 95 | dependencies = [ 96 | "addr2line", 97 | "cfg-if", 98 | "libc", 99 | "miniz_oxide", 100 | "object", 101 | "rustc-demangle", 102 | "windows-targets 0.52.6", 103 | ] 104 | 105 | [[package]] 106 | name = "base64" 107 | version = "0.22.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 110 | 111 | [[package]] 112 | name = "bitflags" 113 | version = "2.6.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 116 | 117 | [[package]] 118 | name = "bumpalo" 119 | version = "3.16.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 122 | 123 | [[package]] 124 | name = "bytes" 125 | version = "1.9.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" 128 | 129 | [[package]] 130 | name = "cc" 131 | version = "1.2.5" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" 134 | dependencies = [ 135 | "shlex", 136 | ] 137 | 138 | [[package]] 139 | name = "cfg-if" 140 | version = "1.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 143 | 144 | [[package]] 145 | name = "clap" 146 | version = "4.5.26" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" 149 | dependencies = [ 150 | "clap_builder", 151 | "clap_derive", 152 | ] 153 | 154 | [[package]] 155 | name = "clap_builder" 156 | version = "4.5.26" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" 159 | dependencies = [ 160 | "anstream", 161 | "anstyle", 162 | "clap_lex", 163 | "strsim", 164 | ] 165 | 166 | [[package]] 167 | name = "clap_complete" 168 | version = "4.5.42" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "33a7e468e750fa4b6be660e8b5651ad47372e8fb114030b594c2d75d48c5ffd0" 171 | dependencies = [ 172 | "clap", 173 | ] 174 | 175 | [[package]] 176 | name = "clap_derive" 177 | version = "4.5.24" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" 180 | dependencies = [ 181 | "heck", 182 | "proc-macro2", 183 | "quote", 184 | "syn", 185 | ] 186 | 187 | [[package]] 188 | name = "clap_lex" 189 | version = "0.7.4" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 192 | 193 | [[package]] 194 | name = "colorchoice" 195 | version = "1.0.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 198 | 199 | [[package]] 200 | name = "colored" 201 | version = "3.0.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" 204 | dependencies = [ 205 | "windows-sys 0.59.0", 206 | ] 207 | 208 | [[package]] 209 | name = "core-foundation" 210 | version = "0.9.4" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 213 | dependencies = [ 214 | "core-foundation-sys", 215 | "libc", 216 | ] 217 | 218 | [[package]] 219 | name = "core-foundation-sys" 220 | version = "0.8.7" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 223 | 224 | [[package]] 225 | name = "crc32fast" 226 | version = "1.4.2" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 229 | dependencies = [ 230 | "cfg-if", 231 | ] 232 | 233 | [[package]] 234 | name = "crossbeam-utils" 235 | version = "0.8.21" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 238 | 239 | [[package]] 240 | name = "derive_arbitrary" 241 | version = "1.4.1" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" 244 | dependencies = [ 245 | "proc-macro2", 246 | "quote", 247 | "syn", 248 | ] 249 | 250 | [[package]] 251 | name = "displaydoc" 252 | version = "0.2.5" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 255 | dependencies = [ 256 | "proc-macro2", 257 | "quote", 258 | "syn", 259 | ] 260 | 261 | [[package]] 262 | name = "equivalent" 263 | version = "1.0.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 266 | 267 | [[package]] 268 | name = "errno" 269 | version = "0.3.10" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 272 | dependencies = [ 273 | "libc", 274 | "windows-sys 0.59.0", 275 | ] 276 | 277 | [[package]] 278 | name = "fastrand" 279 | version = "2.3.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 282 | 283 | [[package]] 284 | name = "flate2" 285 | version = "1.0.35" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 288 | dependencies = [ 289 | "crc32fast", 290 | "miniz_oxide", 291 | ] 292 | 293 | [[package]] 294 | name = "fnv" 295 | version = "1.0.7" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 298 | 299 | [[package]] 300 | name = "foreign-types" 301 | version = "0.3.2" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 304 | dependencies = [ 305 | "foreign-types-shared", 306 | ] 307 | 308 | [[package]] 309 | name = "foreign-types-shared" 310 | version = "0.1.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 313 | 314 | [[package]] 315 | name = "form_urlencoded" 316 | version = "1.2.1" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 319 | dependencies = [ 320 | "percent-encoding", 321 | ] 322 | 323 | [[package]] 324 | name = "futures-channel" 325 | version = "0.3.31" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 328 | dependencies = [ 329 | "futures-core", 330 | "futures-sink", 331 | ] 332 | 333 | [[package]] 334 | name = "futures-core" 335 | version = "0.3.31" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 338 | 339 | [[package]] 340 | name = "futures-io" 341 | version = "0.3.31" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 344 | 345 | [[package]] 346 | name = "futures-sink" 347 | version = "0.3.31" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 350 | 351 | [[package]] 352 | name = "futures-task" 353 | version = "0.3.31" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 356 | 357 | [[package]] 358 | name = "futures-util" 359 | version = "0.3.31" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 362 | dependencies = [ 363 | "futures-core", 364 | "futures-io", 365 | "futures-sink", 366 | "futures-task", 367 | "memchr", 368 | "pin-project-lite", 369 | "pin-utils", 370 | "slab", 371 | ] 372 | 373 | [[package]] 374 | name = "gimli" 375 | version = "0.31.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 378 | 379 | [[package]] 380 | name = "hashbrown" 381 | version = "0.15.2" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 384 | 385 | [[package]] 386 | name = "heck" 387 | version = "0.5.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 390 | 391 | [[package]] 392 | name = "http" 393 | version = "1.2.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 396 | dependencies = [ 397 | "bytes", 398 | "fnv", 399 | "itoa", 400 | ] 401 | 402 | [[package]] 403 | name = "http-body" 404 | version = "1.0.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 407 | dependencies = [ 408 | "bytes", 409 | "http", 410 | ] 411 | 412 | [[package]] 413 | name = "http-body-util" 414 | version = "0.1.2" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 417 | dependencies = [ 418 | "bytes", 419 | "futures-util", 420 | "http", 421 | "http-body", 422 | "pin-project-lite", 423 | ] 424 | 425 | [[package]] 426 | name = "httparse" 427 | version = "1.9.5" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 430 | 431 | [[package]] 432 | name = "hyper" 433 | version = "1.5.2" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" 436 | dependencies = [ 437 | "bytes", 438 | "futures-channel", 439 | "futures-util", 440 | "http", 441 | "http-body", 442 | "httparse", 443 | "itoa", 444 | "pin-project-lite", 445 | "smallvec", 446 | "tokio", 447 | "want", 448 | ] 449 | 450 | [[package]] 451 | name = "hyper-tls" 452 | version = "0.6.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 455 | dependencies = [ 456 | "bytes", 457 | "http-body-util", 458 | "hyper", 459 | "hyper-util", 460 | "native-tls", 461 | "tokio", 462 | "tokio-native-tls", 463 | "tower-service", 464 | ] 465 | 466 | [[package]] 467 | name = "hyper-util" 468 | version = "0.1.10" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 471 | dependencies = [ 472 | "bytes", 473 | "futures-channel", 474 | "futures-util", 475 | "http", 476 | "http-body", 477 | "hyper", 478 | "pin-project-lite", 479 | "socket2", 480 | "tokio", 481 | "tower-service", 482 | "tracing", 483 | ] 484 | 485 | [[package]] 486 | name = "icu_collections" 487 | version = "1.5.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 490 | dependencies = [ 491 | "displaydoc", 492 | "yoke", 493 | "zerofrom", 494 | "zerovec", 495 | ] 496 | 497 | [[package]] 498 | name = "icu_locid" 499 | version = "1.5.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 502 | dependencies = [ 503 | "displaydoc", 504 | "litemap", 505 | "tinystr", 506 | "writeable", 507 | "zerovec", 508 | ] 509 | 510 | [[package]] 511 | name = "icu_locid_transform" 512 | version = "1.5.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 515 | dependencies = [ 516 | "displaydoc", 517 | "icu_locid", 518 | "icu_locid_transform_data", 519 | "icu_provider", 520 | "tinystr", 521 | "zerovec", 522 | ] 523 | 524 | [[package]] 525 | name = "icu_locid_transform_data" 526 | version = "1.5.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 529 | 530 | [[package]] 531 | name = "icu_normalizer" 532 | version = "1.5.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 535 | dependencies = [ 536 | "displaydoc", 537 | "icu_collections", 538 | "icu_normalizer_data", 539 | "icu_properties", 540 | "icu_provider", 541 | "smallvec", 542 | "utf16_iter", 543 | "utf8_iter", 544 | "write16", 545 | "zerovec", 546 | ] 547 | 548 | [[package]] 549 | name = "icu_normalizer_data" 550 | version = "1.5.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 553 | 554 | [[package]] 555 | name = "icu_properties" 556 | version = "1.5.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 559 | dependencies = [ 560 | "displaydoc", 561 | "icu_collections", 562 | "icu_locid_transform", 563 | "icu_properties_data", 564 | "icu_provider", 565 | "tinystr", 566 | "zerovec", 567 | ] 568 | 569 | [[package]] 570 | name = "icu_properties_data" 571 | version = "1.5.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 574 | 575 | [[package]] 576 | name = "icu_provider" 577 | version = "1.5.0" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 580 | dependencies = [ 581 | "displaydoc", 582 | "icu_locid", 583 | "icu_provider_macros", 584 | "stable_deref_trait", 585 | "tinystr", 586 | "writeable", 587 | "yoke", 588 | "zerofrom", 589 | "zerovec", 590 | ] 591 | 592 | [[package]] 593 | name = "icu_provider_macros" 594 | version = "1.5.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 597 | dependencies = [ 598 | "proc-macro2", 599 | "quote", 600 | "syn", 601 | ] 602 | 603 | [[package]] 604 | name = "idna" 605 | version = "1.0.3" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 608 | dependencies = [ 609 | "idna_adapter", 610 | "smallvec", 611 | "utf8_iter", 612 | ] 613 | 614 | [[package]] 615 | name = "idna_adapter" 616 | version = "1.2.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 619 | dependencies = [ 620 | "icu_normalizer", 621 | "icu_properties", 622 | ] 623 | 624 | [[package]] 625 | name = "indexmap" 626 | version = "2.7.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 629 | dependencies = [ 630 | "equivalent", 631 | "hashbrown", 632 | ] 633 | 634 | [[package]] 635 | name = "ipnet" 636 | version = "2.10.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 639 | 640 | [[package]] 641 | name = "is_terminal_polyfill" 642 | version = "1.70.1" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 645 | 646 | [[package]] 647 | name = "itoa" 648 | version = "1.0.14" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 651 | 652 | [[package]] 653 | name = "js-sys" 654 | version = "0.3.76" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 657 | dependencies = [ 658 | "once_cell", 659 | "wasm-bindgen", 660 | ] 661 | 662 | [[package]] 663 | name = "libc" 664 | version = "0.2.169" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 667 | 668 | [[package]] 669 | name = "linux-raw-sys" 670 | version = "0.4.14" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 673 | 674 | [[package]] 675 | name = "litemap" 676 | version = "0.7.4" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 679 | 680 | [[package]] 681 | name = "lockfree-object-pool" 682 | version = "0.1.6" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" 685 | 686 | [[package]] 687 | name = "log" 688 | version = "0.4.22" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 691 | 692 | [[package]] 693 | name = "memchr" 694 | version = "2.7.4" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 697 | 698 | [[package]] 699 | name = "mime" 700 | version = "0.3.17" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 703 | 704 | [[package]] 705 | name = "miniz_oxide" 706 | version = "0.8.2" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" 709 | dependencies = [ 710 | "adler2", 711 | ] 712 | 713 | [[package]] 714 | name = "mio" 715 | version = "1.0.3" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 718 | dependencies = [ 719 | "libc", 720 | "wasi", 721 | "windows-sys 0.52.0", 722 | ] 723 | 724 | [[package]] 725 | name = "native-tls" 726 | version = "0.2.12" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 729 | dependencies = [ 730 | "libc", 731 | "log", 732 | "openssl", 733 | "openssl-probe", 734 | "openssl-sys", 735 | "schannel", 736 | "security-framework", 737 | "security-framework-sys", 738 | "tempfile", 739 | ] 740 | 741 | [[package]] 742 | name = "object" 743 | version = "0.36.7" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 746 | dependencies = [ 747 | "memchr", 748 | ] 749 | 750 | [[package]] 751 | name = "once_cell" 752 | version = "1.20.2" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 755 | 756 | [[package]] 757 | name = "openssl" 758 | version = "0.10.68" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 761 | dependencies = [ 762 | "bitflags", 763 | "cfg-if", 764 | "foreign-types", 765 | "libc", 766 | "once_cell", 767 | "openssl-macros", 768 | "openssl-sys", 769 | ] 770 | 771 | [[package]] 772 | name = "openssl-macros" 773 | version = "0.1.1" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 776 | dependencies = [ 777 | "proc-macro2", 778 | "quote", 779 | "syn", 780 | ] 781 | 782 | [[package]] 783 | name = "openssl-probe" 784 | version = "0.1.5" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 787 | 788 | [[package]] 789 | name = "openssl-sys" 790 | version = "0.9.104" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 793 | dependencies = [ 794 | "cc", 795 | "libc", 796 | "pkg-config", 797 | "vcpkg", 798 | ] 799 | 800 | [[package]] 801 | name = "percent-encoding" 802 | version = "2.3.1" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 805 | 806 | [[package]] 807 | name = "pin-project-lite" 808 | version = "0.2.15" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 811 | 812 | [[package]] 813 | name = "pin-utils" 814 | version = "0.1.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 817 | 818 | [[package]] 819 | name = "pkg-config" 820 | version = "0.3.31" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 823 | 824 | [[package]] 825 | name = "proc-macro2" 826 | version = "1.0.92" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 829 | dependencies = [ 830 | "unicode-ident", 831 | ] 832 | 833 | [[package]] 834 | name = "ptr" 835 | version = "0.15.0" 836 | dependencies = [ 837 | "anyhow", 838 | "clap", 839 | "clap_complete", 840 | "colored", 841 | "regex", 842 | "reqwest", 843 | "self-replace", 844 | "serde", 845 | "serde_json", 846 | "tabwriter", 847 | "toml", 848 | "windows", 849 | "zip", 850 | ] 851 | 852 | [[package]] 853 | name = "quote" 854 | version = "1.0.38" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 857 | dependencies = [ 858 | "proc-macro2", 859 | ] 860 | 861 | [[package]] 862 | name = "regex" 863 | version = "1.11.1" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 866 | dependencies = [ 867 | "regex-automata", 868 | "regex-syntax", 869 | ] 870 | 871 | [[package]] 872 | name = "regex-automata" 873 | version = "0.4.9" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 876 | dependencies = [ 877 | "regex-syntax", 878 | ] 879 | 880 | [[package]] 881 | name = "regex-syntax" 882 | version = "0.8.5" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 885 | 886 | [[package]] 887 | name = "reqwest" 888 | version = "0.12.12" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" 891 | dependencies = [ 892 | "base64", 893 | "bytes", 894 | "futures-channel", 895 | "futures-core", 896 | "futures-util", 897 | "http", 898 | "http-body", 899 | "http-body-util", 900 | "hyper", 901 | "hyper-tls", 902 | "hyper-util", 903 | "ipnet", 904 | "js-sys", 905 | "log", 906 | "mime", 907 | "native-tls", 908 | "once_cell", 909 | "percent-encoding", 910 | "pin-project-lite", 911 | "rustls-pemfile", 912 | "serde", 913 | "serde_json", 914 | "serde_urlencoded", 915 | "sync_wrapper", 916 | "tokio", 917 | "tokio-native-tls", 918 | "tower", 919 | "tower-service", 920 | "url", 921 | "wasm-bindgen", 922 | "wasm-bindgen-futures", 923 | "web-sys", 924 | "windows-registry", 925 | ] 926 | 927 | [[package]] 928 | name = "rustc-demangle" 929 | version = "0.1.24" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 932 | 933 | [[package]] 934 | name = "rustix" 935 | version = "0.38.42" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" 938 | dependencies = [ 939 | "bitflags", 940 | "errno", 941 | "libc", 942 | "linux-raw-sys", 943 | "windows-sys 0.59.0", 944 | ] 945 | 946 | [[package]] 947 | name = "rustls-pemfile" 948 | version = "2.2.0" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 951 | dependencies = [ 952 | "rustls-pki-types", 953 | ] 954 | 955 | [[package]] 956 | name = "rustls-pki-types" 957 | version = "1.10.1" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" 960 | 961 | [[package]] 962 | name = "ryu" 963 | version = "1.0.18" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 966 | 967 | [[package]] 968 | name = "schannel" 969 | version = "0.1.27" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 972 | dependencies = [ 973 | "windows-sys 0.59.0", 974 | ] 975 | 976 | [[package]] 977 | name = "security-framework" 978 | version = "2.11.1" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 981 | dependencies = [ 982 | "bitflags", 983 | "core-foundation", 984 | "core-foundation-sys", 985 | "libc", 986 | "security-framework-sys", 987 | ] 988 | 989 | [[package]] 990 | name = "security-framework-sys" 991 | version = "2.13.0" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" 994 | dependencies = [ 995 | "core-foundation-sys", 996 | "libc", 997 | ] 998 | 999 | [[package]] 1000 | name = "self-replace" 1001 | version = "1.5.0" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "03ec815b5eab420ab893f63393878d89c90fdd94c0bcc44c07abb8ad95552fb7" 1004 | dependencies = [ 1005 | "fastrand", 1006 | "tempfile", 1007 | "windows-sys 0.52.0", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "serde" 1012 | version = "1.0.217" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1015 | dependencies = [ 1016 | "serde_derive", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "serde_derive" 1021 | version = "1.0.217" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1024 | dependencies = [ 1025 | "proc-macro2", 1026 | "quote", 1027 | "syn", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "serde_json" 1032 | version = "1.0.139" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" 1035 | dependencies = [ 1036 | "itoa", 1037 | "memchr", 1038 | "ryu", 1039 | "serde", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "serde_spanned" 1044 | version = "0.6.8" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1047 | dependencies = [ 1048 | "serde", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "serde_urlencoded" 1053 | version = "0.7.1" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1056 | dependencies = [ 1057 | "form_urlencoded", 1058 | "itoa", 1059 | "ryu", 1060 | "serde", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "shlex" 1065 | version = "1.3.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1068 | 1069 | [[package]] 1070 | name = "simd-adler32" 1071 | version = "0.3.7" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1074 | 1075 | [[package]] 1076 | name = "slab" 1077 | version = "0.4.9" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1080 | dependencies = [ 1081 | "autocfg", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "smallvec" 1086 | version = "1.13.2" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1089 | 1090 | [[package]] 1091 | name = "socket2" 1092 | version = "0.5.8" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1095 | dependencies = [ 1096 | "libc", 1097 | "windows-sys 0.52.0", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "stable_deref_trait" 1102 | version = "1.2.0" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1105 | 1106 | [[package]] 1107 | name = "strsim" 1108 | version = "0.11.1" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1111 | 1112 | [[package]] 1113 | name = "syn" 1114 | version = "2.0.95" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" 1117 | dependencies = [ 1118 | "proc-macro2", 1119 | "quote", 1120 | "unicode-ident", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "sync_wrapper" 1125 | version = "1.0.2" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1128 | dependencies = [ 1129 | "futures-core", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "synstructure" 1134 | version = "0.13.1" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1137 | dependencies = [ 1138 | "proc-macro2", 1139 | "quote", 1140 | "syn", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "tabwriter" 1145 | version = "1.4.1" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432" 1148 | dependencies = [ 1149 | "unicode-width", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "tempfile" 1154 | version = "3.14.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 1157 | dependencies = [ 1158 | "cfg-if", 1159 | "fastrand", 1160 | "once_cell", 1161 | "rustix", 1162 | "windows-sys 0.59.0", 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "thiserror" 1167 | version = "2.0.9" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" 1170 | dependencies = [ 1171 | "thiserror-impl", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "thiserror-impl" 1176 | version = "2.0.9" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" 1179 | dependencies = [ 1180 | "proc-macro2", 1181 | "quote", 1182 | "syn", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "tinystr" 1187 | version = "0.7.6" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1190 | dependencies = [ 1191 | "displaydoc", 1192 | "zerovec", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "tokio" 1197 | version = "1.42.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" 1200 | dependencies = [ 1201 | "backtrace", 1202 | "bytes", 1203 | "libc", 1204 | "mio", 1205 | "pin-project-lite", 1206 | "socket2", 1207 | "windows-sys 0.52.0", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "tokio-native-tls" 1212 | version = "0.3.1" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1215 | dependencies = [ 1216 | "native-tls", 1217 | "tokio", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "toml" 1222 | version = "0.8.19" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 1225 | dependencies = [ 1226 | "serde", 1227 | "serde_spanned", 1228 | "toml_datetime", 1229 | "toml_edit", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "toml_datetime" 1234 | version = "0.6.8" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1237 | dependencies = [ 1238 | "serde", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "toml_edit" 1243 | version = "0.22.22" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 1246 | dependencies = [ 1247 | "indexmap", 1248 | "serde", 1249 | "serde_spanned", 1250 | "toml_datetime", 1251 | "winnow", 1252 | ] 1253 | 1254 | [[package]] 1255 | name = "tower" 1256 | version = "0.5.2" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1259 | dependencies = [ 1260 | "futures-core", 1261 | "futures-util", 1262 | "pin-project-lite", 1263 | "sync_wrapper", 1264 | "tokio", 1265 | "tower-layer", 1266 | "tower-service", 1267 | ] 1268 | 1269 | [[package]] 1270 | name = "tower-layer" 1271 | version = "0.3.3" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1274 | 1275 | [[package]] 1276 | name = "tower-service" 1277 | version = "0.3.3" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1280 | 1281 | [[package]] 1282 | name = "tracing" 1283 | version = "0.1.41" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1286 | dependencies = [ 1287 | "pin-project-lite", 1288 | "tracing-core", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "tracing-core" 1293 | version = "0.1.33" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1296 | dependencies = [ 1297 | "once_cell", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "try-lock" 1302 | version = "0.2.5" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1305 | 1306 | [[package]] 1307 | name = "unicode-ident" 1308 | version = "1.0.14" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 1311 | 1312 | [[package]] 1313 | name = "unicode-width" 1314 | version = "0.2.0" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1317 | 1318 | [[package]] 1319 | name = "url" 1320 | version = "2.5.4" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1323 | dependencies = [ 1324 | "form_urlencoded", 1325 | "idna", 1326 | "percent-encoding", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "utf16_iter" 1331 | version = "1.0.5" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1334 | 1335 | [[package]] 1336 | name = "utf8_iter" 1337 | version = "1.0.4" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1340 | 1341 | [[package]] 1342 | name = "utf8parse" 1343 | version = "0.2.2" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1346 | 1347 | [[package]] 1348 | name = "vcpkg" 1349 | version = "0.2.15" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1352 | 1353 | [[package]] 1354 | name = "want" 1355 | version = "0.3.1" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1358 | dependencies = [ 1359 | "try-lock", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "wasi" 1364 | version = "0.11.0+wasi-snapshot-preview1" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1367 | 1368 | [[package]] 1369 | name = "wasm-bindgen" 1370 | version = "0.2.99" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 1373 | dependencies = [ 1374 | "cfg-if", 1375 | "once_cell", 1376 | "wasm-bindgen-macro", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "wasm-bindgen-backend" 1381 | version = "0.2.99" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 1384 | dependencies = [ 1385 | "bumpalo", 1386 | "log", 1387 | "proc-macro2", 1388 | "quote", 1389 | "syn", 1390 | "wasm-bindgen-shared", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "wasm-bindgen-futures" 1395 | version = "0.4.49" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" 1398 | dependencies = [ 1399 | "cfg-if", 1400 | "js-sys", 1401 | "once_cell", 1402 | "wasm-bindgen", 1403 | "web-sys", 1404 | ] 1405 | 1406 | [[package]] 1407 | name = "wasm-bindgen-macro" 1408 | version = "0.2.99" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 1411 | dependencies = [ 1412 | "quote", 1413 | "wasm-bindgen-macro-support", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "wasm-bindgen-macro-support" 1418 | version = "0.2.99" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 1421 | dependencies = [ 1422 | "proc-macro2", 1423 | "quote", 1424 | "syn", 1425 | "wasm-bindgen-backend", 1426 | "wasm-bindgen-shared", 1427 | ] 1428 | 1429 | [[package]] 1430 | name = "wasm-bindgen-shared" 1431 | version = "0.2.99" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 1434 | 1435 | [[package]] 1436 | name = "web-sys" 1437 | version = "0.3.76" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" 1440 | dependencies = [ 1441 | "js-sys", 1442 | "wasm-bindgen", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "windows" 1447 | version = "0.59.0" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" 1450 | dependencies = [ 1451 | "windows-core", 1452 | "windows-targets 0.53.0", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "windows-core" 1457 | version = "0.59.0" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" 1460 | dependencies = [ 1461 | "windows-implement", 1462 | "windows-interface", 1463 | "windows-result 0.3.0", 1464 | "windows-strings 0.3.0", 1465 | "windows-targets 0.53.0", 1466 | ] 1467 | 1468 | [[package]] 1469 | name = "windows-implement" 1470 | version = "0.59.0" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" 1473 | dependencies = [ 1474 | "proc-macro2", 1475 | "quote", 1476 | "syn", 1477 | ] 1478 | 1479 | [[package]] 1480 | name = "windows-interface" 1481 | version = "0.59.0" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" 1484 | dependencies = [ 1485 | "proc-macro2", 1486 | "quote", 1487 | "syn", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "windows-registry" 1492 | version = "0.2.0" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1495 | dependencies = [ 1496 | "windows-result 0.2.0", 1497 | "windows-strings 0.1.0", 1498 | "windows-targets 0.52.6", 1499 | ] 1500 | 1501 | [[package]] 1502 | name = "windows-result" 1503 | version = "0.2.0" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1506 | dependencies = [ 1507 | "windows-targets 0.52.6", 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "windows-result" 1512 | version = "0.3.0" 1513 | source = "registry+https://github.com/rust-lang/crates.io-index" 1514 | checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34" 1515 | dependencies = [ 1516 | "windows-targets 0.53.0", 1517 | ] 1518 | 1519 | [[package]] 1520 | name = "windows-strings" 1521 | version = "0.1.0" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1524 | dependencies = [ 1525 | "windows-result 0.2.0", 1526 | "windows-targets 0.52.6", 1527 | ] 1528 | 1529 | [[package]] 1530 | name = "windows-strings" 1531 | version = "0.3.0" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491" 1534 | dependencies = [ 1535 | "windows-targets 0.53.0", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "windows-sys" 1540 | version = "0.52.0" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1543 | dependencies = [ 1544 | "windows-targets 0.52.6", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "windows-sys" 1549 | version = "0.59.0" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1552 | dependencies = [ 1553 | "windows-targets 0.52.6", 1554 | ] 1555 | 1556 | [[package]] 1557 | name = "windows-targets" 1558 | version = "0.52.6" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1561 | dependencies = [ 1562 | "windows_aarch64_gnullvm 0.52.6", 1563 | "windows_aarch64_msvc 0.52.6", 1564 | "windows_i686_gnu 0.52.6", 1565 | "windows_i686_gnullvm 0.52.6", 1566 | "windows_i686_msvc 0.52.6", 1567 | "windows_x86_64_gnu 0.52.6", 1568 | "windows_x86_64_gnullvm 0.52.6", 1569 | "windows_x86_64_msvc 0.52.6", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "windows-targets" 1574 | version = "0.53.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" 1577 | dependencies = [ 1578 | "windows_aarch64_gnullvm 0.53.0", 1579 | "windows_aarch64_msvc 0.53.0", 1580 | "windows_i686_gnu 0.53.0", 1581 | "windows_i686_gnullvm 0.53.0", 1582 | "windows_i686_msvc 0.53.0", 1583 | "windows_x86_64_gnu 0.53.0", 1584 | "windows_x86_64_gnullvm 0.53.0", 1585 | "windows_x86_64_msvc 0.53.0", 1586 | ] 1587 | 1588 | [[package]] 1589 | name = "windows_aarch64_gnullvm" 1590 | version = "0.52.6" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1593 | 1594 | [[package]] 1595 | name = "windows_aarch64_gnullvm" 1596 | version = "0.53.0" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1599 | 1600 | [[package]] 1601 | name = "windows_aarch64_msvc" 1602 | version = "0.52.6" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1605 | 1606 | [[package]] 1607 | name = "windows_aarch64_msvc" 1608 | version = "0.53.0" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1611 | 1612 | [[package]] 1613 | name = "windows_i686_gnu" 1614 | version = "0.52.6" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1617 | 1618 | [[package]] 1619 | name = "windows_i686_gnu" 1620 | version = "0.53.0" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1623 | 1624 | [[package]] 1625 | name = "windows_i686_gnullvm" 1626 | version = "0.52.6" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1629 | 1630 | [[package]] 1631 | name = "windows_i686_gnullvm" 1632 | version = "0.53.0" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1635 | 1636 | [[package]] 1637 | name = "windows_i686_msvc" 1638 | version = "0.52.6" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1641 | 1642 | [[package]] 1643 | name = "windows_i686_msvc" 1644 | version = "0.53.0" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1647 | 1648 | [[package]] 1649 | name = "windows_x86_64_gnu" 1650 | version = "0.52.6" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1653 | 1654 | [[package]] 1655 | name = "windows_x86_64_gnu" 1656 | version = "0.53.0" 1657 | source = "registry+https://github.com/rust-lang/crates.io-index" 1658 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1659 | 1660 | [[package]] 1661 | name = "windows_x86_64_gnullvm" 1662 | version = "0.52.6" 1663 | source = "registry+https://github.com/rust-lang/crates.io-index" 1664 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1665 | 1666 | [[package]] 1667 | name = "windows_x86_64_gnullvm" 1668 | version = "0.53.0" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1671 | 1672 | [[package]] 1673 | name = "windows_x86_64_msvc" 1674 | version = "0.52.6" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1677 | 1678 | [[package]] 1679 | name = "windows_x86_64_msvc" 1680 | version = "0.53.0" 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" 1682 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1683 | 1684 | [[package]] 1685 | name = "winnow" 1686 | version = "0.6.20" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 1689 | dependencies = [ 1690 | "memchr", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "write16" 1695 | version = "1.0.0" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1698 | 1699 | [[package]] 1700 | name = "writeable" 1701 | version = "0.5.5" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1704 | 1705 | [[package]] 1706 | name = "yoke" 1707 | version = "0.7.5" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 1710 | dependencies = [ 1711 | "serde", 1712 | "stable_deref_trait", 1713 | "yoke-derive", 1714 | "zerofrom", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "yoke-derive" 1719 | version = "0.7.5" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 1722 | dependencies = [ 1723 | "proc-macro2", 1724 | "quote", 1725 | "syn", 1726 | "synstructure", 1727 | ] 1728 | 1729 | [[package]] 1730 | name = "zerofrom" 1731 | version = "0.1.5" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 1734 | dependencies = [ 1735 | "zerofrom-derive", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "zerofrom-derive" 1740 | version = "0.1.5" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 1743 | dependencies = [ 1744 | "proc-macro2", 1745 | "quote", 1746 | "syn", 1747 | "synstructure", 1748 | ] 1749 | 1750 | [[package]] 1751 | name = "zerovec" 1752 | version = "0.10.4" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1755 | dependencies = [ 1756 | "yoke", 1757 | "zerofrom", 1758 | "zerovec-derive", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "zerovec-derive" 1763 | version = "0.10.3" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1766 | dependencies = [ 1767 | "proc-macro2", 1768 | "quote", 1769 | "syn", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "zip" 1774 | version = "2.2.2" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" 1777 | dependencies = [ 1778 | "arbitrary", 1779 | "crc32fast", 1780 | "crossbeam-utils", 1781 | "displaydoc", 1782 | "flate2", 1783 | "indexmap", 1784 | "memchr", 1785 | "thiserror", 1786 | "zopfli", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "zopfli" 1791 | version = "0.8.1" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" 1794 | dependencies = [ 1795 | "bumpalo", 1796 | "crc32fast", 1797 | "lockfree-object-pool", 1798 | "log", 1799 | "once_cell", 1800 | "simd-adler32", 1801 | ] 1802 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ptr" 3 | version = "0.15.0" 4 | authors = ["8LWXpg"] 5 | description = "PowerToys Run plugin manager" 6 | license = "MIT" 7 | repository = "https://github.com/8LWXpg/ptr" 8 | readme = "README.md" 9 | edition = "2021" 10 | 11 | [package.metadata.binstall] 12 | pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }{ archive-suffix }" 13 | bin-dir = "{ bin }{ binary-ext }" 14 | pkg-fmt = "zip" 15 | disabled-strategies = ["quick-install", "compile"] 16 | 17 | [profile.release] 18 | codegen-units = 1 19 | lto = true 20 | opt-level = "s" 21 | panic = "abort" 22 | 23 | [dependencies] 24 | anyhow = "1.0.95" 25 | clap = { version = "4.5.26", features = ["derive"] } 26 | clap_complete = "4.5.42" 27 | colored = "3.0.0" 28 | regex = { version = "1.11.1", default-features = false } 29 | reqwest = { version = "0.12.12", default-features = false, features = ["blocking", "json", "native-tls"] } 30 | self-replace = "1.5.0" 31 | serde = { version = "1.0.217", features = ["derive"] } 32 | serde_json = "1.0.139" 33 | tabwriter = { version = "1.4.1", features = ["ansi_formatting"] } 34 | toml = "0.8.19" 35 | windows = { version = "0.59.0", features = ["Win32_UI_Shell", "Win32_System_Registry", "Win32_System_Threading"] } 36 | zip = { version = "2.2.2", default-features = false, features = ["deflate"] } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 8LWXpg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerToys Run Plugin Manager 2 | 3 | ![preview](https://github.com/user-attachments/assets/94489f6f-0301-4427-8c44-2f801201c64f) 4 | 5 | Install and manage any PowerToys Run plugin released on GitHub with single command line interface. 6 | 7 | ## Installation 8 | 9 | ### Download 10 | 11 | Download binary from [releases](https://github.com/8LWXpg/ptr/releases) page. 12 | 13 | ### Using `cargo-binstall` 14 | 15 | ``` 16 | cargo binstall --git https://github.com/8LWXpg/ptr ptr 17 | ``` 18 | 19 | ### Build from source 20 | 21 | ``` 22 | cargo install --git https://github.com/8LWXpg/ptr.git 23 | ``` 24 | 25 | ## Features 26 | 27 | - Easy to use command line interface with informative help messages. 28 | - Automatically download and install plugins from GitHub. 29 | - Restart PowerToys after installing or removing plugins. 30 | - Update all plugins with a single command. 31 | - Restore plugins from a configuration file. 32 | 33 | ## Quick Start 34 | 35 | ### For New Plugins 36 | 37 | Install a plugin with `add`: 38 | 39 | ``` 40 | ptr add GitHubRepo 8LWXpg/PowerToysRun-GitHubRepo 41 | ``` 42 | 43 | ### For Existing Plugins 44 | 45 | Add existing plugins with `init`: 46 | 47 | ``` 48 | ptr init 49 | ``` 50 | 51 | > [!NOTE] 52 | > This overrides existing config 53 | 54 | Then update with 55 | 56 | ``` 57 | ptr update --all 58 | ``` 59 | 60 | ### Useful tips 61 | 62 | A config file will be created at `%LOCALAPPDATA%\Microsoft\PowerToys\PowerToys Run\Plugins\version.toml`. Check [config](#config) for more detail. 63 | 64 | Check result with `list`: 65 | 66 | ``` 67 | ptr list 68 | ``` 69 | 70 | Use `help`, `-h` or `--help` to quickly check for usage: 71 | 72 | ``` 73 | ptr pin -h 74 | ptr pin add -h 75 | ``` 76 | 77 | Use alias to type commands faster: 78 | 79 | ``` 80 | ptr u -a 81 | ``` 82 | 83 | ## Config 84 | 85 | The following config needs to modify manually at `%LOCALAPPDATA%\Microsoft\PowerToys\PowerToys Run\Plugins\version.toml`: 86 | 87 | ```toml 88 | admin = true # Whether start and kill as admin 89 | token = 'token' # Token used when making request to GitHub. 90 | no_restart = false # Set true to not restart PowerToys after plugin modification 91 | ``` 92 | 93 | For the generated config structure, refer to [`test.toml`](./test/test.toml). 94 | 95 | ## Usage 96 | 97 | Check [usage.md](./usage.md) 98 | 99 | ## Limitations 100 | 101 | If you have any suggestions for these limitations, feel free to open an issue. 102 | 103 | - This tool only supports plugins hosted on GitHub. 104 | - The plugin release must be a zip file with either `x64` or `arm64` in the filename, or a pattern from `--pattern` is required. 105 | 106 | For more general pattern matching and downloading, check another tool I wrote: [gpm](https://github.com/8LWXpg/gpm). 107 | 108 | ## Why Rust? 109 | 110 | The `clap` crate in Rust is very powerful and easy to use for building command line applications, so I chose Rust to build this tool. 111 | -------------------------------------------------------------------------------- /generate_usage.ps1: -------------------------------------------------------------------------------- 1 | Push-Location 2 | Set-Location $PSScriptRoot 3 | 4 | cargo build 5 | 6 | $file = '.\usage.md' 7 | $content = Get-Content $file -Raw 8 | [regex]::Replace($content, '```(?!pwsh)(.+)\n[\s\S]+?```' , { param([System.Text.RegularExpressions.Match]$match) 9 | "``````$($match.Groups[1]) 10 | $((target\debug\ptr.exe "$($match.Groups[1])".Split(' ')) -join "`n") 11 | ``````" 12 | }) | Out-File -NoNewline -FilePath $file 13 | 14 | Pop-Location 15 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use colored::Colorize; 3 | use core::fmt; 4 | use serde::{Deserialize, Serialize, Serializer}; 5 | use std::borrow::Cow; 6 | use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}; 7 | use std::fs; 8 | use std::io::Write; 9 | use std::path::PathBuf; 10 | use tabwriter::TabWriter; 11 | 12 | use crate::polling; 13 | use crate::util::{get_powertoys_path, kill_ptr, start_ptr, ResultExit}; 14 | use crate::{add, error, exit, gh_dl, remove, up_to_date, CONFIG_PATH, PLUGIN_PATH}; 15 | 16 | #[derive(Serialize, Deserialize, Debug)] 17 | pub struct Config { 18 | arch: Arch, 19 | pt_path: PathBuf, 20 | /// Kill and run as admin 21 | admin: bool, 22 | /// Do not restart PowerToys after plugin modification 23 | no_restart: bool, 24 | token: Option, 25 | pin: Option>, 26 | /// GitHub auth token 27 | #[serde(serialize_with = "sort_keys")] 28 | plugins: HashMap, 29 | } 30 | 31 | #[derive(Serialize, Deserialize, Debug)] 32 | pub struct ImportConfig { 33 | plugins: HashMap, 34 | } 35 | 36 | #[derive(Serialize, Deserialize, Debug)] 37 | /// plugin.json metadata 38 | pub struct PluginMetadata { 39 | #[serde(rename = "Version")] 40 | version: String, 41 | #[serde(rename = "Website")] 42 | website: String, 43 | } 44 | 45 | fn sort_keys(value: &HashMap, serializer: S) -> Result 46 | where 47 | T: Serialize, 48 | S: Serializer, 49 | { 50 | value 51 | .iter() 52 | .collect::>() 53 | .serialize(serializer) 54 | } 55 | 56 | impl Config { 57 | pub fn new() -> Result { 58 | if CONFIG_PATH.exists() { 59 | Ok(toml::from_str(&fs::read_to_string(&*CONFIG_PATH).unwrap())?) 60 | } else { 61 | let pt_path = get_powertoys_path()?; 62 | Ok(Self { 63 | arch: Arch::default(), 64 | pt_path, 65 | admin: true, 66 | no_restart: false, 67 | token: None, 68 | pin: None, 69 | plugins: HashMap::new(), 70 | }) 71 | } 72 | } 73 | 74 | /// Try to find plugins and add to config 75 | pub fn init() -> Result { 76 | let plugins: HashMap = fs::read_dir(&*PLUGIN_PATH)? 77 | .filter_map(Result::ok) 78 | .filter(|e| e.path().is_dir()) 79 | .filter_map(|d| { 80 | let path = d.path(); 81 | let dir_name = path.file_name()?.to_str()?; 82 | let metadata_path = path.join("plugin.json"); 83 | if !metadata_path.exists() { 84 | return None; 85 | } 86 | // Strip bom from utf8 with bom 87 | let content = fs::read_to_string(metadata_path).ok()?; 88 | let content: Cow = if let Some(stripped) = content.strip_prefix("\u{FEFF}") { 89 | stripped.into() 90 | } else { 91 | content.into() 92 | }; 93 | 94 | let metadata: PluginMetadata = serde_json::from_str(&content) 95 | .inspect_err(|e| { 96 | error!("failed to deserialize '{}/plugin.json': {}", dir_name, e) 97 | }) 98 | .ok()?; 99 | let repo = metadata 100 | .website 101 | .strip_prefix("https://github.com/") 102 | .or_else(|| { 103 | error!( 104 | "invalid website url in {}: '{}'", 105 | dir_name, metadata.website 106 | ); 107 | None 108 | })? 109 | .to_string(); 110 | Some(( 111 | dir_name.into(), 112 | Plugin { 113 | repo, 114 | version: metadata.version, 115 | pattern: None, 116 | }, 117 | )) 118 | }) 119 | .collect(); 120 | let pt_path = get_powertoys_path()?; 121 | 122 | Ok(Self { 123 | arch: Arch::default(), 124 | pt_path, 125 | admin: true, 126 | no_restart: false, 127 | token: None, 128 | pin: None, 129 | plugins, 130 | }) 131 | } 132 | 133 | /// Ignore configs unrelated to plugins. 134 | pub fn import() -> Result { 135 | let pt_path = get_powertoys_path()?; 136 | let import_config: ImportConfig = 137 | toml::from_str(&fs::read_to_string(&*CONFIG_PATH).unwrap())?; 138 | Ok(Self { 139 | arch: Arch::default(), 140 | pt_path, 141 | admin: true, 142 | no_restart: false, 143 | token: None, 144 | pin: None, 145 | plugins: import_config.plugins, 146 | }) 147 | } 148 | 149 | /// Note: This method already used in the other methods. 150 | pub fn save(&self) -> Result<()> { 151 | fs::write(&*CONFIG_PATH, toml::to_string(self).unwrap()) 152 | .context("Failed to save config")?; 153 | Ok(()) 154 | } 155 | 156 | pub fn restart(&self) { 157 | kill_ptr(self.admin).exit_on_error(); 158 | start_ptr(&self.pt_path).exit_on_error(); 159 | } 160 | 161 | pub fn import_plugins(&mut self, no_restart: bool) { 162 | let no_restart = no_restart || self.no_restart; 163 | let mut new_plugins: HashMap = HashMap::new(); 164 | kill_ptr(self.admin).exit_on_error(); 165 | for (name, plugin) in &mut self.plugins { 166 | if let Err(e) = plugin.force_update(name, &self.arch, self.token.as_deref()) { 167 | if !no_restart { 168 | start_ptr(&self.pt_path).unwrap_or_else(|e| error!(e)); 169 | } 170 | exit!("Failed to import {}: {}", name, e) 171 | } else { 172 | add!(name, &plugin.version); 173 | new_plugins.insert(name.clone(), plugin.clone()); 174 | } 175 | } 176 | if !no_restart { 177 | start_ptr(&self.pt_path).unwrap_or_else(|e| error!(e)); 178 | } 179 | self.plugins = new_plugins; 180 | self.save().exit_on_error(); 181 | } 182 | 183 | pub fn add( 184 | &mut self, 185 | name: &str, 186 | repo: String, 187 | version: Option, 188 | pattern: Option, 189 | no_restart: bool, 190 | ) -> Result<()> { 191 | if let Entry::Vacant(e) = self.plugins.entry(name.to_string()) { 192 | let no_restart = no_restart || self.no_restart; 193 | kill_ptr(self.admin).exit_on_error(); 194 | let version = &e 195 | .insert(Plugin::add( 196 | name, 197 | repo, 198 | version, 199 | &self.arch, 200 | pattern, 201 | self.token.as_deref(), 202 | )?) 203 | .version; 204 | add!(name, version); 205 | if !no_restart { 206 | start_ptr(&self.pt_path).unwrap_or_else(|e| error!(e)); 207 | } 208 | self.save()?; 209 | Ok(()) 210 | } else { 211 | bail!("Plugin already exists") 212 | } 213 | } 214 | 215 | pub fn update(&mut self, names: Vec, versions: Option>, no_restart: bool) { 216 | let no_restart = no_restart || self.no_restart; 217 | kill_ptr(self.admin).exit_on_error(); 218 | 219 | // Update plugins with versions first. 220 | let without_versions = if let Some(versions) = versions { 221 | let (with_versions, without_versions) = names 222 | .split_at_checked(versions.len()) 223 | .unwrap_or((&names, &[])); 224 | for (name, version) in with_versions.iter().zip(versions) { 225 | let Some(plugin) = self.plugins.get_mut(name) else { 226 | continue; 227 | }; 228 | match plugin.update_to(name, &self.arch, &version, self.token.as_deref()) { 229 | Ok(updated) => { 230 | if updated { 231 | add!(name, plugin.version) 232 | } else { 233 | up_to_date!(name, plugin.version) 234 | } 235 | } 236 | Err(e) => error!(e), 237 | } 238 | } 239 | without_versions 240 | } else { 241 | &names 242 | }; 243 | for name in without_versions { 244 | let Some(plugin) = self.plugins.get_mut(name) else { 245 | continue; 246 | }; 247 | match plugin.update(name, &self.arch, self.token.as_deref()) { 248 | Ok(updated) => { 249 | if updated { 250 | add!(name, plugin.version) 251 | } else { 252 | up_to_date!(name, plugin.version) 253 | } 254 | } 255 | Err(e) => error!(e), 256 | } 257 | } 258 | if !no_restart { 259 | start_ptr(&self.pt_path).unwrap_or_else(|e| error!(e)); 260 | } 261 | self.save().exit_on_error(); 262 | } 263 | 264 | pub fn update_all(&mut self, no_restart: bool) { 265 | let no_restart = no_restart || self.no_restart; 266 | kill_ptr(self.admin).exit_on_error(); 267 | for (name, plugin) in &mut self.plugins { 268 | if let Some(pins) = &self.pin { 269 | if pins.contains(name) { 270 | continue; 271 | } 272 | } 273 | match plugin.update(name, &self.arch, self.token.as_deref()) { 274 | Ok(updated) => { 275 | if updated { 276 | add!(name, plugin.version) 277 | } else { 278 | up_to_date!(name, plugin.version) 279 | } 280 | } 281 | Err(e) => error!(e), 282 | } 283 | } 284 | if !no_restart { 285 | start_ptr(&self.pt_path).unwrap_or_else(|e| error!(e)); 286 | } 287 | self.save().exit_on_error(); 288 | } 289 | 290 | pub fn remove(&mut self, names: Vec, no_restart: bool) { 291 | let no_restart = no_restart || self.no_restart; 292 | kill_ptr(self.admin).exit_on_error(); 293 | for name in names { 294 | let Some(plugin) = self.plugins.get(&name) else { 295 | continue; 296 | }; 297 | match plugin.remove(&name) { 298 | Ok(_) => { 299 | self.plugins.remove(&name); 300 | remove!(name); 301 | } 302 | Err(e) => error!(e), 303 | } 304 | } 305 | if !no_restart { 306 | start_ptr(&self.pt_path).unwrap_or_else(|e| error!(e)); 307 | } 308 | self.save().exit_on_error(); 309 | } 310 | 311 | pub fn pin_add(&mut self, names: Vec) { 312 | if let Some(pins) = &mut self.pin { 313 | names.into_iter().for_each(|n| { 314 | pins.insert(n); 315 | }); 316 | } else { 317 | self.pin = Some(HashSet::from_iter(names)); 318 | } 319 | self.save().exit_on_error(); 320 | } 321 | 322 | pub fn pin_remove(&mut self, names: Vec) { 323 | let Some(pins) = &mut self.pin else { 324 | return; 325 | }; 326 | names.iter().for_each(|n| { 327 | pins.remove(n); 328 | }); 329 | self.save().exit_on_error(); 330 | } 331 | 332 | pub fn pin_list(&self) { 333 | if let Some(pins) = &self.pin { 334 | pins.iter().for_each(|n| println!("{n}")); 335 | } 336 | } 337 | 338 | pub fn pin_reset(&mut self) { 339 | self.pin = None; 340 | self.save().exit_on_error(); 341 | } 342 | } 343 | 344 | impl fmt::Display for Config { 345 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 346 | let mut tw = TabWriter::new(vec![]); 347 | writeln!(&mut tw, "{}", "Plugins:".bright_green()).unwrap(); 348 | let btree_map: BTreeMap<_, _> = self.plugins.iter().collect(); 349 | for (name, plugin) in btree_map { 350 | writeln!( 351 | &mut tw, 352 | " {}\t{}\t{}\t{}", 353 | name.bright_cyan(), 354 | plugin.repo, 355 | plugin.version, 356 | match &plugin.pattern { 357 | Some(pattern) => pattern, 358 | None => "", 359 | }, 360 | ) 361 | .unwrap(); 362 | } 363 | tw.flush().unwrap(); 364 | write!( 365 | f, 366 | "{}", 367 | String::from_utf8(tw.into_inner().unwrap()).unwrap() 368 | ) 369 | } 370 | } 371 | 372 | #[derive(Serialize, Deserialize, Clone, Debug)] 373 | pub enum Arch { 374 | #[serde(rename = "x64")] 375 | X64, 376 | #[serde(rename = "arm64")] 377 | ARM64, 378 | } 379 | 380 | impl Default for Arch { 381 | fn default() -> Self { 382 | match std::env::consts::ARCH { 383 | "x86_64" => Arch::X64, 384 | "aarch64" => Arch::ARM64, 385 | _ => unreachable!(), 386 | } 387 | } 388 | } 389 | 390 | impl fmt::Display for Arch { 391 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 392 | match self { 393 | Arch::X64 => write!(f, "x64"), 394 | Arch::ARM64 => write!(f, "arm64"), 395 | } 396 | } 397 | } 398 | 399 | #[derive(Serialize, Deserialize, Clone, Debug)] 400 | struct Plugin { 401 | repo: String, 402 | version: String, 403 | pattern: Option, 404 | } 405 | 406 | impl Plugin { 407 | /// Add a plugin with the specified version, None for the latest version. 408 | fn add( 409 | name: &str, 410 | repo: String, 411 | version: Option, 412 | arch: &Arch, 413 | pattern: Option, 414 | token: Option<&str>, 415 | ) -> Result { 416 | let version = gh_dl!( 417 | name, 418 | &repo, 419 | version.as_deref(), 420 | arch, 421 | pattern.as_deref(), 422 | token 423 | )?; 424 | Ok(Self { 425 | repo, 426 | version, 427 | pattern, 428 | }) 429 | } 430 | 431 | /// Update the plugin to the latest version. 432 | /// Return `true` if the version is updated. 433 | fn update(&mut self, name: &str, arch: &Arch, token: Option<&str>) -> Result { 434 | let version = gh_dl!( 435 | name, 436 | &self.repo, 437 | None, 438 | arch, 439 | &self.version, 440 | self.pattern.as_deref(), 441 | token 442 | ) 443 | .context(format!("Failed to update {}", name))?; 444 | if version != self.version { 445 | self.version = version; 446 | Ok(true) 447 | } else { 448 | Ok(false) 449 | } 450 | } 451 | 452 | /// Update the plugin to specific version. 453 | /// Return `true` if the version is updated. 454 | fn update_to( 455 | &mut self, 456 | name: &str, 457 | arch: &Arch, 458 | version: &str, 459 | token: Option<&str>, 460 | ) -> Result { 461 | let version = gh_dl!( 462 | name, 463 | &self.repo, 464 | Some(version), 465 | arch, 466 | &self.version, 467 | self.pattern.as_deref(), 468 | token 469 | ) 470 | .context(format!("Failed to update {}", name))?; 471 | if version != self.version { 472 | self.version = version; 473 | Ok(true) 474 | } else { 475 | Ok(false) 476 | } 477 | } 478 | 479 | /// Update without checking current version. 480 | fn force_update(&mut self, name: &str, arch: &Arch, token: Option<&str>) -> Result<()> { 481 | let version = gh_dl!( 482 | name, 483 | &self.repo, 484 | None, 485 | arch, 486 | &self.version, 487 | self.pattern.as_deref(), 488 | token 489 | )?; 490 | self.version = version; 491 | Ok(()) 492 | } 493 | 494 | /// Remove the `PLUGIN_PATH/name` directory. 495 | fn remove(&self, name: &str) -> Result<()> { 496 | polling::remove_dir_all(&*PLUGIN_PATH.join(name)) 497 | .context(format!("Failed to remove {}", name))?; 498 | Ok(()) 499 | } 500 | } 501 | 502 | #[cfg(test)] 503 | mod tests { 504 | use std::io::Read; 505 | 506 | use super::*; 507 | 508 | #[test] 509 | fn generate_toml() { 510 | let config = Config { 511 | arch: Arch::X64, 512 | admin: true, 513 | no_restart: false, 514 | pin: None, 515 | token: None, 516 | pt_path: "C:/Program Files/PowerToys/PowerToys.exe".into(), 517 | plugins: HashMap::new(), 518 | }; 519 | let toml = toml::to_string_pretty(&config).unwrap(); 520 | let mut file = fs::File::create("./test/test.toml").unwrap(); 521 | file.write_all(toml.as_bytes()).unwrap(); 522 | } 523 | 524 | #[test] 525 | fn test_breaking_config() { 526 | let mut file = fs::File::open("./test/test.toml").unwrap(); 527 | let mut toml = String::new(); 528 | file.read_to_string(&mut toml).unwrap(); 529 | let config: Config = toml::from_str(&toml).unwrap(); 530 | println!("{:?}", config); 531 | } 532 | } 533 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod polling; 3 | mod util; 4 | 5 | use clap::{builder::styling, CommandFactory, Parser, Subcommand}; 6 | use clap_complete::aot::PowerShell; 7 | use colored::Colorize; 8 | use std::{env, io, path::PathBuf, process::Command, sync::LazyLock}; 9 | use util::{self_update, ResultExit}; 10 | 11 | static PLUGIN_PATH: LazyLock = LazyLock::new(|| { 12 | PathBuf::from(&env::var("LOCALAPPDATA").unwrap()).join(r"Microsoft\PowerToys\PowerToys Run\Plugins") 13 | }); 14 | static CONFIG_PATH: LazyLock = LazyLock::new(|| { 15 | PathBuf::from(&env::var("LOCALAPPDATA").unwrap()).join(r"Microsoft\PowerToys\PowerToys Run\Plugins\version.toml") 16 | }); 17 | 18 | #[derive(Parser)] 19 | #[clap( 20 | version, 21 | name = "ptr", 22 | about = "PowerToys Run Plugin Manager", 23 | styles = get_styles(), 24 | arg_required_else_help = true 25 | )] 26 | struct App { 27 | #[clap(subcommand)] 28 | cmd: TopCommand, 29 | 30 | #[clap(default_value = "false", long)] 31 | /// Do not restart PowerToys after plugin modification 32 | no_restart: bool, 33 | } 34 | 35 | #[derive(Subcommand)] 36 | enum TopCommand { 37 | #[clap()] 38 | /// Try to find and add existing plugins to config 39 | Init, 40 | 41 | #[clap(visible_alias = "a", arg_required_else_help = true)] 42 | /// Add a plugin 43 | Add { 44 | /// Plugin name, can be anything 45 | name: String, 46 | /// GitHub repository identifier or URL of the plugin 47 | repo: String, 48 | #[clap(short, long)] 49 | /// Target version 50 | version: Option, 51 | #[clap(short, long)] 52 | /// Asset match pattern (rust regex) 53 | pattern: Option, 54 | }, 55 | 56 | #[clap(visible_alias = "u", arg_required_else_help = true)] 57 | /// Update plugins 58 | Update { 59 | #[clap(num_args = 1..)] 60 | /// Name of the plugins to update 61 | name: Vec, 62 | #[clap(short, long)] 63 | /// Update all plugins 64 | all: bool, 65 | #[clap(short, long)] 66 | /// Version to update 67 | version: Option>, 68 | }, 69 | 70 | #[clap(visible_alias = "r", arg_required_else_help = true)] 71 | /// Remove plugins 72 | Remove { 73 | #[clap(num_args = 1..)] 74 | /// Name of the plugins to remove. 75 | name: Vec, 76 | }, 77 | 78 | #[clap(visible_alias = "l")] 79 | /// List all installed plugins 80 | List, 81 | 82 | #[clap(visible_alias = "p", arg_required_else_help = true)] 83 | /// Pin plugins so it's not updated with `update --all`. 84 | Pin { 85 | #[clap(subcommand)] 86 | cmd: PinSubcommand, 87 | }, 88 | 89 | #[clap(visible_alias = "i")] 90 | /// Import plugins from configuration file 91 | Import { 92 | #[clap(short, long)] 93 | /// Update the configuration file without downloading the plugin 94 | dry_run: bool, 95 | }, 96 | 97 | #[clap()] 98 | /// Restart PowerToys 99 | Restart, 100 | 101 | #[clap()] 102 | /// Open config file in default editor 103 | Edit, 104 | 105 | #[clap()] 106 | /// Self update to latest 107 | SelfUpdate, 108 | 109 | #[clap()] 110 | /// Generate shell completion (PowerShell) 111 | Completion, 112 | } 113 | 114 | #[derive(Subcommand)] 115 | enum PinSubcommand { 116 | #[clap(visible_alias = "a")] 117 | /// Add pins. 118 | Add { 119 | #[clap(num_args = 1..)] 120 | /// The name of the plugins to pin. 121 | name: Vec, 122 | }, 123 | #[clap(visible_alias = "r")] 124 | /// Remove pins 125 | Remove { 126 | #[clap(num_args = 1..)] 127 | /// The name of the plugins to pin. 128 | name: Vec, 129 | }, 130 | #[clap(visible_alias = "l")] 131 | /// List pins. 132 | List, 133 | /// Clear all pins. 134 | Reset, 135 | } 136 | 137 | fn get_styles() -> clap::builder::Styles { 138 | clap::builder::Styles::default() 139 | .usage(styling::AnsiColor::BrightGreen.on_default()) 140 | .header(styling::AnsiColor::BrightGreen.on_default()) 141 | .literal(styling::AnsiColor::BrightCyan.on_default()) 142 | .invalid(styling::AnsiColor::BrightYellow.on_default()) 143 | .error(styling::AnsiColor::BrightRed.on_default().bold()) 144 | .valid(styling::AnsiColor::BrightGreen.on_default()) 145 | .placeholder(styling::AnsiColor::Cyan.on_default()) 146 | } 147 | 148 | fn main() { 149 | let args = App::parse(); 150 | match args.cmd { 151 | TopCommand::Init => { 152 | if PathBuf::from(&*CONFIG_PATH).exists() 153 | && util::prompt("Found existing config, override? [y/N]: ").exit_on_error() != "y" 154 | { 155 | return; 156 | } 157 | let config = config::Config::init().exit_on_error(); 158 | println!("{config}"); 159 | println!( 160 | "{} Some plugin may failed to find due to incomplete metadata.", 161 | "Note:".bright_blue() 162 | ); 163 | println!(" If that occurs, please contact the plugin author."); 164 | config.save().exit_on_error(); 165 | } 166 | TopCommand::Import { dry_run } => { 167 | let mut config = config::Config::import().exit_on_error(); 168 | if dry_run { 169 | config.save().exit_on_error(); 170 | } else { 171 | config.import_plugins(args.no_restart); 172 | } 173 | } 174 | TopCommand::SelfUpdate => self_update().exit_on_error(), 175 | _ => { 176 | let mut config = config::Config::new().exit_on_error(); 177 | match args.cmd { 178 | TopCommand::Add { 179 | name, 180 | repo, 181 | version, 182 | pattern, 183 | } => config 184 | .add( 185 | &name, 186 | if let Some(repo) = repo.strip_prefix("https://github.com/") { 187 | repo.to_string() 188 | } else { 189 | repo 190 | }, 191 | version, 192 | pattern, 193 | args.no_restart, 194 | ) 195 | .exit_on_error(), 196 | TopCommand::Update { name, all, version } => { 197 | if all { 198 | config.update_all(args.no_restart); 199 | } else { 200 | config.update(name, version, args.no_restart); 201 | } 202 | } 203 | TopCommand::Remove { name } => config.remove(name, args.no_restart), 204 | TopCommand::Edit => { 205 | _ = Command::new("cmd") 206 | .args(["/c", (*CONFIG_PATH).to_str().unwrap()]) 207 | .status() 208 | .unwrap_or_else(|e| exit!(e)) 209 | } 210 | TopCommand::Pin { cmd } => match cmd { 211 | PinSubcommand::Add { name } => config.pin_add(name), 212 | PinSubcommand::List => config.pin_list(), 213 | PinSubcommand::Remove { name } => config.pin_remove(name), 214 | PinSubcommand::Reset => config.pin_reset(), 215 | }, 216 | TopCommand::List => print!("{}", config), 217 | TopCommand::Restart => config.restart(), 218 | TopCommand::Completion => { 219 | clap_complete::generate(PowerShell, &mut App::command(), "ptr", &mut io::stdout()) 220 | } 221 | _ => unreachable!(), 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/polling.rs: -------------------------------------------------------------------------------- 1 | //! Retry IO operations on error. 2 | 3 | use std::io::{Read, Write}; 4 | use std::path::Path; 5 | use std::thread; 6 | use std::time::Duration; 7 | use std::{fs, io}; 8 | 9 | const MAX_RETRIES: u32 = 10; 10 | const RETRY_DELAY: Duration = Duration::from_millis(50); 11 | 12 | fn retry(mut operation: F) -> Result 13 | where 14 | F: FnMut() -> Result, 15 | E: Into, 16 | { 17 | let mut last_error = None; 18 | 19 | for _ in 0..MAX_RETRIES { 20 | match operation() { 21 | Ok(result) => return Ok(result), 22 | Err(err) => { 23 | last_error = Some(err.into()); 24 | thread::sleep(RETRY_DELAY); 25 | } 26 | } 27 | } 28 | 29 | Err(last_error.unwrap_or_else(|| io::Error::new(io::ErrorKind::Other, "Max retries reached"))) 30 | } 31 | 32 | /// Wrapper around `io::copy` that retries on errors. 33 | pub fn copy(reader: &mut R, writer: &mut W) -> io::Result 34 | where 35 | R: ?Sized, 36 | W: ?Sized, 37 | R: Read, 38 | W: Write, 39 | { 40 | retry(|| io::copy(reader, writer)) 41 | } 42 | 43 | /// Wrapper around `fs::remove_dir_all` that retries on errors. 44 | pub fn remove_dir_all>(path: P) -> io::Result<()> { 45 | retry(|| fs::remove_dir_all(&path)) 46 | } 47 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail, Ok, Result}; 2 | use colored::Colorize; 3 | use regex::Regex; 4 | use reqwest::blocking::Client; 5 | use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION, USER_AGENT}; 6 | use serde::Deserialize; 7 | use std::fs::{self, File}; 8 | use std::io::{self, Write}; 9 | use std::path::{Path, PathBuf}; 10 | use std::process::Command; 11 | use std::{env, mem}; 12 | use zip::ZipArchive; 13 | 14 | use crate::config::Arch; 15 | use crate::polling; 16 | use crate::PLUGIN_PATH; 17 | use crate::{error, exit}; 18 | 19 | #[derive(Deserialize)] 20 | struct ApiResponse { 21 | tag_name: String, 22 | assets: Box<[Assets]>, 23 | } 24 | 25 | #[derive(Deserialize)] 26 | struct Assets { 27 | name: String, 28 | browser_download_url: String, 29 | } 30 | 31 | impl Assets { 32 | /// Currently match for upper and lower case arch names. 33 | /// 34 | fn is_arch(&self, arch: &Arch) -> bool { 35 | let arch = &arch.to_string(); 36 | (self.name.contains(arch) || self.name.contains(&arch.to_uppercase())) 37 | && self.name.ends_with(".zip") 38 | } 39 | } 40 | 41 | #[macro_export] 42 | macro_rules! gh_dl { 43 | ($root_name:expr, $repo:expr, $version:expr, $arch:expr, $pattern:expr, $token:expr) => { 44 | $crate::util::gh_dl($root_name, $repo, $version, $arch, None, $pattern, $token) 45 | }; 46 | ($root_name:expr, $repo:expr, $version:expr, $arch:expr, $current_version:expr, $pattern:expr, $token:expr) => { 47 | $crate::util::gh_dl( 48 | $root_name, 49 | $repo, 50 | $version, 51 | $arch, 52 | Some($current_version), 53 | $pattern, 54 | $token, 55 | ) 56 | }; 57 | } 58 | 59 | /// Download a file from a GitHub repository. 60 | /// 61 | /// # Arguments 62 | /// 63 | /// * `repo` - Repository identifier. 64 | /// * `version` - Tagged version. 65 | /// * `arch` - Architecture of the system, either x64 or arm64. 66 | /// * `current_version` - Current tagged version. 67 | /// * `pattern` - Match pattern for assets. 68 | /// * `token` - GitHub auth token. 69 | /// 70 | /// # Returns 71 | /// The version of the repository that was downloaded. 72 | pub fn gh_dl( 73 | root_name: &str, 74 | repo: &str, 75 | version: Option<&str>, 76 | arch: &Arch, 77 | current_version: Option<&str>, 78 | pattern: Option<&str>, 79 | token: Option<&str>, 80 | ) -> Result { 81 | let url = if let Some(version) = version { 82 | format!("https://api.github.com/repos/{repo}/releases/tags/{version}") 83 | } else { 84 | format!("https://api.github.com/repos/{repo}/releases/latest") 85 | }; 86 | let mut headers = HeaderMap::new(); 87 | headers.insert(USER_AGENT, "reqwest".parse().unwrap()); 88 | headers.insert(ACCEPT, "application/vnd.github+json".parse().unwrap()); 89 | headers.insert("X-GitHub-Api-Version", "2022-11-28".parse().unwrap()); 90 | if let Some(token) = token { 91 | headers.insert(AUTHORIZATION, format!("Bearer {token}").parse().unwrap()); 92 | } 93 | let res = Client::new().get(&url).headers(headers).send()?; 94 | if !res.status().is_success() { 95 | bail!( 96 | "Failed to fetch {}: {}", 97 | version.unwrap_or("latest"), 98 | res.status().canonical_reason().unwrap_or("Unknown"), 99 | ); 100 | } 101 | let res: ApiResponse = res.json()?; 102 | let tag = res.tag_name; 103 | if let Some(current_version) = current_version { 104 | if tag == current_version { 105 | return Ok(current_version.to_string()); 106 | } 107 | } 108 | 109 | let assets = res.assets; 110 | let asset = match assets.iter().find(|a| { 111 | if let Some(pattern) = pattern { 112 | let p = Regex::new(pattern).unwrap_or_else(|e| { 113 | exit!(anyhow!(e).context(format!("Invalid regex pattern: '{}': ", pattern))) 114 | }); 115 | p.is_match(&a.name) 116 | } else { 117 | a.is_arch(arch) 118 | } 119 | }) { 120 | Some(asset) => asset, 121 | None => manual_select(&assets)?, 122 | }; 123 | let (url, name) = (&asset.browser_download_url, &asset.name); 124 | let res = Client::new().get(url).send()?; 125 | 126 | let file_path = PLUGIN_PATH.join(name); 127 | let mut file = File::create(&file_path)?; 128 | file.write_all(&res.bytes()?)?; 129 | 130 | extract_zip(&file_path, root_name)?; 131 | fs::remove_file(&file_path)?; 132 | 133 | Ok(tag) 134 | } 135 | 136 | fn manual_select(assets: &[Assets]) -> Result<&Assets> { 137 | if assets.len() == 1 { 138 | return Ok(&assets[0]); 139 | } 140 | 141 | for (i, asset) in assets.iter().enumerate() { 142 | println!("{}: {}", i.to_string().bright_yellow(), asset.name); 143 | } 144 | let index: usize = prompt("Fail to match assets, please select one: ")?.parse()?; 145 | assets.get(index).ok_or(anyhow!("Invalid index")) 146 | } 147 | 148 | fn extract_zip(zip_path: &Path, root_name: &str) -> Result<()> { 149 | let mut archive = ZipArchive::new(File::open(zip_path)?)?; 150 | env::set_current_dir(&*PLUGIN_PATH)?; 151 | 152 | // Locate for.dll file and find it's parent 153 | let dll = archive 154 | .file_names() 155 | .find(|f| f.ends_with(".dll")) 156 | .ok_or(anyhow!("No .dll file found in zip"))? 157 | .to_owned(); 158 | let parent = Path::new(&dll).parent().unwrap_or(Path::new("")); 159 | 160 | // Extract all files and keep the directory structure 161 | let root = PathBuf::from(root_name); 162 | for i in 0..archive.len() { 163 | let mut file = archive.by_index(i)?; 164 | 165 | let out_path = 166 | if let std::result::Result::Ok(path) = Path::new(file.name()).strip_prefix(parent) { 167 | root.join(path) 168 | } else { 169 | error!("Unexpected file in zip at {}", file.name()); 170 | continue; 171 | }; 172 | 173 | if file.is_dir() { 174 | fs::create_dir_all(out_path)?; 175 | } else { 176 | if let Some(p) = out_path.parent() { 177 | fs::create_dir_all(p)?; 178 | } 179 | let mut out_file = File::create(out_path)?; 180 | polling::copy(&mut file, &mut out_file)?; 181 | } 182 | } 183 | 184 | Ok(()) 185 | } 186 | 187 | fn run_process(program: &str, args: &str, admin: bool) -> Result<()> { 188 | use windows::core::{w, HSTRING, PCWSTR}; 189 | use windows::Win32::Foundation::CloseHandle; 190 | use windows::Win32::System::Threading::{GetExitCodeProcess, WaitForSingleObject, INFINITE}; 191 | use windows::Win32::UI::Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW}; 192 | 193 | let mut sei: SHELLEXECUTEINFOW = unsafe { mem::zeroed() }; 194 | sei.cbSize = mem::size_of::() as u32; 195 | sei.fMask = SEE_MASK_NOCLOSEPROCESS; 196 | if admin { 197 | sei.lpVerb = w!("runas"); 198 | } 199 | let h_file = HSTRING::from(program); 200 | sei.lpFile = PCWSTR(h_file.as_ptr()); 201 | sei.nShow = 0; // SW_HIDE 202 | let h_args = HSTRING::from(args); 203 | sei.lpParameters = PCWSTR(h_args.as_ptr()); 204 | 205 | unsafe { 206 | ShellExecuteExW(&mut sei)?; 207 | let process = sei.hProcess; 208 | _ = WaitForSingleObject(process, INFINITE); 209 | let mut exit_code = 0; 210 | GetExitCodeProcess(process, &mut exit_code)?; 211 | CloseHandle(process)?; 212 | 213 | if exit_code == 0 { 214 | Ok(()) 215 | } else { 216 | Err(io::Error::from_raw_os_error(exit_code as i32).into()) 217 | } 218 | } 219 | } 220 | 221 | pub fn kill_ptr(admin: bool) -> Result<()> { 222 | run_process("taskkill.exe", "/F /FI \"IMAGENAME eq PowerToys*\"", admin) 223 | .map_err(|e| anyhow!("Failed to kill PowerToys: {}", e))?; 224 | Ok(()) 225 | } 226 | 227 | pub fn start_ptr(powertoys_path: &Path) -> Result<()> { 228 | let c = Command::new(powertoys_path) 229 | .spawn() 230 | .map_err(|e| anyhow!("Failed to start PowerToys: {}", e))?; 231 | mem::forget(c); 232 | Ok(()) 233 | } 234 | 235 | pub fn get_powertoys_path() -> Result { 236 | let possible_paths = [ 237 | PathBuf::from(env::var("ProgramFiles").unwrap_or_default()), 238 | PathBuf::from(env::var("LOCALAPPDATA").unwrap_or_default()), 239 | ] 240 | .map(|p| p.join(r"PowerToys\PowerToys.exe")); 241 | for path in possible_paths { 242 | if path.exists() { 243 | return Ok(path); 244 | } 245 | } 246 | prompt("PowerToys executable not found in any of the expected locations\nEnter path: ") 247 | .map(|s| s.into()) 248 | } 249 | 250 | /// Prompt the user for string input. 251 | pub fn prompt(msg: &str) -> Result { 252 | let mut input = String::new(); 253 | print!("{msg}"); 254 | io::stdout().flush()?; 255 | io::stdin().read_line(&mut input)?; 256 | Ok(input.trim().to_string()) 257 | } 258 | 259 | pub fn self_update() -> Result<()> { 260 | use crate::{add, up_to_date}; 261 | 262 | // download asset 263 | let current_version = env!("CARGO_PKG_VERSION"); 264 | let url = "https://api.github.com/repos/8LWXpg/ptr/releases/latest"; 265 | let mut headers = HeaderMap::new(); 266 | headers.insert(USER_AGENT, "reqwest".parse().unwrap()); 267 | headers.insert(ACCEPT, "application/vnd.github+json".parse().unwrap()); 268 | headers.insert("X-GitHub-Api-Version", "2022-11-28".parse().unwrap()); 269 | let res = Client::new().get(url).headers(headers).send()?; 270 | if !res.status().is_success() { 271 | bail!( 272 | "Failed to fetch latest: {}", 273 | res.status().canonical_reason().unwrap_or("Unknown"), 274 | ); 275 | } 276 | let res: ApiResponse = res.json()?; 277 | let tag = res.tag_name; 278 | if tag == format!("v{current_version}") { 279 | up_to_date!("ptr", current_version); 280 | return Ok(()); 281 | } 282 | 283 | let assets = res.assets; 284 | let asset = assets 285 | .iter() 286 | .find(|a| a.name.contains(std::env::consts::ARCH)) 287 | .unwrap(); 288 | let (url, name) = (&asset.browser_download_url, &asset.name); 289 | let res = Client::new().get(url).send()?; 290 | 291 | let file_path = env::temp_dir().join(name); 292 | File::create(&file_path)?.write_all(&res.bytes()?)?; 293 | 294 | // extract and self replace 295 | let mut archive = ZipArchive::new(File::open(&file_path)?)?; 296 | let out_path = env::temp_dir().join("ptr.exe"); 297 | let mut out_file = File::create(&out_path)?; 298 | io::copy(&mut archive.by_name("ptr.exe")?, &mut out_file)?; 299 | self_replace::self_replace(&out_path)?; 300 | fs::remove_file(&file_path)?; 301 | fs::remove_file(&out_path)?; 302 | add!("ptr", tag); 303 | Ok(()) 304 | } 305 | 306 | // region: macro 307 | #[macro_export] 308 | macro_rules! print_message { 309 | ($symbol:expr, $color:ident, $msg:expr) => { 310 | println!("{} {}", $symbol.$color().bold(), $msg) 311 | }; 312 | ($symbol:expr, $color:ident, $fmt:expr, $($arg:tt)*) => { 313 | println!("{} {}", $symbol.$color().bold(), format!($fmt, $($arg)*)) 314 | }; 315 | } 316 | 317 | /// Print message as following format for adding an item. 318 | /// 319 | /// `+ name@version` 320 | #[macro_export] 321 | macro_rules! add { 322 | ($name:expr, $version:expr) => { 323 | $crate::print_message!("+", bright_green, "{}@{}", $name, $version) 324 | }; 325 | } 326 | 327 | /// Print message as following format for item that is up to date. 328 | /// 329 | /// `= name@version` 330 | #[macro_export] 331 | macro_rules! up_to_date { 332 | ($name:expr, $version:expr) => { 333 | $crate::print_message!("=", bright_blue, "{}@{}", $name, $version) 334 | }; 335 | } 336 | 337 | /// Print message as following format for removing an item. 338 | /// 339 | /// `- name` 340 | #[macro_export] 341 | macro_rules! remove { 342 | ($name:expr) => { 343 | $crate::print_message!("-", bright_red, $name) 344 | }; 345 | } 346 | 347 | /// Print an error message to stderr. 348 | #[macro_export] 349 | macro_rules! error { 350 | ($msg:expr) => {{ 351 | use colored::Colorize; 352 | eprintln!("{} {:#}", "error:".bright_red().bold(), $msg) 353 | }}; 354 | ($fmt:expr, $($arg:tt)*) => {{ 355 | use colored::Colorize; 356 | eprintln!("{} {}", "error:".bright_red().bold(), format!($fmt, $($arg)*)) 357 | }}; 358 | } 359 | 360 | /// Print an error message to stderr and exit with code 1. 361 | #[macro_export] 362 | macro_rules! exit { 363 | ($($arg:tt)*) => {{ 364 | $crate::error!($($arg)*); 365 | std::process::exit(1); 366 | }}; 367 | } 368 | // endregion 369 | 370 | pub trait ResultExit { 371 | /// Exit the program with error code 1 if Result is Err, otherwise return the Ok value 372 | fn exit_on_error(self) -> T; 373 | } 374 | 375 | impl ResultExit for Result { 376 | fn exit_on_error(self) -> T { 377 | self.unwrap_or_else(|e| exit!(e)) 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /test/test.toml: -------------------------------------------------------------------------------- 1 | arch = "x64" 2 | pt_path = "C:/Program Files/PowerToys/PowerToys.exe" 3 | admin = true 4 | pins = ["GitHubRepo"] 5 | token = 'token' 6 | 7 | [plugins.GitHubRepo] 8 | repo = "8LWXpg/PowerToysRun-GitHubRepo" 9 | version = "v1.9.0" 10 | 11 | [plugins.ProcessKiller] 12 | repo = "8LWXpg/PowerToysRun-ProcessKiller" 13 | version = "v1.9.0" 14 | -------------------------------------------------------------------------------- /usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ```help 4 | PowerToys Run Plugin Manager 5 | 6 | Usage: ptr.exe [OPTIONS] 7 | 8 | Commands: 9 | init Try to find and add existing plugins to config 10 | add Add a plugin [aliases: a] 11 | update Update plugins [aliases: u] 12 | remove Remove plugins [aliases: r] 13 | list List all installed plugins [aliases: l] 14 | pin Pin plugins so it's not updated with `update --all` [aliases: p] 15 | import Import plugins from configuration file [aliases: i] 16 | restart Restart PowerToys 17 | edit Open config file in default editor 18 | self-update Self update to latest 19 | completion Generate shell completion (PowerShell) 20 | help Print this message or the help of the given subcommand(s) 21 | 22 | Options: 23 | --no-restart Do not restart PowerToys after plugin modification 24 | -h, --help Print help 25 | -V, --version Print version 26 | ``` 27 | 28 | ## Init 29 | 30 | ```init --help 31 | Try to find and add existing plugins to config 32 | 33 | Usage: ptr.exe init 34 | 35 | Options: 36 | -h, --help Print help 37 | ``` 38 | 39 | ## Add 40 | 41 | ```add --help 42 | Add a plugin 43 | 44 | Usage: ptr.exe add [OPTIONS] 45 | 46 | Arguments: 47 | Plugin name, can be anything 48 | GitHub repository identifier or URL of the plugin 49 | 50 | Options: 51 | -v, --version Target version 52 | -p, --pattern Asset match pattern (rust regex) 53 | -h, --help Print help 54 | ``` 55 | 56 | e.g. 57 | 58 | ``` 59 | ptr a GitHubRepo 8LWXpg/PowerToysRun-GitHubRepo 60 | ``` 61 | 62 | ## Update 63 | 64 | ```update --help 65 | Update plugins 66 | 67 | Usage: ptr.exe update [OPTIONS] [NAME]... 68 | 69 | Arguments: 70 | [NAME]... Name of the plugins to update 71 | 72 | Options: 73 | -a, --all Update all plugins 74 | -v, --version Version to update 75 | -h, --help Print help 76 | ``` 77 | 78 | e.g. 79 | 80 | ``` 81 | ptr u -a 82 | ``` 83 | 84 | ``` 85 | ptr u Plugin1 Plugin2 -v v1.1.0 -v 1.2.0 86 | ``` 87 | 88 | ## Remove 89 | 90 | ```remove --help 91 | Remove plugins 92 | 93 | Usage: ptr.exe remove [NAME]... 94 | 95 | Arguments: 96 | [NAME]... Name of the plugins to remove 97 | 98 | Options: 99 | -h, --help Print help 100 | ``` 101 | 102 | e.g. 103 | 104 | ``` 105 | ptr r GitHubRepo ProcessKiller 106 | ``` 107 | 108 | ## List 109 | 110 | ``` 111 | Usage: ptr.exe list 112 | ``` 113 | 114 | ## Pin 115 | 116 | ```pin --help 117 | Pin plugins so it's not updated with `update --all` 118 | 119 | Usage: ptr.exe pin 120 | 121 | Commands: 122 | add Add pins [aliases: a] 123 | remove Remove pins [aliases: r] 124 | list List pins [aliases: l] 125 | reset Clear all pins 126 | help Print this message or the help of the given subcommand(s) 127 | 128 | Options: 129 | -h, --help Print help 130 | ``` 131 | 132 | ## Import 133 | 134 | This reads the configuration file at `%LOCALAPPDATA%\Microsoft\PowerToys\PowerToys Run\Plugins\version.toml`. 135 | 136 | ```import --help 137 | Import plugins from configuration file 138 | 139 | Usage: ptr.exe import [OPTIONS] 140 | 141 | Options: 142 | -d, --dry-run Update the configuration file without downloading the plugin 143 | -h, --help Print help 144 | ``` 145 | 146 | ## Restart 147 | 148 | ``` 149 | Usage: ptr.exe restart 150 | ``` 151 | 152 | ## Edit 153 | 154 | ``` 155 | Usage: ptr.exe edit 156 | ``` 157 | 158 | ## Self Update 159 | 160 | ``` 161 | Usage: ptr.exe self-update 162 | ``` 163 | 164 | ## Completion 165 | 166 | ``` 167 | Usage: ptr.exe completion 168 | ``` 169 | 170 | Add this line in your PowerShell `$PROFILE`: 171 | 172 | ```pwsh 173 | (ptr completion) -join "`n" | iex 174 | ``` --------------------------------------------------------------------------------