teams-cli 0.1.1__tar.gz → 0.1.3__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 (30) hide show
  1. teams_cli-0.1.3/.github/workflows/ci.yml +54 -0
  2. {teams_cli-0.1.1 → teams_cli-0.1.3}/.github/workflows/release.yml +78 -25
  3. teams_cli-0.1.3/.mise.toml +22 -0
  4. {teams_cli-0.1.1 → teams_cli-0.1.3}/CHANGELOG.md +25 -3
  5. {teams_cli-0.1.1 → teams_cli-0.1.3}/Cargo.lock +1 -1
  6. {teams_cli-0.1.1 → teams_cli-0.1.3}/Cargo.toml +2 -2
  7. {teams_cli-0.1.1 → teams_cli-0.1.3}/Makefile +10 -1
  8. {teams_cli-0.1.1 → teams_cli-0.1.3}/PKG-INFO +43 -8
  9. {teams_cli-0.1.1 → teams_cli-0.1.3}/PRODUCT.md +2 -1
  10. {teams_cli-0.1.1 → teams_cli-0.1.3}/README.md +42 -7
  11. teams_cli-0.1.3/docs/releases.md +38 -0
  12. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/auth.rs +67 -9
  13. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/cli.rs +24 -1
  14. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/config.rs +53 -4
  15. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/error.rs +16 -6
  16. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/graph.rs +40 -3
  17. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/main.rs +158 -59
  18. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/output.rs +2 -2
  19. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/schema.rs +64 -6
  20. {teams_cli-0.1.1 → teams_cli-0.1.3}/tests/cli.rs +85 -11
  21. teams_cli-0.1.3/vership.toml +10 -0
  22. teams_cli-0.1.1/.github/workflows/ci.yml +0 -47
  23. {teams_cli-0.1.1 → teams_cli-0.1.3}/.github/dependabot.yml +0 -0
  24. {teams_cli-0.1.1 → teams_cli-0.1.3}/.gitignore +0 -0
  25. {teams_cli-0.1.1 → teams_cli-0.1.3}/DESIGN.md +0 -0
  26. {teams_cli-0.1.1 → teams_cli-0.1.3}/LICENSE +0 -0
  27. {teams_cli-0.1.1 → teams_cli-0.1.3}/examples/validate_schema.rs +0 -0
  28. {teams_cli-0.1.1 → teams_cli-0.1.3}/pyproject.toml +0 -0
  29. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/lib.rs +0 -0
  30. {teams_cli-0.1.1 → teams_cli-0.1.3}/src/tui.rs +0 -0
@@ -0,0 +1,54 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_call:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ name: Test (${{ matrix.os }})
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ os: [ubuntu-latest, macos-latest, windows-latest]
18
+ runs-on: ${{ matrix.os }}
19
+ steps:
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
21
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
22
+ with:
23
+ toolchain: 1.96.0
24
+ components: rustfmt, clippy
25
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
26
+ - run: cargo test --locked --all-targets
27
+
28
+ quality:
29
+ name: Formatting and lints
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
33
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
34
+ with:
35
+ toolchain: 1.96.0
36
+ components: rustfmt, clippy
37
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
38
+ - run: cargo fmt -- --check
39
+ - run: cargo clippy --locked --all-targets -- -D warnings
40
+
41
+ security:
42
+ name: Dependency audit
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
46
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
47
+ with:
48
+ toolchain: 1.96.0
49
+ components: rustfmt, clippy
50
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
51
+ - name: Install cargo-audit
52
+ run: cargo install cargo-audit --version 0.22.2 --locked
53
+ - name: Audit Cargo.lock
54
+ run: cargo audit
@@ -3,6 +3,17 @@ name: Release
3
3
  on:
4
4
  push:
5
5
  tags: ["v[0-9]*.[0-9]*.[0-9]*"]
6
+ workflow_dispatch:
7
+ inputs:
8
+ version:
9
+ description: "Version to rehearse (defaults to Cargo.toml)"
10
+ required: false
11
+ type: string
12
+ dry_run:
13
+ description: "Build and validate without publishing"
14
+ required: true
15
+ default: true
16
+ type: boolean
6
17
 
7
18
  permissions:
8
19
  contents: read
@@ -12,11 +23,23 @@ jobs:
12
23
  name: Verify release version
13
24
  runs-on: ubuntu-latest
14
25
  steps:
15
- - uses: actions/checkout@v7
26
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
16
27
  - name: Match tag to package version
28
+ env:
29
+ REQUESTED_VERSION: ${{ inputs.version }}
30
+ DRY_RUN: ${{ inputs.dry_run }}
17
31
  run: |
18
- version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)"
19
- test "v${version}" = "$GITHUB_REF_NAME"
32
+ manifest_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)"
33
+ version="${REQUESTED_VERSION#v}"
34
+ version="${version:-$manifest_version}"
35
+ [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]
36
+ test "$version" = "$manifest_version"
37
+ if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
38
+ test "v${version}" = "$GITHUB_REF_NAME"
39
+ elif [[ "$DRY_RUN" != "true" ]]; then
40
+ test "$GITHUB_REF_TYPE" = "tag"
41
+ test "v${version}" = "$GITHUB_REF_NAME"
42
+ fi
20
43
 
21
44
  verify:
22
45
  uses: ./.github/workflows/ci.yml
@@ -46,11 +69,13 @@ jobs:
46
69
  id-token: write
47
70
  attestations: write
48
71
  steps:
49
- - uses: actions/checkout@v7
50
- - uses: dtolnay/rust-toolchain@stable
72
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
73
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
51
74
  with:
75
+ toolchain: 1.96.0
76
+ components: rustfmt, clippy
52
77
  targets: ${{ matrix.target }}
53
- - uses: Swatinem/rust-cache@v2
78
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
54
79
  with:
55
80
  key: ${{ matrix.target }}
56
81
  - run: cargo build --locked --release --target ${{ matrix.target }}
@@ -63,10 +88,11 @@ jobs:
63
88
  tar -C dist/package -czf "dist/teams-cli-${GITHUB_REF_NAME}-${{ matrix.target }}.tar.gz" .
64
89
  rm -rf dist/package
65
90
  - name: Attest build provenance
66
- uses: actions/attest@v4
91
+ if: ${{ inputs.dry_run != true }}
92
+ uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6
67
93
  with:
68
94
  subject-path: dist/*.tar.gz
69
- - uses: actions/upload-artifact@v7
95
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
70
96
  with:
71
97
  name: binary-${{ matrix.target }}
72
98
  path: dist/*.tar.gz
@@ -93,7 +119,7 @@ jobs:
93
119
  manylinux: auto
94
120
  runs-on: ${{ matrix.os }}
95
121
  steps:
96
- - uses: actions/checkout@v7
122
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
97
123
  - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
98
124
  with:
99
125
  maturin-version: v1.14.1
@@ -101,7 +127,12 @@ jobs:
101
127
  target: ${{ matrix.target }}
102
128
  manylinux: ${{ matrix.manylinux }}
103
129
  args: --release --locked --compatibility pypi --out dist
104
- - uses: actions/upload-artifact@v7
130
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d
131
+ if: ${{ inputs.dry_run == true }}
132
+ - name: Validate wheel metadata
133
+ if: ${{ inputs.dry_run == true }}
134
+ run: uvx --from twine==6.2.0 twine check --strict dist/*.whl
135
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
105
136
  with:
106
137
  name: wheel-${{ matrix.target }}
107
138
  path: dist/*.whl
@@ -112,16 +143,27 @@ jobs:
112
143
  needs: [version, verify]
113
144
  runs-on: ubuntu-latest
114
145
  steps:
115
- - uses: actions/checkout@v7
116
- - uses: dtolnay/rust-toolchain@stable
146
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
147
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
148
+ with:
149
+ toolchain: 1.96.0
150
+ components: rustfmt, clippy
117
151
  - run: cargo package --locked
152
+ - name: Validate crates.io package
153
+ if: ${{ inputs.dry_run == true }}
154
+ run: cargo publish --locked --dry-run
118
155
  - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
119
156
  with:
120
157
  maturin-version: v1.14.1
121
158
  command: sdist
122
159
  args: --out dist
123
160
  - run: cp target/package/*.crate dist/
124
- - uses: actions/upload-artifact@v7
161
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d
162
+ if: ${{ inputs.dry_run == true }}
163
+ - name: Validate source distribution metadata
164
+ if: ${{ inputs.dry_run == true }}
165
+ run: uvx --from twine==6.2.0 twine check --strict dist/*.tar.gz
166
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
125
167
  with:
126
168
  name: source-distributions
127
169
  path: dist/*
@@ -136,13 +178,13 @@ jobs:
136
178
  id-token: write
137
179
  attestations: write
138
180
  steps:
139
- - uses: actions/checkout@v7
140
- - uses: actions/download-artifact@v8
181
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
182
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
141
183
  with:
142
184
  pattern: binary-*
143
185
  path: dist
144
186
  merge-multiple: true
145
- - uses: actions/download-artifact@v8
187
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
146
188
  with:
147
189
  name: source-distributions
148
190
  path: dist
@@ -155,11 +197,13 @@ jobs:
155
197
  working-directory: dist
156
198
  run: sha256sum ./*.tar.gz ./*.crate ./*.json > SHA256SUMS
157
199
  - name: Attest artifacts and SBOM
158
- uses: actions/attest@v4
200
+ if: ${{ inputs.dry_run != true }}
201
+ uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6
159
202
  with:
160
203
  subject-path: dist/*
161
204
  sbom-path: dist/teams-cli.cdx.json
162
205
  - name: Create GitHub release
206
+ if: ${{ inputs.dry_run != true }}
163
207
  env:
164
208
  GH_TOKEN: ${{ github.token }}
165
209
  run: gh release create "$GITHUB_REF_NAME" dist/* --verify-tag --generate-notes
@@ -167,13 +211,17 @@ jobs:
167
211
  crates-io:
168
212
  name: Publish crates.io
169
213
  needs: github-release
214
+ if: ${{ inputs.dry_run != true }}
170
215
  runs-on: ubuntu-latest
171
216
  environment: crates-io
172
217
  permissions:
173
218
  id-token: write
174
219
  steps:
175
- - uses: actions/checkout@v7
176
- - uses: dtolnay/rust-toolchain@stable
220
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
221
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
222
+ with:
223
+ toolchain: 1.96.0
224
+ components: rustfmt, clippy
177
225
  - name: Check whether this version is already published
178
226
  id: crate
179
227
  shell: bash
@@ -191,7 +239,7 @@ jobs:
191
239
  - name: Get temporary crates.io token
192
240
  if: steps.crate.outputs.exists != 'true'
193
241
  id: crates-auth
194
- uses: rust-lang/crates-io-auth-action@v1
242
+ uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18
195
243
  - if: steps.crate.outputs.exists != 'true'
196
244
  run: cargo publish --locked
197
245
  env:
@@ -202,6 +250,7 @@ jobs:
202
250
  pypi:
203
251
  name: Publish PyPI
204
252
  needs: github-release
253
+ if: ${{ inputs.dry_run != true }}
205
254
  runs-on: ubuntu-latest
206
255
  environment:
207
256
  name: pypi
@@ -209,28 +258,32 @@ jobs:
209
258
  permissions:
210
259
  id-token: write
211
260
  steps:
212
- - uses: actions/download-artifact@v8
261
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
213
262
  with:
214
263
  pattern: wheel-*
215
264
  path: dist
216
265
  merge-multiple: true
217
- - uses: actions/download-artifact@v8
266
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
218
267
  with:
219
268
  name: source-distributions
220
269
  path: source
221
270
  - name: Collect Python distributions
222
271
  run: find source -name '*.tar.gz' -exec cp {} dist/ \;
223
- - uses: pypa/gh-action-pypi-publish@release/v1
272
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
224
273
  with:
225
274
  packages-dir: dist
226
275
 
227
276
  post-publish-smoke:
228
277
  name: Verify public packages
229
278
  needs: [crates-io, pypi]
279
+ if: ${{ inputs.dry_run != true }}
230
280
  runs-on: ubuntu-latest
231
281
  steps:
232
- - uses: dtolnay/rust-toolchain@stable
233
- - uses: actions/setup-python@v7
282
+ - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
283
+ with:
284
+ toolchain: 1.96.0
285
+ components: rustfmt, clippy
286
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
234
287
  with:
235
288
  python-version: "3.x"
236
289
  - name: Install and run the crates.io package
@@ -0,0 +1,22 @@
1
+ # Reproducible local and CI tooling.
2
+ min_version = "2026.4.19"
3
+
4
+ [env]
5
+ RUST_BACKTRACE = "1"
6
+ BINSTALL_DISABLE_TELEMETRY = "true"
7
+
8
+ [tools]
9
+ rust = "1.96.0"
10
+ actionlint = "1.7.12"
11
+ cargo-binstall = "1.22.0"
12
+ "cargo:cargo-nextest" = "0.9.143"
13
+
14
+ [settings]
15
+ cargo.binstall = true
16
+
17
+ [tasks.setup]
18
+ description = "Install the exact development toolchain"
19
+ run = """
20
+ mise install rust actionlint cargo-binstall
21
+ mise install cargo:cargo-nextest
22
+ """
@@ -5,6 +5,31 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.3](https://github.com/rvben/teams-cli/compare/v0.1.2...v0.1.3) - 2026-08-26
11
+
12
+ ### Added
13
+
14
+ - **cli**: align safety and configuration contract ([c564f63](https://github.com/rvben/teams-cli/commit/c564f63f7deeb4e8a7c8faa359e32f3ea0da8556))
15
+
16
+ ### Fixed
17
+
18
+ - **config**: persist profiles atomically ([ababe5e](https://github.com/rvben/teams-cli/commit/ababe5e46f24a52d86007de5737cd7601a1ae0c3))
19
+ - **ci**: install pinned Rust components ([f5ecdcc](https://github.com/rvben/teams-cli/commit/f5ecdccfb4ca697ff23cc828393e519a788cbea8))
20
+
21
+ ## [0.1.2] - 2026-08-24
22
+
23
+ ### Added
24
+
25
+ - Bundled the maintained multitenant teams-cli Entra public-client registration for zero-ID onboarding.
26
+ - Added explicit `--channel-history` consent and machine-readable authentication capability metadata.
27
+
28
+ ### Changed
29
+
30
+ - Normal login and token refresh now use only the user-consentable delegated permission baseline.
31
+ - Diagnostics now distinguish successful Microsoft identity access from an unlicensed or unprovisioned Teams tenant.
32
+
8
33
  ## [0.1.1] - 2026-08-24
9
34
 
10
35
  ### Fixed
@@ -21,6 +46,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
46
  - Commands for identity, joined teams, channels, chats, and reading or sending messages through Microsoft Graph.
22
47
  - Guided onboarding, diagnostics, completions, bounded collections, and deterministic TUI snapshots.
23
48
  - Distribution through crates.io, PyPI binary wheels, and signed GitHub release artifacts.
24
-
25
- [0.1.1]: https://github.com/rvben/teams-cli/compare/v0.1.0...v0.1.1
26
- [0.1.0]: https://github.com/rvben/teams-cli/releases/tag/v0.1.0
@@ -2964,7 +2964,7 @@ dependencies = [
2964
2964
 
2965
2965
  [[package]]
2966
2966
  name = "teams-cli"
2967
- version = "0.1.1"
2967
+ version = "0.1.3"
2968
2968
  dependencies = [
2969
2969
  "assert_cmd",
2970
2970
  "base64 0.23.1",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "teams-cli"
3
- version = "0.1.1"
3
+ version = "0.1.3"
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"
@@ -31,6 +31,7 @@ serde = { version = "1", features = ["derive"] }
31
31
  serde_json = "1"
32
32
  sha2 = "0.10"
33
33
  thiserror = "2"
34
+ tempfile = "3"
34
35
  tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "io-util", "time"] }
35
36
  toml = "1.1"
36
37
  url = "2"
@@ -39,5 +40,4 @@ url = "2"
39
40
  assert_cmd = "2"
40
41
  jsonschema = "0.49.8"
41
42
  predicates = "3"
42
- tempfile = "3"
43
43
  wiremock = "0.6"
@@ -1,4 +1,4 @@
1
- .PHONY: build test lint fmt check release install clean
1
+ .PHONY: build test lint fmt check release install clean release-patch release-minor release-major
2
2
 
3
3
  build:
4
4
  cargo build --locked
@@ -23,3 +23,12 @@ install: check release
23
23
 
24
24
  clean:
25
25
  cargo clean
26
+
27
+ release-patch:
28
+ vership bump patch
29
+
30
+ release-minor:
31
+ vership bump minor
32
+
33
+ release-major:
34
+ vership bump major
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teams-cli
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  License-File: LICENSE
@@ -57,28 +57,44 @@ Run `teams` in a terminal on first use, or start explicitly:
57
57
  teams init
58
58
  ```
59
59
 
60
- You need a Microsoft Entra public-client application:
60
+ `teams-cli` includes a maintained multitenant Microsoft Entra public-client registration, so normal setup does not require creating an app or copying a client ID:
61
61
 
62
- 1. In [Microsoft Entra app registrations](https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade), register an app for organizational accounts.
63
- 2. Under **Authentication**, add the **Mobile and desktop applications** redirect URI `http://localhost` and enable public client flows.
64
- 3. Under **API permissions**, add these delegated Microsoft Graph permissions (plus the standard OpenID scopes requested at sign-in):
62
+ ```console
63
+ teams init
64
+ ```
65
+
66
+ 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:
65
67
 
66
68
  ```text
67
69
  openid profile offline_access User.Read Team.ReadBasic.All
68
70
  Channel.ReadBasic.All Chat.Read Chat.Create ChatMessage.Send
69
- ChannelMessage.Send ChannelMessage.Read.All
71
+ ChannelMessage.Send
70
72
  ```
71
73
 
72
- Reading channel messages requires the administrator-consented `ChannelMessage.Read.All` permission. If the tenant has not granted it, Microsoft may require an administrator during sign-in. The CLI surfaces missing consent as a stable `permission_denied` error instead of silently reaching for undocumented Teams APIs.
74
+ Reading channel history is intentionally separate because `ChannelMessage.Read.All` requires administrator consent. Request it only when needed:
75
+
76
+ ```console
77
+ teams auth login --channel-history
78
+ ```
79
+
80
+ The CLI surfaces missing consent as a stable `permission_denied` error instead of silently reaching for undocumented Teams APIs. Tenant policy can still require an administrator to approve otherwise user-consentable permissions.
73
81
 
74
82
  Headless setup is explicit and never prompts:
75
83
 
76
84
  ```console
77
- teams init --client-id "$TEAMS_CLIENT_ID" --tenant organizations --no-login
85
+ teams init --no-login
78
86
  teams auth login --device-code
79
87
  teams whoami --output json
80
88
  ```
81
89
 
90
+ Organizations that prefer their own Entra registration can override the bundled application:
91
+
92
+ ```console
93
+ teams init --client-id "$TEAMS_CLIENT_ID" --tenant organizations --no-login
94
+ ```
95
+
96
+ The custom app must be a public client with `http://localhost` registered for browser login, public-client flows enabled for device-code login, and the baseline delegated permissions listed above.
97
+
82
98
  For short-lived automation, `TEAMS_ACCESS_TOKEN` overrides the stored credential. Refresh credentials are never written to the config file.
83
99
 
84
100
  ## Everyday commands
@@ -88,16 +104,30 @@ teams list
88
104
  teams channels list TEAM_ID
89
105
  teams chats list --limit 25
90
106
  teams messages list --chat CHAT_ID
107
+ teams auth login --channel-history # once, with administrator approval
108
+ teams messages list --team TEAM_ID --channel CHANNEL_ID
91
109
  teams messages send --chat CHAT_ID --body "On it."
92
110
  printf 'Status update' | teams messages send --chat CHAT_ID --body -
111
+ teams config show
93
112
  teams doctor
94
113
  ```
95
114
 
96
115
  Collection commands expose `--limit` and `--fields`; Graph-native collections use `--cursor`, while joined teams and channels use `--offset` because those endpoints do not accept `$top`. With a terminal they render concise text; when stdout is piped they emit JSON envelopes with continuation metadata and `truncated`.
97
116
 
117
+ Profiles can block every remote write while keeping reads, sign-in, diagnostics, and the TUI available:
118
+
119
+ ```console
120
+ teams init --no-login --read-only
121
+ # or for one invocation/environment
122
+ TEAMS_READ_ONLY=true teams messages send --chat CHAT_ID --body "Blocked"
123
+ ```
124
+
125
+ Like the other tools in the suite, the config file is selected with `--profile`, overridden by `TEAMS_*` environment variables, and inspectable without exposing credentials through `teams config show` and `teams config path`.
126
+
98
127
  ## Boundaries
99
128
 
100
129
  - Microsoft 365 work or school accounts only. The relevant Teams Graph endpoints do not support personal Microsoft accounts.
130
+ - A work account can authenticate successfully before Microsoft Teams is licensed or provisioned; `teams doctor` distinguishes identity access from Teams availability.
101
131
  - Calling and screen sharing are not available as a delegated user CLI; Graph calling is built around bot/application scenarios.
102
132
  - No extracted desktop tokens, browser interception, or private Teams protocols.
103
133
  - Tenant policy and admin consent can restrict delegated capabilities.
@@ -106,3 +136,8 @@ Collection commands expose `--limit` and `--fields`; Graph-native collections us
106
136
 
107
137
  `teams schema` is offline, unauthenticated, and follows [CLI Spec v0.3](https://clispec.dev/): flat invocable command paths, declared effects and cardinality, bounded collections, stable error kinds and exit codes, stdout/stderr separation, and safe noninteractive behavior.
108
138
 
139
+ ## Releasing
140
+
141
+ Vership owns versioning, changelog generation, release commits, and tags. See
142
+ [the release runbook](docs/releases.md) for the verified workflow and recovery policy.
143
+
@@ -29,12 +29,13 @@ A human-first and agent-first Microsoft Teams terminal client: a polished TUI an
29
29
  - Installed from PyPI, preferably with `pipx install teams-cli`, despite the implementation being Rust.
30
30
  - Invoked as `teams` alongside sibling tools such as `jira` and `confluence`.
31
31
  - Used interactively in a local terminal, non-interactively in shell pipelines, and by AI agents.
32
- - Authenticates a Microsoft 365 work or school account through a customer-owned Microsoft Entra public-client application.
32
+ - Authenticates a Microsoft 365 work or school account through the maintained teams-cli Entra public-client application, with an explicit override for customer-owned registrations.
33
33
  - Opens the native or web Teams client for experiences that Microsoft Graph does not expose, such as normal user calls.
34
34
 
35
35
  ## Capabilities and Constraints
36
36
 
37
37
  - Browser-based delegated OAuth with PKCE is the preferred login; device-code flow is an explicit headless fallback.
38
+ - Everyday commands use a user-consentable baseline; administrator-consented channel history is a deliberate login opt-in.
38
39
  - Refresh credentials must use OS-protected storage and must never be written as plaintext tokens.
39
40
  - Initial product surface covers onboarding, authentication diagnostics, identity, teams, channels, chats, messages, and the TUI foundation.
40
41
  - Commands offer human text and structured JSON output, with clean stdout/stderr separation.
@@ -41,28 +41,44 @@ Run `teams` in a terminal on first use, or start explicitly:
41
41
  teams init
42
42
  ```
43
43
 
44
- You need a Microsoft Entra public-client application:
44
+ `teams-cli` includes a maintained multitenant Microsoft Entra public-client registration, so normal setup does not require creating an app or copying a client ID:
45
45
 
46
- 1. In [Microsoft Entra app registrations](https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade), register an app for organizational accounts.
47
- 2. Under **Authentication**, add the **Mobile and desktop applications** redirect URI `http://localhost` and enable public client flows.
48
- 3. Under **API permissions**, add these delegated Microsoft Graph permissions (plus the standard OpenID scopes requested at sign-in):
46
+ ```console
47
+ teams init
48
+ ```
49
+
50
+ 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:
49
51
 
50
52
  ```text
51
53
  openid profile offline_access User.Read Team.ReadBasic.All
52
54
  Channel.ReadBasic.All Chat.Read Chat.Create ChatMessage.Send
53
- ChannelMessage.Send ChannelMessage.Read.All
55
+ ChannelMessage.Send
54
56
  ```
55
57
 
56
- Reading channel messages requires the administrator-consented `ChannelMessage.Read.All` permission. If the tenant has not granted it, Microsoft may require an administrator during sign-in. The CLI surfaces missing consent as a stable `permission_denied` error instead of silently reaching for undocumented Teams APIs.
58
+ Reading channel history is intentionally separate because `ChannelMessage.Read.All` requires administrator consent. Request it only when needed:
59
+
60
+ ```console
61
+ teams auth login --channel-history
62
+ ```
63
+
64
+ The CLI surfaces missing consent as a stable `permission_denied` error instead of silently reaching for undocumented Teams APIs. Tenant policy can still require an administrator to approve otherwise user-consentable permissions.
57
65
 
58
66
  Headless setup is explicit and never prompts:
59
67
 
60
68
  ```console
61
- teams init --client-id "$TEAMS_CLIENT_ID" --tenant organizations --no-login
69
+ teams init --no-login
62
70
  teams auth login --device-code
63
71
  teams whoami --output json
64
72
  ```
65
73
 
74
+ Organizations that prefer their own Entra registration can override the bundled application:
75
+
76
+ ```console
77
+ teams init --client-id "$TEAMS_CLIENT_ID" --tenant organizations --no-login
78
+ ```
79
+
80
+ The custom app must be a public client with `http://localhost` registered for browser login, public-client flows enabled for device-code login, and the baseline delegated permissions listed above.
81
+
66
82
  For short-lived automation, `TEAMS_ACCESS_TOKEN` overrides the stored credential. Refresh credentials are never written to the config file.
67
83
 
68
84
  ## Everyday commands
@@ -72,16 +88,30 @@ teams list
72
88
  teams channels list TEAM_ID
73
89
  teams chats list --limit 25
74
90
  teams messages list --chat CHAT_ID
91
+ teams auth login --channel-history # once, with administrator approval
92
+ teams messages list --team TEAM_ID --channel CHANNEL_ID
75
93
  teams messages send --chat CHAT_ID --body "On it."
76
94
  printf 'Status update' | teams messages send --chat CHAT_ID --body -
95
+ teams config show
77
96
  teams doctor
78
97
  ```
79
98
 
80
99
  Collection commands expose `--limit` and `--fields`; Graph-native collections use `--cursor`, while joined teams and channels use `--offset` because those endpoints do not accept `$top`. With a terminal they render concise text; when stdout is piped they emit JSON envelopes with continuation metadata and `truncated`.
81
100
 
101
+ Profiles can block every remote write while keeping reads, sign-in, diagnostics, and the TUI available:
102
+
103
+ ```console
104
+ teams init --no-login --read-only
105
+ # or for one invocation/environment
106
+ TEAMS_READ_ONLY=true teams messages send --chat CHAT_ID --body "Blocked"
107
+ ```
108
+
109
+ Like the other tools in the suite, the config file is selected with `--profile`, overridden by `TEAMS_*` environment variables, and inspectable without exposing credentials through `teams config show` and `teams config path`.
110
+
82
111
  ## Boundaries
83
112
 
84
113
  - Microsoft 365 work or school accounts only. The relevant Teams Graph endpoints do not support personal Microsoft accounts.
114
+ - A work account can authenticate successfully before Microsoft Teams is licensed or provisioned; `teams doctor` distinguishes identity access from Teams availability.
85
115
  - Calling and screen sharing are not available as a delegated user CLI; Graph calling is built around bot/application scenarios.
86
116
  - No extracted desktop tokens, browser interception, or private Teams protocols.
87
117
  - Tenant policy and admin consent can restrict delegated capabilities.
@@ -89,3 +119,8 @@ Collection commands expose `--limit` and `--fields`; Graph-native collections us
89
119
  ## CLI Spec
90
120
 
91
121
  `teams schema` is offline, unauthenticated, and follows [CLI Spec v0.3](https://clispec.dev/): flat invocable command paths, declared effects and cardinality, bounded collections, stable error kinds and exit codes, stdout/stderr separation, and safe noninteractive behavior.
122
+
123
+ ## Releasing
124
+
125
+ Vership owns versioning, changelog generation, release commits, and tags. See
126
+ [the release runbook](docs/releases.md) for the verified workflow and recovery policy.
@@ -0,0 +1,38 @@
1
+ # Releases
2
+
3
+ Vership is the release control plane for `teams-cli`. GitHub Actions is the
4
+ execution plane for cross-platform builds, publication, provenance, and
5
+ downstream packaging.
6
+
7
+ ## Create a release
8
+
9
+ Start from a clean, up-to-date `main` branch and run:
10
+
11
+ ```sh
12
+ vership preflight
13
+ vership bump patch # or minor/major
14
+ tarry cmd --timeout 20m -- vership verify
15
+ ```
16
+
17
+ Vership runs `make check`, updates the package version and changelog, creates
18
+ the Conventional Commit release commit and tag, and pushes both. The tag starts
19
+ the release workflow. Use `vership release` only when the on-disk version was
20
+ intentionally set in advance.
21
+
22
+ Cargo and Python package versions are kept synchronized as one release unit.
23
+
24
+ ## Failure policy
25
+
26
+ If a release fails before any release, artifact, package, checksum, or
27
+ attestation becomes public, delete and recreate the brief tag and retry the
28
+ same version. Once anything was published, preserve the tag and issue a patch
29
+ release for release-content corrections. Registry and downstream-packaging
30
+ jobs are separate recovery domains; retry only the failed job when possible.
31
+
32
+ ## Dry runs
33
+
34
+ The Release workflow can be dispatched from `main` with `dry_run` enabled. It
35
+ uses the package version unless an explicit `version` is supplied, builds the
36
+ real artifacts, and exercises registry validation without publishing packages,
37
+ creating a GitHub release, generating attestations, or updating downstream
38
+ packaging.