teams-cli 0.2.4__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {teams_cli-0.2.4 → teams_cli-0.2.6}/CHANGELOG.md +18 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/Cargo.lock +167 -6
- {teams_cli-0.2.4 → teams_cli-0.2.6}/Cargo.toml +5 -3
- {teams_cli-0.2.4 → teams_cli-0.2.6}/PKG-INFO +11 -1
- {teams_cli-0.2.4 → teams_cli-0.2.6}/README.md +10 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/install.rs +216 -27
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/macos.rs +33 -5
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop.js +32 -7
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop.rs +4 -3
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop_cache.js +9 -2
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop_people.js +34 -27
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/tui.rs +91 -2
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/desktop_scripts.test.cjs +67 -7
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/js/people.test.mjs +2 -1
- {teams_cli-0.2.4 → teams_cli-0.2.6}/.github/workflows/ci.yml +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/.github/workflows/dependencies.yml +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/.github/workflows/release.yml +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/.gitignore +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/.mise.toml +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/LICENSE +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/Makefile +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/build.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/docs/releases.md +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/examples/validate_schema.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/pyproject.toml +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/scripts/smoke-release.py +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/auth/pass.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/auth/process.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/auth/wsl.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/auth/wsl_credentials.ps1 +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/auth.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/backend.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/cli.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/config.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/delivery.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/avatars.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/browser.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/browser_children.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/browser_core.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/browser_install.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/browser_lifetime.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/browser_setup.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/cdp.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/connection.ps1 +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/connection.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/helper.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/owner.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/session.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop/windows.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop_core.js +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/desktop_send.js +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/error.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/graph.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/lib.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/main.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/output.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/render.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/schema.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/setup/capabilities.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/setup.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/src/teams_cli_alias.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/browser_lifetime.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/cli.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/connection.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/connection.test.ps1 +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/credentials.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/desktop.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/desktop_scripts.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/fixtures/clispec-v0.3.json +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/package_alias.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/pass_credentials.sh +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/release_smoke.rs +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/tests/wsl_credentials.test.ps1 +0 -0
- {teams_cli-0.2.4 → teams_cli-0.2.6}/vership.toml +0 -0
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.6](https://github.com/rvben/teams-cli/compare/v0.2.5...v0.2.6) - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `teams people search` finds work and school colleagues again with the desktop backend. It now uses the same directory search as the Teams people picker; the lookup it used before returned nothing, or failed with "Teams could not resolve this person" in a headless browser.
|
|
15
|
+
- **desktop**: accept signed macOS system UIDs ([b7a1521](https://github.com/rvben/teams-cli/commit/b7a152189d2bc636cebe5ca67f24172dd702ae75))
|
|
16
|
+
- **desktop**: validate helper archives with tar-codec ([b955dd8](https://github.com/rvben/teams-cli/commit/b955dd8ea93cb302d72107cd2701e2adc8380ab3))
|
|
17
|
+
|
|
18
|
+
## [0.2.5](https://github.com/rvben/teams-cli/compare/v0.2.4...v0.2.5) - 2026-10-21
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Update TLS certificate verification and bundled root certificates to address RUSTSEC-2026-0382 and RUSTSEC-2026-0383.
|
|
23
|
+
- Desktop chats whose messages Teams keeps in a secondary reply-chain store (`replychains-2`, ...) no longer look empty. All such stores are now read and merged, which also fills in member names for those 1:1 chats.
|
|
24
|
+
- Desktop chat messages missing from the current cache are filled in from caches the same user and tenant left under earlier display languages. Those caches no longer update, so messages edited or deleted since can appear as they were.
|
|
25
|
+
- In the TUI, a chat whose messages are missing now shows its latest message from the chat list and why, instead of "No messages in this chat yet." With the desktop backend it says no messages for the chat were found in the Teams cache on this PC.
|
|
26
|
+
- **deps**: update TLS certificate trust dependencies ([435b11c](https://github.com/rvben/teams-cli/commit/435b11c39113e165e843d73579bc28505ef05620))
|
|
27
|
+
|
|
10
28
|
## [0.2.4](https://github.com/rvben/teams-cli/compare/v0.2.3...v0.2.4) - 2026-10-20
|
|
11
29
|
|
|
12
30
|
### Fixed
|
|
@@ -37,6 +37,12 @@ version = "0.2.21"
|
|
|
37
37
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
38
38
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|
39
39
|
|
|
40
|
+
[[package]]
|
|
41
|
+
name = "ambient-authority"
|
|
42
|
+
version = "0.0.2"
|
|
43
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
44
|
+
checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b"
|
|
45
|
+
|
|
40
46
|
[[package]]
|
|
41
47
|
name = "anstream"
|
|
42
48
|
version = "1.0.0"
|
|
@@ -102,6 +108,19 @@ dependencies = [
|
|
|
102
108
|
"num-traits",
|
|
103
109
|
]
|
|
104
110
|
|
|
111
|
+
[[package]]
|
|
112
|
+
name = "archive-trait"
|
|
113
|
+
version = "0.0.15"
|
|
114
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
115
|
+
checksum = "999bf88cc483664cab77700ca2fdfb0415a73412abe284ef5bc2319d00938e00"
|
|
116
|
+
dependencies = [
|
|
117
|
+
"cap-std",
|
|
118
|
+
"thiserror 2.0.20",
|
|
119
|
+
"tokio",
|
|
120
|
+
"walkdir",
|
|
121
|
+
"windows-sys 0.60.2",
|
|
122
|
+
]
|
|
123
|
+
|
|
105
124
|
[[package]]
|
|
106
125
|
name = "assert-json-diff"
|
|
107
126
|
version = "2.0.2"
|
|
@@ -127,6 +146,18 @@ dependencies = [
|
|
|
127
146
|
"wait-timeout",
|
|
128
147
|
]
|
|
129
148
|
|
|
149
|
+
[[package]]
|
|
150
|
+
name = "async-compression"
|
|
151
|
+
version = "0.4.51"
|
|
152
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
153
|
+
checksum = "9d5755640120b24961f8231e05892e4a4f7e0d04e8aeca161508c9c397946970"
|
|
154
|
+
dependencies = [
|
|
155
|
+
"compression-codecs",
|
|
156
|
+
"compression-core",
|
|
157
|
+
"pin-project-lite",
|
|
158
|
+
"tokio",
|
|
159
|
+
]
|
|
160
|
+
|
|
130
161
|
[[package]]
|
|
131
162
|
name = "atomic"
|
|
132
163
|
version = "0.6.1"
|
|
@@ -296,6 +327,36 @@ version = "1.12.1"
|
|
|
296
327
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
297
328
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|
298
329
|
|
|
330
|
+
[[package]]
|
|
331
|
+
name = "cap-primitives"
|
|
332
|
+
version = "4.0.3"
|
|
333
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
334
|
+
checksum = "8b5f74729fd2f44701d1a8eb47e906cdb3ccd9ec0f02baad85a744b791940b18"
|
|
335
|
+
dependencies = [
|
|
336
|
+
"ambient-authority",
|
|
337
|
+
"fs-set-times",
|
|
338
|
+
"io-extras",
|
|
339
|
+
"io-lifetimes 3.0.1",
|
|
340
|
+
"ipnet",
|
|
341
|
+
"maybe-owned",
|
|
342
|
+
"rustix",
|
|
343
|
+
"rustix-linux-procfs",
|
|
344
|
+
"windows-sys 0.61.2",
|
|
345
|
+
"winx",
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
[[package]]
|
|
349
|
+
name = "cap-std"
|
|
350
|
+
version = "4.0.3"
|
|
351
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
352
|
+
checksum = "c1ec78e242cfa2cfe276807ac2ecc00315a6c97786977414bcd1c3963b6c91b8"
|
|
353
|
+
dependencies = [
|
|
354
|
+
"cap-primitives",
|
|
355
|
+
"io-extras",
|
|
356
|
+
"io-lifetimes 3.0.1",
|
|
357
|
+
"rustix",
|
|
358
|
+
]
|
|
359
|
+
|
|
299
360
|
[[package]]
|
|
300
361
|
name = "castaway"
|
|
301
362
|
version = "0.2.4"
|
|
@@ -428,6 +489,23 @@ dependencies = [
|
|
|
428
489
|
"static_assertions",
|
|
429
490
|
]
|
|
430
491
|
|
|
492
|
+
[[package]]
|
|
493
|
+
name = "compression-codecs"
|
|
494
|
+
version = "0.4.46"
|
|
495
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
496
|
+
checksum = "cae36683101b2899f48bd31f8db9839437356188e3b1ff23096e73dab09ba8fe"
|
|
497
|
+
dependencies = [
|
|
498
|
+
"compression-core",
|
|
499
|
+
"flate2",
|
|
500
|
+
"memchr",
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
[[package]]
|
|
504
|
+
name = "compression-core"
|
|
505
|
+
version = "0.4.34"
|
|
506
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
507
|
+
checksum = "2ebdda89edc534bf8d5dd2857562633b6495557e5846552ade994b99ed13c36a"
|
|
508
|
+
|
|
431
509
|
[[package]]
|
|
432
510
|
name = "const-oid"
|
|
433
511
|
version = "0.10.2"
|
|
@@ -959,6 +1037,17 @@ dependencies = [
|
|
|
959
1037
|
"num",
|
|
960
1038
|
]
|
|
961
1039
|
|
|
1040
|
+
[[package]]
|
|
1041
|
+
name = "fs-set-times"
|
|
1042
|
+
version = "0.20.3"
|
|
1043
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1044
|
+
checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a"
|
|
1045
|
+
dependencies = [
|
|
1046
|
+
"io-lifetimes 2.0.4",
|
|
1047
|
+
"rustix",
|
|
1048
|
+
"windows-sys 0.52.0",
|
|
1049
|
+
]
|
|
1050
|
+
|
|
962
1051
|
[[package]]
|
|
963
1052
|
name = "fs_extra"
|
|
964
1053
|
version = "1.3.0"
|
|
@@ -1419,6 +1508,28 @@ dependencies = [
|
|
|
1419
1508
|
"syn 3.0.4",
|
|
1420
1509
|
]
|
|
1421
1510
|
|
|
1511
|
+
[[package]]
|
|
1512
|
+
name = "io-extras"
|
|
1513
|
+
version = "0.19.0"
|
|
1514
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1515
|
+
checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f"
|
|
1516
|
+
dependencies = [
|
|
1517
|
+
"io-lifetimes 3.0.1",
|
|
1518
|
+
"windows-sys 0.60.2",
|
|
1519
|
+
]
|
|
1520
|
+
|
|
1521
|
+
[[package]]
|
|
1522
|
+
name = "io-lifetimes"
|
|
1523
|
+
version = "2.0.4"
|
|
1524
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1525
|
+
checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983"
|
|
1526
|
+
|
|
1527
|
+
[[package]]
|
|
1528
|
+
name = "io-lifetimes"
|
|
1529
|
+
version = "3.0.1"
|
|
1530
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1531
|
+
checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96"
|
|
1532
|
+
|
|
1422
1533
|
[[package]]
|
|
1423
1534
|
name = "ipnet"
|
|
1424
1535
|
version = "2.12.1"
|
|
@@ -1780,6 +1891,12 @@ dependencies = [
|
|
|
1780
1891
|
"winapi",
|
|
1781
1892
|
]
|
|
1782
1893
|
|
|
1894
|
+
[[package]]
|
|
1895
|
+
name = "maybe-owned"
|
|
1896
|
+
version = "0.3.4"
|
|
1897
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1898
|
+
checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
|
|
1899
|
+
|
|
1783
1900
|
[[package]]
|
|
1784
1901
|
name = "memchr"
|
|
1785
1902
|
version = "2.8.3"
|
|
@@ -2750,6 +2867,16 @@ dependencies = [
|
|
|
2750
2867
|
"windows-sys 0.61.2",
|
|
2751
2868
|
]
|
|
2752
2869
|
|
|
2870
|
+
[[package]]
|
|
2871
|
+
name = "rustix-linux-procfs"
|
|
2872
|
+
version = "0.1.1"
|
|
2873
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2874
|
+
checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056"
|
|
2875
|
+
dependencies = [
|
|
2876
|
+
"once_cell",
|
|
2877
|
+
"rustix",
|
|
2878
|
+
]
|
|
2879
|
+
|
|
2753
2880
|
[[package]]
|
|
2754
2881
|
name = "rustls"
|
|
2755
2882
|
version = "0.23.46"
|
|
@@ -2815,9 +2942,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
|
|
2815
2942
|
|
|
2816
2943
|
[[package]]
|
|
2817
2944
|
name = "rustls-webpki"
|
|
2818
|
-
version = "0.103.
|
|
2945
|
+
version = "0.103.16"
|
|
2819
2946
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2820
|
-
checksum = "
|
|
2947
|
+
checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281"
|
|
2821
2948
|
dependencies = [
|
|
2822
2949
|
"aws-lc-rs",
|
|
2823
2950
|
"ring",
|
|
@@ -3185,11 +3312,34 @@ dependencies = [
|
|
|
3185
3312
|
"xattr",
|
|
3186
3313
|
]
|
|
3187
3314
|
|
|
3315
|
+
[[package]]
|
|
3316
|
+
name = "tar-codec"
|
|
3317
|
+
version = "0.0.15"
|
|
3318
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3319
|
+
checksum = "b9dad615e50d181afea4bafea23dacb8e33ed52dde06503813570634b932d467"
|
|
3320
|
+
dependencies = [
|
|
3321
|
+
"archive-trait",
|
|
3322
|
+
"tar-framing",
|
|
3323
|
+
"thiserror 2.0.20",
|
|
3324
|
+
"tokio",
|
|
3325
|
+
]
|
|
3326
|
+
|
|
3327
|
+
[[package]]
|
|
3328
|
+
name = "tar-framing"
|
|
3329
|
+
version = "0.0.15"
|
|
3330
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3331
|
+
checksum = "563e8181b47c9cdcce569eae3ad431eec71543d6269dd60c7bd2350f70bcd233"
|
|
3332
|
+
dependencies = [
|
|
3333
|
+
"thiserror 2.0.20",
|
|
3334
|
+
"tokio",
|
|
3335
|
+
]
|
|
3336
|
+
|
|
3188
3337
|
[[package]]
|
|
3189
3338
|
name = "teams-cli"
|
|
3190
|
-
version = "0.2.
|
|
3339
|
+
version = "0.2.6"
|
|
3191
3340
|
dependencies = [
|
|
3192
3341
|
"assert_cmd",
|
|
3342
|
+
"async-compression",
|
|
3193
3343
|
"base64 0.23.1",
|
|
3194
3344
|
"clap",
|
|
3195
3345
|
"clap_complete",
|
|
@@ -3210,6 +3360,7 @@ dependencies = [
|
|
|
3210
3360
|
"serde_json",
|
|
3211
3361
|
"sha2",
|
|
3212
3362
|
"tar",
|
|
3363
|
+
"tar-codec",
|
|
3213
3364
|
"tempfile",
|
|
3214
3365
|
"thiserror 2.0.20",
|
|
3215
3366
|
"tokio",
|
|
@@ -3229,7 +3380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
3229
3380
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
|
3230
3381
|
dependencies = [
|
|
3231
3382
|
"fastrand",
|
|
3232
|
-
"getrandom 0.3
|
|
3383
|
+
"getrandom 0.4.3",
|
|
3233
3384
|
"once_cell",
|
|
3234
3385
|
"rustix",
|
|
3235
3386
|
"windows-sys 0.61.2",
|
|
@@ -3843,9 +3994,9 @@ dependencies = [
|
|
|
3843
3994
|
|
|
3844
3995
|
[[package]]
|
|
3845
3996
|
name = "webpki-root-certs"
|
|
3846
|
-
version = "1.0.
|
|
3997
|
+
version = "1.0.10"
|
|
3847
3998
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3848
|
-
checksum = "
|
|
3999
|
+
checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb"
|
|
3849
4000
|
dependencies = [
|
|
3850
4001
|
"rustls-pki-types",
|
|
3851
4002
|
]
|
|
@@ -4121,6 +4272,16 @@ version = "1.0.4"
|
|
|
4121
4272
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4122
4273
|
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
|
4123
4274
|
|
|
4275
|
+
[[package]]
|
|
4276
|
+
name = "winx"
|
|
4277
|
+
version = "0.36.4"
|
|
4278
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4279
|
+
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
|
4280
|
+
dependencies = [
|
|
4281
|
+
"bitflags 2.13.1",
|
|
4282
|
+
"windows-sys 0.52.0",
|
|
4283
|
+
]
|
|
4284
|
+
|
|
4124
4285
|
[[package]]
|
|
4125
4286
|
name = "wiremock"
|
|
4126
4287
|
version = "0.6.5"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "teams-cli"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.6"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.90"
|
|
6
6
|
description = "A Microsoft Teams CLI and TUI designed equally for humans and agents"
|
|
@@ -22,12 +22,12 @@ name = "teams-cli"
|
|
|
22
22
|
path = "src/teams_cli_alias.rs"
|
|
23
23
|
|
|
24
24
|
[dependencies]
|
|
25
|
+
async-compression = { version = "0.4", features = ["tokio", "gzip"] }
|
|
25
26
|
base64 = "0.23"
|
|
26
27
|
clap = { version = "4", features = ["derive", "env"] }
|
|
27
28
|
clap_complete = "4"
|
|
28
29
|
crossterm = "0.29"
|
|
29
30
|
dirs = "6"
|
|
30
|
-
flate2 = "1"
|
|
31
31
|
futures-util = { version = "0.3", default-features = false, features = ["sink"] }
|
|
32
32
|
keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service", "vendored"] }
|
|
33
33
|
open = "5"
|
|
@@ -37,7 +37,7 @@ reqwest = { version = "0.13", default-features = false, features = ["rustls", "j
|
|
|
37
37
|
serde = { version = "1", features = ["derive"] }
|
|
38
38
|
serde_json = "1"
|
|
39
39
|
sha2 = "0.10"
|
|
40
|
-
tar = "0.
|
|
40
|
+
tar-codec = "=0.0.15"
|
|
41
41
|
thiserror = "2"
|
|
42
42
|
tempfile = "3"
|
|
43
43
|
tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "io-util", "time", "process", "io-std"] }
|
|
@@ -64,6 +64,8 @@ windows-sys = { version = "0.61", features = [
|
|
|
64
64
|
libc = "0.2"
|
|
65
65
|
|
|
66
66
|
[dev-dependencies]
|
|
67
|
+
flate2 = "1"
|
|
68
|
+
tar = "0.4"
|
|
67
69
|
assert_cmd = "2"
|
|
68
70
|
jsonschema = "0.49.8"
|
|
69
71
|
predicates = "3"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: teams-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
License-File: LICENSE
|
|
@@ -24,6 +24,16 @@ For humans, no arguments open guided onboarding on first use and the TUI afterwa
|
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
27
|
+
### Homebrew
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
brew install rvben/tap/teams-cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Homebrew installs native binaries on macOS and builds from source on Linux.
|
|
34
|
+
|
|
35
|
+
### Other installation methods
|
|
36
|
+
|
|
27
37
|
```console
|
|
28
38
|
pipx install teams-cli
|
|
29
39
|
# or
|
|
@@ -8,6 +8,16 @@ For humans, no arguments open guided onboarding on first use and the TUI afterwa
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
+
### Homebrew
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
brew install rvben/tap/teams-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Homebrew installs native binaries on macOS and builds from source on Linux.
|
|
18
|
+
|
|
19
|
+
### Other installation methods
|
|
20
|
+
|
|
11
21
|
```console
|
|
12
22
|
pipx install teams-cli
|
|
13
23
|
# or
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
use crate::error::AppError;
|
|
4
4
|
use sha2::{Digest, Sha256};
|
|
5
5
|
use std::{
|
|
6
|
-
io::Read,
|
|
7
6
|
path::{Path, PathBuf},
|
|
8
7
|
time::Duration,
|
|
9
8
|
};
|
|
9
|
+
use tar_codec::{Archive as _, Member, MemberPayload as _, TarArchive};
|
|
10
|
+
use tokio::io::AsyncReadExt as _;
|
|
10
11
|
|
|
11
12
|
use super::helper::PROTOCOL;
|
|
12
13
|
|
|
@@ -133,7 +134,15 @@ async fn ensure_state(state: HelperState, version: &str) -> Result<PathBuf, AppE
|
|
|
133
134
|
"{asset} does not match its published checksum; nothing was installed"
|
|
134
135
|
)));
|
|
135
136
|
}
|
|
136
|
-
|
|
137
|
+
// Member payloads borrow the decoder across awaits. Parse on a blocking
|
|
138
|
+
// worker so the downloader remains Send on Rust 1.90, and decompression
|
|
139
|
+
// does not occupy an async runtime worker.
|
|
140
|
+
let helper = tokio::task::spawn_blocking(move || {
|
|
141
|
+
tokio::runtime::Handle::current().block_on(extract(&archive))
|
|
142
|
+
})
|
|
143
|
+
.await
|
|
144
|
+
.map_err(|_| AppError::Desktop("could not read the helper archive".into()))??;
|
|
145
|
+
install(&path, &helper)?;
|
|
137
146
|
Ok(path)
|
|
138
147
|
}
|
|
139
148
|
|
|
@@ -192,30 +201,73 @@ fn checksum(sums: &str, name: &str) -> Option<String> {
|
|
|
192
201
|
})
|
|
193
202
|
}
|
|
194
203
|
|
|
195
|
-
/// Only `teams.exe` is
|
|
196
|
-
|
|
204
|
+
/// Only one top-level `teams.exe` is accepted. Validate the entire archive
|
|
205
|
+
/// before installing it, including entries after the executable and gzip CRC.
|
|
206
|
+
async fn extract(archive: &[u8]) -> Result<Vec<u8>, AppError> {
|
|
197
207
|
let invalid = || AppError::Desktop("the helper archive is not valid".into());
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
208
|
+
if archive.len() > MAX_ARCHIVE {
|
|
209
|
+
return Err(invalid());
|
|
210
|
+
}
|
|
211
|
+
let mut reader =
|
|
212
|
+
async_compression::tokio::bufread::GzipDecoder::new(archive).take(MAX_ARCHIVE as u64 + 1);
|
|
213
|
+
let mut helper = None;
|
|
214
|
+
{
|
|
215
|
+
let mut entries = TarArchive::new(&mut reader).members();
|
|
216
|
+
let mut chunk = Vec::new();
|
|
217
|
+
while let Some(entry) = entries.next().await.map_err(|_| invalid())? {
|
|
218
|
+
if let Member::File {
|
|
219
|
+
metadata,
|
|
220
|
+
size,
|
|
221
|
+
mut payload,
|
|
222
|
+
..
|
|
223
|
+
} = entry
|
|
224
|
+
{
|
|
225
|
+
// Match the release layout without normalizing away traversal.
|
|
226
|
+
let is_helper = matches!(metadata.path.as_str(), "teams.exe" | "./teams.exe");
|
|
227
|
+
if is_helper {
|
|
228
|
+
if helper.is_some() || size > MAX_ARCHIVE as u64 {
|
|
229
|
+
return Err(invalid());
|
|
230
|
+
}
|
|
231
|
+
let mut bytes = Vec::new();
|
|
232
|
+
while payload
|
|
233
|
+
.next_chunk(&mut chunk, 64 * 1024)
|
|
234
|
+
.await
|
|
235
|
+
.map_err(|_| invalid())?
|
|
236
|
+
{
|
|
237
|
+
if bytes.len() + chunk.len() > MAX_ARCHIVE {
|
|
238
|
+
return Err(invalid());
|
|
239
|
+
}
|
|
240
|
+
bytes.extend_from_slice(&chunk);
|
|
241
|
+
}
|
|
242
|
+
if !bytes.starts_with(b"MZ") {
|
|
243
|
+
return Err(invalid());
|
|
244
|
+
}
|
|
245
|
+
helper = Some(bytes);
|
|
246
|
+
} else {
|
|
247
|
+
payload.skip().await.map_err(|_| invalid())?;
|
|
248
|
+
}
|
|
249
|
+
} else if !matches!(entry, Member::Directory { .. }) {
|
|
250
|
+
// Release packages contain only directories and regular files.
|
|
213
251
|
return Err(invalid());
|
|
214
252
|
}
|
|
215
|
-
return Ok(bytes);
|
|
216
253
|
}
|
|
217
254
|
}
|
|
218
|
-
|
|
255
|
+
// The tar end marker can precede padding and the gzip trailer. Reading to
|
|
256
|
+
// EOF also makes the decompressed-size budget cover ignored archive data.
|
|
257
|
+
let mut padding = [0; 8192];
|
|
258
|
+
loop {
|
|
259
|
+
let read = reader.read(&mut padding).await.map_err(|_| invalid())?;
|
|
260
|
+
if read == 0 {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
if padding[..read].iter().any(|&byte| byte != 0) {
|
|
264
|
+
return Err(invalid());
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if reader.limit() == 0 || !reader.get_ref().get_ref().is_empty() {
|
|
268
|
+
return Err(invalid());
|
|
269
|
+
}
|
|
270
|
+
helper.ok_or_else(invalid)
|
|
219
271
|
}
|
|
220
272
|
|
|
221
273
|
/// Writes atomically, marks it executable for WSL interop, and removes other versions.
|
|
@@ -249,6 +301,12 @@ fn install(path: &Path, bytes: &[u8]) -> Result<(), AppError> {
|
|
|
249
301
|
mod tests {
|
|
250
302
|
use super::*;
|
|
251
303
|
|
|
304
|
+
#[test]
|
|
305
|
+
fn helper_downloader_can_be_sent_between_runtime_workers() {
|
|
306
|
+
fn assert_send(_: impl Send) {}
|
|
307
|
+
assert_send(ensure());
|
|
308
|
+
}
|
|
309
|
+
|
|
252
310
|
#[tokio::test]
|
|
253
311
|
async fn legacy_release_needs_a_built_helper_but_accepts_explicit_or_cached_builds() {
|
|
254
312
|
let temp = tempfile::tempdir().unwrap();
|
|
@@ -299,14 +357,145 @@ mod tests {
|
|
|
299
357
|
assert_eq!(hex(&[0, 15, 255]), "000fff");
|
|
300
358
|
}
|
|
301
359
|
|
|
302
|
-
#[test]
|
|
303
|
-
fn extracts_only_the_top_level_windows_executable() {
|
|
360
|
+
#[tokio::test]
|
|
361
|
+
async fn extracts_only_the_top_level_windows_executable() {
|
|
304
362
|
let exe = b"MZ fixture";
|
|
305
363
|
let bytes = archive(&[("./README.md", b"readme"), ("./teams.exe", exe)]);
|
|
306
|
-
assert_eq!(extract(&bytes).unwrap(), exe);
|
|
307
|
-
assert!(
|
|
308
|
-
|
|
309
|
-
|
|
364
|
+
assert_eq!(extract(&bytes).await.unwrap(), exe);
|
|
365
|
+
assert!(
|
|
366
|
+
extract(&archive(&[("./nested/teams.exe", exe)]))
|
|
367
|
+
.await
|
|
368
|
+
.is_err()
|
|
369
|
+
);
|
|
370
|
+
assert!(
|
|
371
|
+
extract(&archive(&[("./teams.exe", b"#!/bin/sh")]))
|
|
372
|
+
.await
|
|
373
|
+
.is_err()
|
|
374
|
+
);
|
|
375
|
+
assert!(extract(b"not an archive").await.is_err());
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
#[tokio::test]
|
|
379
|
+
async fn extracts_a_helper_across_multiple_payload_chunks() {
|
|
380
|
+
let mut exe = vec![0x5a; 3 * 64 * 1024 + 17];
|
|
381
|
+
exe[..2].copy_from_slice(b"MZ");
|
|
382
|
+
assert_eq!(
|
|
383
|
+
extract(&archive(&[("teams.exe", &exe)])).await.unwrap(),
|
|
384
|
+
exe
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
#[cfg(unix)]
|
|
389
|
+
#[tokio::test]
|
|
390
|
+
async fn accepts_the_release_workflows_system_tar_packaging() {
|
|
391
|
+
let temp = tempfile::tempdir().unwrap();
|
|
392
|
+
let package = temp.path().join("package");
|
|
393
|
+
std::fs::create_dir(&package).unwrap();
|
|
394
|
+
std::fs::write(package.join("teams.exe"), b"MZ release fixture").unwrap();
|
|
395
|
+
std::fs::write(package.join("README.md"), b"readme").unwrap();
|
|
396
|
+
let output = temp.path().join("helper.tar.gz");
|
|
397
|
+
// Match .github/workflows/release.yml, including the '.' root entry.
|
|
398
|
+
assert!(
|
|
399
|
+
std::process::Command::new("tar")
|
|
400
|
+
.arg("-C")
|
|
401
|
+
.arg(&package)
|
|
402
|
+
.arg("-czf")
|
|
403
|
+
.arg(&output)
|
|
404
|
+
.arg(".")
|
|
405
|
+
.status()
|
|
406
|
+
.unwrap()
|
|
407
|
+
.success()
|
|
408
|
+
);
|
|
409
|
+
assert_eq!(
|
|
410
|
+
extract(&std::fs::read(output).unwrap()).await.unwrap(),
|
|
411
|
+
b"MZ release fixture"
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
#[tokio::test]
|
|
416
|
+
async fn rejects_duplicate_helpers_and_damage_after_the_helper() {
|
|
417
|
+
let duplicate = archive(&[("teams.exe", b"MZ first"), ("./teams.exe", b"MZ second")]);
|
|
418
|
+
assert!(extract(&duplicate).await.is_err());
|
|
419
|
+
|
|
420
|
+
let mut damaged = archive(&[("teams.exe", b"MZ fixture")]);
|
|
421
|
+
let crc = damaged.len() - 8;
|
|
422
|
+
damaged[crc] ^= 1;
|
|
423
|
+
assert!(extract(&damaged).await.is_err(), "gzip CRC must be checked");
|
|
424
|
+
damaged.truncate(crc);
|
|
425
|
+
assert!(
|
|
426
|
+
extract(&damaged).await.is_err(),
|
|
427
|
+
"missing trailer must fail"
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
// A malformed second tar header must not be hidden by finding the
|
|
431
|
+
// executable first. Recompress so the gzip checksum remains valid.
|
|
432
|
+
use std::io::{Read as _, Write as _};
|
|
433
|
+
let valid = archive(&[("teams.exe", b"MZ fixture"), ("README.md", b"readme")]);
|
|
434
|
+
let mut raw = Vec::new();
|
|
435
|
+
flate2::read::GzDecoder::new(valid.as_slice())
|
|
436
|
+
.read_to_end(&mut raw)
|
|
437
|
+
.unwrap();
|
|
438
|
+
raw[1024] ^= 1;
|
|
439
|
+
let mut gz = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
|
|
440
|
+
gz.write_all(&raw).unwrap();
|
|
441
|
+
assert!(extract(&gz.finish().unwrap()).await.is_err());
|
|
442
|
+
|
|
443
|
+
let mut trailing = archive(&[("teams.exe", b"MZ fixture")]);
|
|
444
|
+
trailing.extend_from_slice(b"junk");
|
|
445
|
+
assert!(
|
|
446
|
+
extract(&trailing).await.is_err(),
|
|
447
|
+
"trailing compressed data must fail"
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
let mut concatenated = archive(&[("teams.exe", b"MZ first")]);
|
|
451
|
+
concatenated.extend_from_slice(&archive(&[("teams.exe", b"MZ second")]));
|
|
452
|
+
assert!(
|
|
453
|
+
extract(&concatenated).await.is_err(),
|
|
454
|
+
"multiple gzip streams must fail"
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#[tokio::test]
|
|
459
|
+
async fn rejects_links_even_after_a_valid_helper() {
|
|
460
|
+
let mut builder = tar::Builder::new(flate2::write::GzEncoder::new(
|
|
461
|
+
Vec::new(),
|
|
462
|
+
flate2::Compression::fast(),
|
|
463
|
+
));
|
|
464
|
+
let mut file = tar::Header::new_gnu();
|
|
465
|
+
file.set_size(2);
|
|
466
|
+
file.set_mode(0o644);
|
|
467
|
+
builder
|
|
468
|
+
.append_data(&mut file, "teams.exe", &b"MZ"[..])
|
|
469
|
+
.unwrap();
|
|
470
|
+
let mut link = tar::Header::new_gnu();
|
|
471
|
+
link.set_entry_type(tar::EntryType::Symlink);
|
|
472
|
+
link.set_size(0);
|
|
473
|
+
link.set_mode(0o777);
|
|
474
|
+
builder
|
|
475
|
+
.append_link(&mut link, "other", "teams.exe")
|
|
476
|
+
.unwrap();
|
|
477
|
+
let bytes = builder.into_inner().unwrap().finish().unwrap();
|
|
478
|
+
assert!(extract(&bytes).await.is_err());
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
#[tokio::test]
|
|
482
|
+
async fn bounds_decompressed_data_including_ignored_files_and_padding() {
|
|
483
|
+
// Compressed input is small, but ignored payloads still consume the
|
|
484
|
+
// same total budget as the helper itself.
|
|
485
|
+
let bytes = archive(&[("README.md", &vec![0; MAX_ARCHIVE]), ("teams.exe", b"MZ")]);
|
|
486
|
+
assert!(bytes.len() < MAX_ARCHIVE);
|
|
487
|
+
assert!(extract(&bytes).await.is_err());
|
|
488
|
+
|
|
489
|
+
use std::io::{Read as _, Write as _};
|
|
490
|
+
let valid = archive(&[("teams.exe", b"MZ")]);
|
|
491
|
+
let mut raw = Vec::new();
|
|
492
|
+
flate2::read::GzDecoder::new(valid.as_slice())
|
|
493
|
+
.read_to_end(&mut raw)
|
|
494
|
+
.unwrap();
|
|
495
|
+
raw.resize(MAX_ARCHIVE + 1, 0);
|
|
496
|
+
let mut gz = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
|
|
497
|
+
gz.write_all(&raw).unwrap();
|
|
498
|
+
assert!(extract(&gz.finish().unwrap()).await.is_err());
|
|
310
499
|
}
|
|
311
500
|
|
|
312
501
|
#[test]
|