teams-cli 0.1.12__tar.gz → 0.2.0__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.
Files changed (59) hide show
  1. {teams_cli-0.1.12 → teams_cli-0.2.0}/CHANGELOG.md +18 -0
  2. {teams_cli-0.1.12 → teams_cli-0.2.0}/Cargo.lock +40 -6
  3. {teams_cli-0.1.12 → teams_cli-0.2.0}/Cargo.toml +5 -1
  4. {teams_cli-0.1.12 → teams_cli-0.2.0}/PKG-INFO +136 -3
  5. {teams_cli-0.1.12 → teams_cli-0.2.0}/README.md +135 -2
  6. {teams_cli-0.1.12 → teams_cli-0.2.0}/docs/releases.md +69 -0
  7. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/backend.rs +2 -3
  8. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/cli.rs +17 -4
  9. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/config.rs +49 -1
  10. teams_cli-0.2.0/src/desktop/avatars.rs +589 -0
  11. teams_cli-0.2.0/src/desktop/browser.rs +1217 -0
  12. teams_cli-0.2.0/src/desktop/browser_core.rs +227 -0
  13. teams_cli-0.2.0/src/desktop/browser_install.rs +554 -0
  14. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/cdp.rs +109 -8
  15. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/connection.rs +21 -0
  16. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/helper.rs +9 -0
  17. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/session.rs +60 -8
  18. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop.rs +176 -16
  19. teams_cli-0.2.0/src/desktop_core.js +25 -0
  20. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop_send.js +20 -8
  21. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/main.rs +147 -38
  22. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/schema.rs +49 -20
  23. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/tui.rs +33 -1
  24. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/desktop.rs +193 -0
  25. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/desktop_scripts.test.cjs +72 -3
  26. {teams_cli-0.1.12 → teams_cli-0.2.0}/.github/workflows/ci.yml +0 -0
  27. {teams_cli-0.1.12 → teams_cli-0.2.0}/.github/workflows/dependencies.yml +0 -0
  28. {teams_cli-0.1.12 → teams_cli-0.2.0}/.github/workflows/release.yml +0 -0
  29. {teams_cli-0.1.12 → teams_cli-0.2.0}/.gitignore +0 -0
  30. {teams_cli-0.1.12 → teams_cli-0.2.0}/.mise.toml +0 -0
  31. {teams_cli-0.1.12 → teams_cli-0.2.0}/LICENSE +0 -0
  32. {teams_cli-0.1.12 → teams_cli-0.2.0}/Makefile +0 -0
  33. {teams_cli-0.1.12 → teams_cli-0.2.0}/build.rs +0 -0
  34. {teams_cli-0.1.12 → teams_cli-0.2.0}/examples/validate_schema.rs +0 -0
  35. {teams_cli-0.1.12 → teams_cli-0.2.0}/pyproject.toml +0 -0
  36. {teams_cli-0.1.12 → teams_cli-0.2.0}/scripts/smoke-release.py +0 -0
  37. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/auth.rs +0 -0
  38. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/delivery.rs +0 -0
  39. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/connection.ps1 +0 -0
  40. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/install.rs +0 -0
  41. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/macos.rs +0 -0
  42. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/owner.rs +0 -0
  43. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop/windows.rs +0 -0
  44. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop.js +0 -0
  45. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/desktop_cache.js +0 -0
  46. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/error.rs +0 -0
  47. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/graph.rs +0 -0
  48. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/lib.rs +0 -0
  49. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/output.rs +0 -0
  50. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/render.rs +0 -0
  51. {teams_cli-0.1.12 → teams_cli-0.2.0}/src/teams_cli_alias.rs +0 -0
  52. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/cli.rs +0 -0
  53. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/connection.rs +0 -0
  54. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/connection.test.ps1 +0 -0
  55. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/desktop_scripts.rs +0 -0
  56. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/fixtures/clispec-v0.3.json +0 -0
  57. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/package_alias.rs +0 -0
  58. {teams_cli-0.1.12 → teams_cli-0.2.0}/tests/release_smoke.rs +0 -0
  59. {teams_cli-0.1.12 → teams_cli-0.2.0}/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.0](https://github.com/rvben/teams-cli/compare/v0.1.13...v0.2.0) - 2026-10-14
11
+
12
+ ### Added
13
+
14
+ - **desktop**: add one-command lightweight browser setup with automatic matching browser downloads, visible sign-in followed by verified hidden operation, personal-account defaults, and safe reconnect/recovery.
15
+ - **desktop**: add managed browser clients and cached avatars ([319c494](https://github.com/rvben/teams-cli/commit/319c494b18c9dff98d26b2d069d4347a39d02e6b))
16
+
17
+ ### Fixed
18
+
19
+ - **security**: update rustls to 0.23.46 to address the newly published TLS advisories.
20
+
21
+ ## [0.1.13](https://github.com/rvben/teams-cli/compare/v0.1.12...v0.1.13) - 2026-10-14
22
+
23
+ ### Fixed
24
+
25
+ - **tui**: keep the canvas behind text so the terminal background never shows through ([596eb7b](https://github.com/rvben/teams-cli/commit/596eb7b6f79db174ba1f33e6eaf5eb345912504a))
26
+ - **desktop**: send URLs as clickable links like Teams' compose box does ([4b46ca2](https://github.com/rvben/teams-cli/commit/4b46ca2bb2a3d466313ba2f7ce8172c6eb065dbd))
27
+
10
28
  ## [0.1.12](https://github.com/rvben/teams-cli/compare/v0.1.11...v0.1.12) - 2026-10-14
11
29
 
12
30
  ### Breaking Changes
@@ -1776,7 +1776,7 @@ version = "1.1.8"
1776
1776
  source = "registry+https://github.com/rust-lang/crates.io-index"
1777
1777
  checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
1778
1778
  dependencies = [
1779
- "nix",
1779
+ "nix 0.29.0",
1780
1780
  "winapi",
1781
1781
  ]
1782
1782
 
@@ -1848,6 +1848,18 @@ dependencies = [
1848
1848
  "memoffset",
1849
1849
  ]
1850
1850
 
1851
+ [[package]]
1852
+ name = "nix"
1853
+ version = "0.31.3"
1854
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1855
+ checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
1856
+ dependencies = [
1857
+ "bitflags 2.13.1",
1858
+ "cfg-if",
1859
+ "cfg_aliases",
1860
+ "libc",
1861
+ ]
1862
+
1851
1863
  [[package]]
1852
1864
  name = "nom"
1853
1865
  version = "7.1.3"
@@ -2740,9 +2752,9 @@ dependencies = [
2740
2752
 
2741
2753
  [[package]]
2742
2754
  name = "rustls"
2743
- version = "0.23.45"
2755
+ version = "0.23.46"
2744
2756
  source = "registry+https://github.com/rust-lang/crates.io-index"
2745
- checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
2757
+ checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
2746
2758
  dependencies = [
2747
2759
  "aws-lc-rs",
2748
2760
  "once_cell",
@@ -3175,7 +3187,7 @@ dependencies = [
3175
3187
 
3176
3188
  [[package]]
3177
3189
  name = "teams-cli"
3178
- version = "0.1.12"
3190
+ version = "0.2.0"
3179
3191
  dependencies = [
3180
3192
  "assert_cmd",
3181
3193
  "base64 0.23.1",
@@ -3188,6 +3200,7 @@ dependencies = [
3188
3200
  "jiff",
3189
3201
  "jsonschema",
3190
3202
  "keyring",
3203
+ "libc",
3191
3204
  "open",
3192
3205
  "predicates",
3193
3206
  "rand 0.9.5",
@@ -3206,6 +3219,7 @@ dependencies = [
3206
3219
  "url",
3207
3220
  "windows-sys 0.61.2",
3208
3221
  "wiremock",
3222
+ "zip",
3209
3223
  ]
3210
3224
 
3211
3225
  [[package]]
@@ -3215,7 +3229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3215
3229
  checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
3216
3230
  dependencies = [
3217
3231
  "fastrand",
3218
- "getrandom 0.4.3",
3232
+ "getrandom 0.3.4",
3219
3233
  "once_cell",
3220
3234
  "rustix",
3221
3235
  "windows-sys 0.61.2",
@@ -3279,7 +3293,7 @@ dependencies = [
3279
3293
  "libc",
3280
3294
  "log",
3281
3295
  "memmem",
3282
- "nix",
3296
+ "nix 0.29.0",
3283
3297
  "num-derive",
3284
3298
  "num-traits",
3285
3299
  "ordered-float",
@@ -3577,6 +3591,12 @@ dependencies = [
3577
3591
  "thiserror 2.0.20",
3578
3592
  ]
3579
3593
 
3594
+ [[package]]
3595
+ name = "typed-path"
3596
+ version = "0.12.3"
3597
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3598
+ checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
3599
+
3580
3600
  [[package]]
3581
3601
  name = "typenum"
3582
3602
  version = "1.20.1"
@@ -4263,6 +4283,20 @@ dependencies = [
4263
4283
  "syn 3.0.4",
4264
4284
  ]
4265
4285
 
4286
+ [[package]]
4287
+ name = "zip"
4288
+ version = "9.0.3"
4289
+ source = "registry+https://github.com/rust-lang/crates.io-index"
4290
+ checksum = "c2d98561b612a84ae525331a7a094429ef8a5772514ed507ff6db5f54badc971"
4291
+ dependencies = [
4292
+ "crc32fast",
4293
+ "flate2",
4294
+ "indexmap",
4295
+ "memchr",
4296
+ "nix 0.31.3",
4297
+ "typed-path",
4298
+ ]
4299
+
4266
4300
  [[package]]
4267
4301
  name = "zmij"
4268
4302
  version = "1.0.23"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "teams-cli"
3
- version = "0.1.12"
3
+ version = "0.2.0"
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"
@@ -46,6 +46,7 @@ toml = "1.1"
46
46
  url = "2"
47
47
  jiff = "0.2"
48
48
  unicode-width = "0.2"
49
+ zip = { version = "9.0.3", default-features = false, features = ["deflate-flate2"] }
49
50
 
50
51
  [target.'cfg(windows)'.dependencies]
51
52
  windows-sys = { version = "0.61", features = [
@@ -58,6 +59,9 @@ windows-sys = { version = "0.61", features = [
58
59
  "Win32_System_Threading",
59
60
  ] }
60
61
 
62
+ [target.'cfg(target_os = "macos")'.dependencies]
63
+ libc = "0.2"
64
+
61
65
  [dev-dependencies]
62
66
  assert_cmd = "2"
63
67
  jsonschema = "0.49.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teams-cli
3
- Version: 0.1.12
3
+ Version: 0.2.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  License-File: LICENSE
@@ -18,7 +18,7 @@ Project-URL: Repository, https://github.com/rvben/teams-cli
18
18
 
19
19
  Microsoft Teams from your terminal—human-first and agent-first.
20
20
 
21
- `teams-cli` is a Rust CLI and TUI for everyday Microsoft Teams work through supported Microsoft Graph APIs, with an optional experimental desktop backend that reads chats from, and can send chat messages through, a running Teams client on macOS, Windows or WSL. It is designed as two equally first-class experiences over one typed core: a fast, calm terminal interface for people and a deterministic, introspectable command contract for agents. The default Graph backend signs in as you with delegated OAuth and protects refresh credentials in your operating system's credential store. Output is readable text on a TTY and structured JSON when piped.
21
+ `teams-cli` is a Rust CLI and TUI for everyday Microsoft Teams work through supported Microsoft Graph APIs, with an optional experimental desktop backend that reads chats from, and can send chat messages through, a running Teams client on macOS, Windows or WSL. A dedicated browser client also provides an experimental route on native Linux; see [browser setup](#dedicated-browser-client-experimental). It is designed as two equally first-class experiences over one typed core: a fast, calm terminal interface for people and a deterministic, introspectable command contract for agents. The default Graph backend signs in as you with delegated OAuth and protects refresh credentials in your operating system's credential store. Output is readable text on a TTY and structured JSON when piped.
22
22
 
23
23
  For humans, no arguments open guided onboarding on first use and the TUI afterward. For agents, no command ever waits for an undeclared prompt: `teams schema` works offline, collection sizes are caller-bounded, writes declare their effects, errors have stable kinds and exit codes, and stdout contains data only.
24
24
 
@@ -68,6 +68,16 @@ teams init
68
68
  teams init
69
69
  ```
70
70
 
71
+ For a personal Microsoft account with the lightweight hidden browser:
72
+
73
+ ```console
74
+ teams init --profile personal --backend desktop --desktop-client browser-core
75
+ ```
76
+
77
+ This downloads its browsers automatically, opens visible sign-in once, and then
78
+ runs hidden. See [lightweight browser setup](#low-memory-browser-client-experimental)
79
+ for sending, reconnecting, and experimental compatibility limits.
80
+
71
81
  Browser PKCE is the human-friendly default. The browser talks directly to Microsoft, and the CLI never receives or stores your password. Normal sign-in requests these delegated scopes:
72
82
 
73
83
  ```text
@@ -223,6 +233,15 @@ ids returned by `chats list`; personal chats are accessed through the desktop ba
223
233
  The chat with yourself is listed too, with the id `48:notes`, so
224
234
  you can try sending there first.
225
235
 
236
+ Desktop chat results may also include optional `avatarData` raster data URIs on
237
+ members and one-to-one chats. Photos come only from image resources already loaded
238
+ by that account's Teams page; the CLI never fetches a photo URL. Missing, stale,
239
+ ambiguous, or unsupported photos leave the fields absent, so clients should show
240
+ initials as a fallback. Reads allow at most 16 photos, 256 KiB per photo, 1 MiB of
241
+ avatar data in total, and a one-second budget. A photo failure never fails a chat
242
+ read. Personal photo reads have been checked on macOS; work/school and other
243
+ platforms still need photo-specific runtime validation.
244
+
226
245
  Desktop profiles are read-only unless created with `init --allow-send`;
227
246
  `TEAMS_READ_ONLY=true` still blocks sending, but `TEAMS_READ_ONLY=false` cannot unlock a
228
247
  desktop profile. With the opt-in, `messages send --chat` sends one plain-text message
@@ -360,12 +379,126 @@ This backend is experimental: chat reading depends on the Teams client's interna
360
379
  cache layout and sending on its internal send interface, and an unrecognised layout
361
380
  fails with `desktop_error` rather than guessing.
362
381
 
382
+ ### Dedicated browser client (experimental)
383
+
384
+ On native Linux, macOS, or WSL2, the desktop backend can use an installed Chrome,
385
+ Chromium, or Edge browser instead of the native Teams app. WSL2 uses a Linux browser;
386
+ visible sign-in requires WSLg or another graphical display. The existing Windows
387
+ helper route remains the default for native desktop profiles.
388
+
389
+ Create a separate read-only profile and sign in in the window that opens:
390
+
391
+ ```console
392
+ teams init --profile browser --backend desktop --desktop-client browser --desktop-account work-school --yes
393
+ # The window closes automatically after a successful chat read.
394
+ # Teams then runs headless with the same saved sign-in:
395
+ teams --profile browser
396
+ # To renew sign-in or complete MFA later:
397
+ teams --profile browser desktop login --yes
398
+ ```
399
+
400
+ Login waits up to five minutes for a readable chat cache, then restarts headless
401
+ and verifies another read before reporting success. An empty chat list is valid.
402
+ A sign-in timeout leaves the visible window open; finish signing in and retry
403
+ `desktop login`. Interrupting login preserves the browser and its saved profile.
404
+ On WSL2, the visible step needs WSLg or another graphical display.
405
+
406
+ Use `--desktop-account personal` for a personal Microsoft account. Sending still
407
+ requires an explicit `init --allow-send` and the existing send confirmation.
408
+ `init --no-login` only saves configuration. `desktop connect --visible` remains
409
+ available for manually managed sign-in; `desktop connect` starts headless.
410
+
411
+ The browser uses its own profile under the local data directory at
412
+ `teams-cli/browser/<port>/profile`; it never copies or opens your normal browser
413
+ profile. Different ports use different browser profiles. Disconnect closes this
414
+ browser and keeps its sign-in data. To switch between visible and headless operation,
415
+ disconnect first. `desktop recover` closes a verified managed browser or clears a
416
+ stale record when no listener remains. Unverifiable processes are left untouched.
417
+
418
+ The CLI checks that every listener is loopback-only and belongs to the recorded
419
+ browser process, running as you with the expected executable, debugging port and
420
+ dedicated profile. Only HTTPS Teams pages are used for reads or sends. While the
421
+ port is open, other programs running as you can access that browser session.
422
+
423
+ Browser discovery supports common system installations. For another installation,
424
+ set `TEAMS_DESKTOP_BROWSER` to the absolute path of the actual browser executable,
425
+ not a shell launcher. Snap/Flatpak installations are not automatically supported.
426
+ The optional `TEAMS_DESKTOP_BROWSER_DATA_DIR` sets an absolute browser data directory
427
+ (each port gets a separate subdirectory). Keep the same browser executable and data
428
+ directory settings while managing a session. Headless mode removes the visible window; Teams still consumes browser CPU and RAM.
429
+
430
+ This route is experimental. Browser lifecycle and ownership checks can be tested
431
+ without signing in, but live Teams Web reads, sends, session persistence and MFA
432
+ recovery still require validation on a signed-in machine. Existing native Teams
433
+ validation does not establish browser-client compatibility.
434
+
435
+ ### Low-memory browser client (experimental)
436
+
437
+ `--desktop-client browser-core` keeps the authenticated Teams data runtime without
438
+ starting the main UI. It supports personal accounts only and requires the
439
+ exact Teams Personal bootstrap build validated by this CLI. Unknown builds fail
440
+ with `desktop_error`; the CLI does not fall back to old cache data or the full UI.
441
+ Use `--desktop-client browser` on a separate port for the full web client.
442
+
443
+ Start with one command:
444
+
445
+ ```console
446
+ teams init --profile core --backend desktop --desktop-client browser-core
447
+ ```
448
+
449
+ The CLI downloads a matching **Chrome for Testing / chrome-headless-shell** pair
450
+ from Google's official Stable channel on first setup, opens a separate sign-in
451
+ window, and hides it once chats are readable. It verifies the hidden session before
452
+ saving your CLI profile. Core mode defaults to a personal account, port **9223**,
453
+ and read-only access; add `--allow-send` to enable sending. Your normal browser,
454
+ its profile, and the native Teams connection on port 9222 are left alone.
455
+
456
+ Browser binaries and sign-in state live in a private `teams-cli/browser` data
457
+ directory. The installed pair is reused offline and is not silently updated while
458
+ sessions are running. Setup needs roughly 1 GiB of disk space for the browsers and
459
+ extraction; the ZIP downloads are removed after installation. Linux/WSL2 need a
460
+ desktop display (WSLg on WSL2) for initial sign-in.
461
+
462
+ ```console
463
+ teams --profile core desktop login # Sign in again; automatically hide afterward
464
+ teams --profile core desktop disconnect # Stop the browser; retain sign-in
465
+ teams --profile core desktop connect # Start hidden using the saved session
466
+ teams --profile core desktop status # Inspect process and recovery state
467
+ ```
468
+
469
+ An interrupted sign-in retains the dedicated window and profile. Rerun `desktop
470
+ login` to finish; an expired hidden session can use the same command. A stale
471
+ process record is recovered automatically only when its port has no listener;
472
+ unrelated or changed processes are refused. `desktop recover` preserves sign-in
473
+ and can close only a verified managed browser. The status command does not prove
474
+ that you are signed in; `chats list` or `doctor` verifies message access.
475
+
476
+ Advanced setups can override both `TEAMS_DESKTOP_BROWSER` and
477
+ `TEAMS_DESKTOP_HEADLESS_BROWSER` with absolute paths to matching executables.
478
+ Set both or neither; a partial override is refused. `TEAMS_DESKTOP_BROWSER_DATA_DIR`
479
+ changes the private data location. Keep these settings stable while a session is
480
+ running. Unknown Teams builds are refused; use the full `browser` client on its
481
+ own port if your Teams build is not yet supported.
482
+
483
+ On macOS, both dedicated test browsers use mock-keychain encryption because the
484
+ headless shell uses it. This profile contains sensitive login state and remains in
485
+ a private directory; it never opens your ordinary browser profile.
486
+
487
+ The integrated CLI measured about **307 MiB after collection and 322 MiB idle**
488
+ of summed browser process physical footprint on macOS with a personal account.
489
+ Signed-in reads, visible-to-headless login, sending/readback and incoming updates
490
+ from a second verified client passed. This is approximate process accounting, not a guaranteed RAM cap or a
491
+ Linux measurement. Startup peaks, large accounts, overnight use, reconnect/MFA and
492
+ Linux/WSL2 signed-in operation still need validation. Size-oriented V8 settings may
493
+ trade CPU for memory. Avatar fields may be absent because UI photos are not loaded.
494
+ Sending still requires `init --allow-send` and the existing send confirmation.
495
+
363
496
  ## Graph boundaries
364
497
 
365
498
  - The Graph backend supports Microsoft 365 work or school accounts only. Use the experimental desktop backend on macOS, Windows or WSL for personal Microsoft accounts; the relevant Teams Graph endpoints do not support them.
366
499
  - A work account can authenticate successfully before Microsoft Teams is licensed or provisioned; `teams doctor` distinguishes identity access from Teams availability.
367
500
  - Calling and screen sharing are not available as a delegated user CLI; Graph calling is built around bot/application scenarios.
368
- - No extracted desktop tokens, browser interception, or direct calls to private Teams services; the experimental desktop backend reads the local client cache and sends only through the Teams window's own client.
501
+ - No extracted desktop tokens or direct calls to private Teams services; the experimental desktop backend reads the local client cache and sends only through the Teams window's own client.
369
502
  - Tenant policy and admin consent can restrict delegated capabilities.
370
503
 
371
504
  ## CLI Spec
@@ -2,7 +2,7 @@
2
2
 
3
3
  Microsoft Teams from your terminal—human-first and agent-first.
4
4
 
5
- `teams-cli` is a Rust CLI and TUI for everyday Microsoft Teams work through supported Microsoft Graph APIs, with an optional experimental desktop backend that reads chats from, and can send chat messages through, a running Teams client on macOS, Windows or WSL. It is designed as two equally first-class experiences over one typed core: a fast, calm terminal interface for people and a deterministic, introspectable command contract for agents. The default Graph backend signs in as you with delegated OAuth and protects refresh credentials in your operating system's credential store. Output is readable text on a TTY and structured JSON when piped.
5
+ `teams-cli` is a Rust CLI and TUI for everyday Microsoft Teams work through supported Microsoft Graph APIs, with an optional experimental desktop backend that reads chats from, and can send chat messages through, a running Teams client on macOS, Windows or WSL. A dedicated browser client also provides an experimental route on native Linux; see [browser setup](#dedicated-browser-client-experimental). It is designed as two equally first-class experiences over one typed core: a fast, calm terminal interface for people and a deterministic, introspectable command contract for agents. The default Graph backend signs in as you with delegated OAuth and protects refresh credentials in your operating system's credential store. Output is readable text on a TTY and structured JSON when piped.
6
6
 
7
7
  For humans, no arguments open guided onboarding on first use and the TUI afterward. For agents, no command ever waits for an undeclared prompt: `teams schema` works offline, collection sizes are caller-bounded, writes declare their effects, errors have stable kinds and exit codes, and stdout contains data only.
8
8
 
@@ -52,6 +52,16 @@ teams init
52
52
  teams init
53
53
  ```
54
54
 
55
+ For a personal Microsoft account with the lightweight hidden browser:
56
+
57
+ ```console
58
+ teams init --profile personal --backend desktop --desktop-client browser-core
59
+ ```
60
+
61
+ This downloads its browsers automatically, opens visible sign-in once, and then
62
+ runs hidden. See [lightweight browser setup](#low-memory-browser-client-experimental)
63
+ for sending, reconnecting, and experimental compatibility limits.
64
+
55
65
  Browser PKCE is the human-friendly default. The browser talks directly to Microsoft, and the CLI never receives or stores your password. Normal sign-in requests these delegated scopes:
56
66
 
57
67
  ```text
@@ -207,6 +217,15 @@ ids returned by `chats list`; personal chats are accessed through the desktop ba
207
217
  The chat with yourself is listed too, with the id `48:notes`, so
208
218
  you can try sending there first.
209
219
 
220
+ Desktop chat results may also include optional `avatarData` raster data URIs on
221
+ members and one-to-one chats. Photos come only from image resources already loaded
222
+ by that account's Teams page; the CLI never fetches a photo URL. Missing, stale,
223
+ ambiguous, or unsupported photos leave the fields absent, so clients should show
224
+ initials as a fallback. Reads allow at most 16 photos, 256 KiB per photo, 1 MiB of
225
+ avatar data in total, and a one-second budget. A photo failure never fails a chat
226
+ read. Personal photo reads have been checked on macOS; work/school and other
227
+ platforms still need photo-specific runtime validation.
228
+
210
229
  Desktop profiles are read-only unless created with `init --allow-send`;
211
230
  `TEAMS_READ_ONLY=true` still blocks sending, but `TEAMS_READ_ONLY=false` cannot unlock a
212
231
  desktop profile. With the opt-in, `messages send --chat` sends one plain-text message
@@ -344,12 +363,126 @@ This backend is experimental: chat reading depends on the Teams client's interna
344
363
  cache layout and sending on its internal send interface, and an unrecognised layout
345
364
  fails with `desktop_error` rather than guessing.
346
365
 
366
+ ### Dedicated browser client (experimental)
367
+
368
+ On native Linux, macOS, or WSL2, the desktop backend can use an installed Chrome,
369
+ Chromium, or Edge browser instead of the native Teams app. WSL2 uses a Linux browser;
370
+ visible sign-in requires WSLg or another graphical display. The existing Windows
371
+ helper route remains the default for native desktop profiles.
372
+
373
+ Create a separate read-only profile and sign in in the window that opens:
374
+
375
+ ```console
376
+ teams init --profile browser --backend desktop --desktop-client browser --desktop-account work-school --yes
377
+ # The window closes automatically after a successful chat read.
378
+ # Teams then runs headless with the same saved sign-in:
379
+ teams --profile browser
380
+ # To renew sign-in or complete MFA later:
381
+ teams --profile browser desktop login --yes
382
+ ```
383
+
384
+ Login waits up to five minutes for a readable chat cache, then restarts headless
385
+ and verifies another read before reporting success. An empty chat list is valid.
386
+ A sign-in timeout leaves the visible window open; finish signing in and retry
387
+ `desktop login`. Interrupting login preserves the browser and its saved profile.
388
+ On WSL2, the visible step needs WSLg or another graphical display.
389
+
390
+ Use `--desktop-account personal` for a personal Microsoft account. Sending still
391
+ requires an explicit `init --allow-send` and the existing send confirmation.
392
+ `init --no-login` only saves configuration. `desktop connect --visible` remains
393
+ available for manually managed sign-in; `desktop connect` starts headless.
394
+
395
+ The browser uses its own profile under the local data directory at
396
+ `teams-cli/browser/<port>/profile`; it never copies or opens your normal browser
397
+ profile. Different ports use different browser profiles. Disconnect closes this
398
+ browser and keeps its sign-in data. To switch between visible and headless operation,
399
+ disconnect first. `desktop recover` closes a verified managed browser or clears a
400
+ stale record when no listener remains. Unverifiable processes are left untouched.
401
+
402
+ The CLI checks that every listener is loopback-only and belongs to the recorded
403
+ browser process, running as you with the expected executable, debugging port and
404
+ dedicated profile. Only HTTPS Teams pages are used for reads or sends. While the
405
+ port is open, other programs running as you can access that browser session.
406
+
407
+ Browser discovery supports common system installations. For another installation,
408
+ set `TEAMS_DESKTOP_BROWSER` to the absolute path of the actual browser executable,
409
+ not a shell launcher. Snap/Flatpak installations are not automatically supported.
410
+ The optional `TEAMS_DESKTOP_BROWSER_DATA_DIR` sets an absolute browser data directory
411
+ (each port gets a separate subdirectory). Keep the same browser executable and data
412
+ directory settings while managing a session. Headless mode removes the visible window; Teams still consumes browser CPU and RAM.
413
+
414
+ This route is experimental. Browser lifecycle and ownership checks can be tested
415
+ without signing in, but live Teams Web reads, sends, session persistence and MFA
416
+ recovery still require validation on a signed-in machine. Existing native Teams
417
+ validation does not establish browser-client compatibility.
418
+
419
+ ### Low-memory browser client (experimental)
420
+
421
+ `--desktop-client browser-core` keeps the authenticated Teams data runtime without
422
+ starting the main UI. It supports personal accounts only and requires the
423
+ exact Teams Personal bootstrap build validated by this CLI. Unknown builds fail
424
+ with `desktop_error`; the CLI does not fall back to old cache data or the full UI.
425
+ Use `--desktop-client browser` on a separate port for the full web client.
426
+
427
+ Start with one command:
428
+
429
+ ```console
430
+ teams init --profile core --backend desktop --desktop-client browser-core
431
+ ```
432
+
433
+ The CLI downloads a matching **Chrome for Testing / chrome-headless-shell** pair
434
+ from Google's official Stable channel on first setup, opens a separate sign-in
435
+ window, and hides it once chats are readable. It verifies the hidden session before
436
+ saving your CLI profile. Core mode defaults to a personal account, port **9223**,
437
+ and read-only access; add `--allow-send` to enable sending. Your normal browser,
438
+ its profile, and the native Teams connection on port 9222 are left alone.
439
+
440
+ Browser binaries and sign-in state live in a private `teams-cli/browser` data
441
+ directory. The installed pair is reused offline and is not silently updated while
442
+ sessions are running. Setup needs roughly 1 GiB of disk space for the browsers and
443
+ extraction; the ZIP downloads are removed after installation. Linux/WSL2 need a
444
+ desktop display (WSLg on WSL2) for initial sign-in.
445
+
446
+ ```console
447
+ teams --profile core desktop login # Sign in again; automatically hide afterward
448
+ teams --profile core desktop disconnect # Stop the browser; retain sign-in
449
+ teams --profile core desktop connect # Start hidden using the saved session
450
+ teams --profile core desktop status # Inspect process and recovery state
451
+ ```
452
+
453
+ An interrupted sign-in retains the dedicated window and profile. Rerun `desktop
454
+ login` to finish; an expired hidden session can use the same command. A stale
455
+ process record is recovered automatically only when its port has no listener;
456
+ unrelated or changed processes are refused. `desktop recover` preserves sign-in
457
+ and can close only a verified managed browser. The status command does not prove
458
+ that you are signed in; `chats list` or `doctor` verifies message access.
459
+
460
+ Advanced setups can override both `TEAMS_DESKTOP_BROWSER` and
461
+ `TEAMS_DESKTOP_HEADLESS_BROWSER` with absolute paths to matching executables.
462
+ Set both or neither; a partial override is refused. `TEAMS_DESKTOP_BROWSER_DATA_DIR`
463
+ changes the private data location. Keep these settings stable while a session is
464
+ running. Unknown Teams builds are refused; use the full `browser` client on its
465
+ own port if your Teams build is not yet supported.
466
+
467
+ On macOS, both dedicated test browsers use mock-keychain encryption because the
468
+ headless shell uses it. This profile contains sensitive login state and remains in
469
+ a private directory; it never opens your ordinary browser profile.
470
+
471
+ The integrated CLI measured about **307 MiB after collection and 322 MiB idle**
472
+ of summed browser process physical footprint on macOS with a personal account.
473
+ Signed-in reads, visible-to-headless login, sending/readback and incoming updates
474
+ from a second verified client passed. This is approximate process accounting, not a guaranteed RAM cap or a
475
+ Linux measurement. Startup peaks, large accounts, overnight use, reconnect/MFA and
476
+ Linux/WSL2 signed-in operation still need validation. Size-oriented V8 settings may
477
+ trade CPU for memory. Avatar fields may be absent because UI photos are not loaded.
478
+ Sending still requires `init --allow-send` and the existing send confirmation.
479
+
347
480
  ## Graph boundaries
348
481
 
349
482
  - The Graph backend supports Microsoft 365 work or school accounts only. Use the experimental desktop backend on macOS, Windows or WSL for personal Microsoft accounts; the relevant Teams Graph endpoints do not support them.
350
483
  - A work account can authenticate successfully before Microsoft Teams is licensed or provisioned; `teams doctor` distinguishes identity access from Teams availability.
351
484
  - Calling and screen sharing are not available as a delegated user CLI; Graph calling is built around bot/application scenarios.
352
- - No extracted desktop tokens, browser interception, or direct calls to private Teams services; the experimental desktop backend reads the local client cache and sends only through the Teams window's own client.
485
+ - No extracted desktop tokens or direct calls to private Teams services; the experimental desktop backend reads the local client cache and sends only through the Teams window's own client.
353
486
  - Tenant policy and admin consent can restrict delegated capabilities.
354
487
 
355
488
  ## CLI Spec
@@ -23,6 +23,29 @@ do not bump again. Its tag starts the release workflow.
23
23
 
24
24
  Cargo and Python package versions are kept synchronized as one release unit.
25
25
 
26
+ ## Release 0.2.0
27
+
28
+ The managed lightweight browser is an experimental personal-account option on
29
+ macOS and Linux/WSL2. `teams init --backend desktop --desktop-client browser-core`
30
+ installs a matching Chrome for Testing pair from Google's Stable channel, uses
31
+ port 9223, and saves the CLI profile only after both visible and hidden chat reads
32
+ succeed. No version bump is needed: 0.2.0 is already prepared.
33
+
34
+ Fresh macOS onboarding passed real downloads, automatic hiding, signed-in reads,
35
+ one-shot sends with exact ID/content readback, incoming updates from a separate
36
+ verified client without reloading, reuse with the download network unavailable,
37
+ stale-process reconnection, repeated login, quiet mode, and verified shutdown.
38
+ The managed browser measured approximately 316 MiB idle process footprint in this
39
+ short personal-account run. macOS/Linux tests and lint, and Windows cross-compilation
40
+ cover the installer and integration. Linux signed-in use, MFA expiry, prolonged
41
+ operation and work/school core accounts remain outside the validated scope.
42
+
43
+ After publication workflows finish, verify every intended target:
44
+
45
+ ```sh
46
+ vership verify 0.2.0 --targets tag,release,crates,pypi
47
+ ```
48
+
26
49
  ## Release 0.1.11
27
50
 
28
51
  The `0.1.11` candidate builds on the desktop implementation validated in
@@ -128,3 +151,49 @@ uses the package version unless an explicit `version` is supplied, builds the
128
151
  real artifacts, and exercises registry validation without publishing packages,
129
152
  creating a GitHub release, generating attestations, or updating downstream
130
153
  packaging.
154
+
155
+ ## Browser-client validation
156
+
157
+ The optional `--desktop-client browser` route runs an isolated Chrome/Chromium/Edge
158
+ profile on macOS or Linux, including WSL2 with a Linux browser. A visible initial
159
+ sign-in and subsequent headless operation use the same persistent profile. Native
160
+ Teams and the WSL Windows helper retain their existing behavior.
161
+
162
+ Before claiming live browser compatibility, validate signed-in personal and
163
+ work/school cache reads, a single explicitly approved send with exact readback,
164
+ visible-to-headless session persistence, expired-sign-in recovery, and normal
165
+ shutdown on each intended platform. Linux process/socket tests and browser lifecycle
166
+ smoke tests establish plumbing behavior, not signed-in Teams compatibility.
167
+
168
+ Repeat the unsigned-in real-browser lifecycle test with a test installation:
169
+
170
+ ```sh
171
+ TEAMS_DESKTOP_BROWSER=/absolute/path/to/test/chromium cargo test --locked --test desktop browser_lifecycle_with_disposable_profile -- --ignored
172
+ ```
173
+
174
+ The test uses a disposable profile, preserves the normal browser profile, exercises
175
+ idempotent connect and mode-change refusal, and closes the managed browser on exit.
176
+ On macOS, use Chrome for Testing for this short-lived validation.
177
+
178
+ Automatic browser login opens visibly, waits for a successful chat read, closes
179
+ only the verified managed browser, and restarts headless with the same profile.
180
+ The headless chat read must also succeed. Test `desktop login` and browser `init`
181
+ with real sign-in, MFA, an empty chat list, expiry, and interrupted/timeout recovery
182
+ before claiming this automatic flow is validated on a platform.
183
+
184
+
185
+ ## Core browser release checks
186
+
187
+ `browser-core` is an opt-in, build-pinned personal-account route. It requires
188
+ a matching Chrome for Testing / chrome-headless-shell pair (automatically
189
+ installed from Google on first setup) and a separate private profile. A startup verifies the retained data client with a fresh chat
190
+ query before allowing cache reads or sends. Unknown bootstrap hashes and missing
191
+ readiness markers refuse operations; there is no automatic full-UI fallback.
192
+
193
+ Release validation should cover actual CLI connect/read/login/disconnect, incoming
194
+ updates, a separately approved send and exact readback, and footprint after those
195
+ operations. Pure browser prototype evidence is insufficient for CLI integration.
196
+ Run macOS and Linux checks; compile Windows native paths too. Do not claim Linux
197
+ signed-in compatibility, work/school core support, a fixed RAM limit, or long-running
198
+ reliability from the short personal-account macOS validation. Prototype reports,
199
+ profiles, public bundle copies and memory snapshots remain local and uncommitted.
@@ -20,9 +20,8 @@ impl TeamsBackend {
20
20
  BackendKind::Graph => Ok(Self::Graph(GraphClient::new(
21
21
  auth::access_token(&name, &selected).await?,
22
22
  ))),
23
- BackendKind::Desktop => Ok(Self::Desktop(Box::new(DesktopClient::with_account(
24
- selected.desktop_port,
25
- selected.desktop_account,
23
+ BackendKind::Desktop => Ok(Self::Desktop(Box::new(DesktopClient::from_profile(
24
+ &selected,
26
25
  )?))),
27
26
  }
28
27
  }
@@ -9,7 +9,9 @@ use crate::output::OutputFormat;
9
9
  version,
10
10
  long_version = env!("TEAMS_LONG_VERSION"),
11
11
  about = "Microsoft Teams from your terminal, for humans and agents",
12
- after_help = "Get started:\n teams init Guided setup\n teams tui --demo Explore without an account\n teams doctor Diagnose configuration and access\n teams schema --command 'messages send'\n Inspect the automation contract"
12
+ after_help = "Get started:\n teams init Guided setup\n teams init --backend desktop --desktop-client browser-core
13
+ Lightweight browser, personal account
14
+ teams tui --demo Explore without an account\n teams doctor Diagnose configuration and access\n teams schema --command 'messages send'\n Inspect the automation contract"
13
15
  )]
14
16
  pub struct Cli {
15
17
  /// Config profile to use
@@ -107,6 +109,9 @@ pub struct InitArgs {
107
109
  /// Select the personal or work/school Teams surface; auto refuses mixed surfaces
108
110
  #[arg(long, value_enum)]
109
111
  pub desktop_account: Option<crate::config::DesktopAccount>,
112
+ /// Use the native Teams app or a dedicated Chrome/Edge browser profile
113
+ #[arg(long, value_enum)]
114
+ pub desktop_client: Option<crate::config::DesktopClientKind>,
110
115
  /// Override the maintained teams-cli public-client application ID
111
116
  #[arg(long)]
112
117
  pub client_id: Option<String>,
@@ -262,16 +267,24 @@ pub struct TuiArgs {
262
267
  pub enum DesktopCommand {
263
268
  /// Inspect endpoint metadata and managed-session recovery state
264
269
  Status(DesktopArgs),
265
- /// Restart Teams with debugging enabled, preserving existing settings
270
+ /// Open a debugging session (native Teams restart or dedicated browser launch)
266
271
  Connect(DesktopArgs),
267
- /// Close this CLI's debugging session by restarting Teams normally
272
+ /// Sign in visibly, then automatically continue headless (browser only)
273
+ Login(DesktopArgs),
274
+ /// Close the dedicated browser, or restart native Teams normally
268
275
  Disconnect(DesktopArgs),
269
- /// Restore settings after interrupted setup, without restarting Teams
276
+ /// Recover interrupted native settings or close a managed browser session
270
277
  Recover(DesktopArgs),
271
278
  }
272
279
 
273
280
  #[derive(Debug, Args)]
274
281
  pub struct DesktopArgs {
282
+ /// Override the configured desktop client
283
+ #[arg(long, value_enum)]
284
+ pub desktop_client: Option<crate::config::DesktopClientKind>,
285
+ /// Open the browser visibly for sign-in (browser connect only)
286
+ #[arg(long)]
287
+ pub visible: bool,
275
288
  /// Override the selected profile's desktop port
276
289
  #[arg(long, value_parser = clap::value_parser!(u16).range(1024..))]
277
290
  pub port: Option<u16>,