ask-marcel-office-cli 2.0.0 → 2.2.0
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.
- package/CHANGELOG.md +269 -0
- package/README.md +178 -118
- package/dist/cli.js +44841 -26996
- package/dist/commands.json +423 -51
- package/dist/composition/build-deps.d.ts +8 -0
- package/dist/composition/mcp.d.ts +20 -0
- package/dist/composition/run-registry-command.d.ts +34 -0
- package/dist/domain/tenant-id.d.ts +9 -0
- package/dist/domain/utilities/spo-tenant.d.ts +20 -0
- package/dist/index.js +2671 -1463
- package/dist/infra/auth.d.ts +55 -3
- package/dist/infra/browser-auth.d.ts +10 -3
- package/dist/infra/graph-client.d.ts +62 -0
- package/dist/presenter/graph-cursor.d.ts +2 -0
- package/dist/presenter/output.d.ts +1 -1
- package/dist/presenter/render-to-string.d.ts +15 -0
- package/dist/use-cases/commands/build-command.d.ts +19 -2
- package/dist/use-cases/commands/command-types.d.ts +16 -11
- package/dist/use-cases/commands/convert-calendar-event-attachment-to-markdown.d.ts +4 -0
- package/dist/use-cases/commands/{convert-drive-item-zip.d.ts → convert-drive-item-zip-to-markdown.d.ts} +5 -0
- package/dist/use-cases/commands/{convert-local-file.d.ts → convert-local-file-to-markdown.d.ts} +8 -0
- package/dist/use-cases/commands/convert-mail-attachment-to-markdown.d.ts +10 -2
- package/dist/use-cases/commands/{convert-mail-attachment-zip.d.ts → convert-mail-attachment-zip-to-markdown.d.ts} +5 -1
- package/dist/use-cases/commands/convert-mail-to-markdown.d.ts +35 -1
- package/dist/use-cases/commands/create-forward-draft.d.ts +16 -0
- package/dist/use-cases/commands/create-reply-draft.d.ts +5 -1
- package/dist/use-cases/commands/download-drive-item-as-markdown.d.ts +7 -2
- package/dist/use-cases/commands/download-drive-item-as-pdf.d.ts +1 -0
- package/dist/use-cases/commands/download-drive-item-content.d.ts +1 -0
- package/dist/use-cases/commands/draft-comment-splicer.d.ts +29 -0
- package/dist/use-cases/commands/draft-dedup.d.ts +18 -0
- package/dist/use-cases/commands/draft-response.d.ts +4 -0
- package/dist/use-cases/commands/extract-drive-item-images.d.ts +1 -0
- package/dist/use-cases/commands/extract-local-file-images.d.ts +1 -1
- package/dist/use-cases/commands/fetch-raw-bytes.d.ts +13 -0
- package/dist/use-cases/commands/find-mail-drafts.d.ts +9 -0
- package/dist/use-cases/commands/get-mail-signature.d.ts +8 -0
- package/dist/use-cases/commands/get-user.d.ts +10 -0
- package/dist/use-cases/commands/image-extraction.d.ts +2 -1
- package/dist/use-cases/commands/inline-image-embedder.d.ts +2 -1
- package/dist/use-cases/commands/login-status.d.ts +13 -0
- package/dist/use-cases/commands/login.d.ts +24 -1
- package/dist/use-cases/commands/mail-message-select.d.ts +1 -0
- package/dist/use-cases/commands/mail-quote-stripper.d.ts +16 -15
- package/dist/use-cases/commands/markdown-dispatch.d.ts +2 -1
- package/dist/use-cases/commands/msg-to-markdown.d.ts +10 -1
- package/dist/use-cases/commands/odata-query.d.ts +15 -1
- package/dist/use-cases/commands/office-to-markdown.d.ts +1 -0
- package/dist/use-cases/commands/parse-recipients.d.ts +14 -0
- package/dist/use-cases/commands/read-mail-attachment.d.ts +4 -0
- package/dist/use-cases/commands/resolve-command.d.ts +25 -0
- package/dist/use-cases/commands/search-all-files.d.ts +8 -0
- package/dist/use-cases/commands/search-escape.d.ts +23 -0
- package/dist/use-cases/commands/signature-extractor.d.ts +2 -0
- package/dist/use-cases/commands/tenant-option.d.ts +44 -0
- package/dist/use-cases/commands/update-mail-draft.d.ts +1 -0
- package/dist/use-cases/commands/zip-archive-to-markdown.d.ts +12 -5
- package/dist/use-cases/ports/filesystem.d.ts +1 -1
- package/docs/COMMANDS.md +34 -28
- package/docs/USAGE.md +65 -7
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,275 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `ask-marcel-office-cli` are documented here.
|
|
4
4
|
|
|
5
|
+
## 2.2.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`ask-marcel-office mcp` — serve the whole CLI to any MCP client over stdio.** Hosts
|
|
10
|
+
without a shell (Claude Desktop, other MCP clients) can now reach every
|
|
11
|
+
command. Register it with:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
claude mcp add --transport stdio --scope user ask-marcel-office -- ask-marcel-office mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It exposes **five gateway tools**, not one per command — 183 tool schemas
|
|
18
|
+
would inject hundreds of KB into every session, the exact token bloat this
|
|
19
|
+
CLI exists to avoid:
|
|
20
|
+
|
|
21
|
+
- `list-commands` — the terse manifest (`{name, summary, category}`),
|
|
22
|
+
optionally filtered to one category. The discovery entry point.
|
|
23
|
+
- `get-command-docs` — full Markdown docs for a single command: every option,
|
|
24
|
+
its Graph endpoint, an example, the response shape. Lifecycle commands and
|
|
25
|
+
deprecated command names both resolve.
|
|
26
|
+
- `run-command` — runs any of the **179 read** commands. Declared
|
|
27
|
+
`readOnlyHint: true`, so an MCP client can auto-approve it; a write routed
|
|
28
|
+
here is refused *before* it executes.
|
|
29
|
+
- `run-write-command` — runs the **4** mail-draft write commands
|
|
30
|
+
(`create-mail-draft`, `create-forward-draft`, `create-reply-draft`,
|
|
31
|
+
`update-mail-draft`). A separate tool precisely so the 179 read commands
|
|
32
|
+
keep an honest read-only annotation. Marked non-destructive: every write
|
|
33
|
+
produces an UNSENT draft, and this CLI still cannot send mail.
|
|
34
|
+
- `login` — sign in or refresh. The elevated (M365) token lapses roughly
|
|
35
|
+
hourly and only a browser can recapture it, so this saves a terminal
|
|
36
|
+
round-trip mid-session.
|
|
37
|
+
|
|
38
|
+
Both run tools accept `outputPath` / `outputDir`, mirroring the CLI flags, so
|
|
39
|
+
a multi-MB PDF lands on disk instead of flooding the model's context.
|
|
40
|
+
|
|
41
|
+
**Raise your MCP client's tool timeout to ~5 minutes** (`MCP_TOOL_TIMEOUT=300000`
|
|
42
|
+
or equivalent). Measured against a real tenant: a browser sign-in takes
|
|
43
|
+
**37–64 s even with no MFA prompt**, while the MCP default request timeout is
|
|
44
|
+
**60 s** — so `login` times out intermittently at the default. The server keeps
|
|
45
|
+
running through a client-side timeout, so the sign-in has usually completed
|
|
46
|
+
anyway; re-run your original command before calling `login` again. Sign in
|
|
47
|
+
from a terminal the first time (`ask-marcel-office login`), where an MFA prompt adds
|
|
48
|
+
minutes on top.
|
|
49
|
+
|
|
50
|
+
Read/write sets are derived from the registry's `mutates` flag, so a new
|
|
51
|
+
command lands on the correct tool with no code change here.
|
|
52
|
+
|
|
53
|
+
- **Files in a partner tenant you are a guest in are now readable.** Previously
|
|
54
|
+
every such read died at `401 invalidAudienceUri: Invalid audience Uri
|
|
55
|
+
'00000003-0000-0ff1-ce00-000000000000'` (SharePoint Online's app id): your
|
|
56
|
+
home-tenant Graph cannot mint a SharePoint token for a foreign tenant, so no
|
|
57
|
+
home-tier token could reach the file — which is exactly the case when a partner
|
|
58
|
+
sends you a "Copy link" URL.
|
|
59
|
+
|
|
60
|
+
**`resolve-drive-share-link` crosses the boundary by itself.** It tries your
|
|
61
|
+
home token and, only on that specific error, identifies the owning tenant from
|
|
62
|
+
the URL host (`contoso.sharepoint.com` → `contoso.onmicrosoft.com` → the
|
|
63
|
+
tenant's public OIDC discovery document) and retries with a guest token minted
|
|
64
|
+
by redeeming your existing refresh token against that tenant's authority. No
|
|
65
|
+
new sign-in, no browser, no Azure app. It returns the tenant as **`tenantId`**;
|
|
66
|
+
the field's PRESENCE is the signal — absent means the file is in your own
|
|
67
|
+
tenant and nothing changes.
|
|
68
|
+
|
|
69
|
+
**`--tenant-id` carries it to the rest of the family.** `driveId` and `itemId`
|
|
70
|
+
carry no tenant, so the commands that consume them cannot recover on their own;
|
|
71
|
+
pass the `tenantId` that `resolve-drive-share-link` returned and they sign with
|
|
72
|
+
the guest token instead. Available on `get-drive-item`,
|
|
73
|
+
`download-drive-item-content`, `download-drive-item-as-markdown`,
|
|
74
|
+
`download-drive-item-as-pdf`, `extract-drive-item-images`,
|
|
75
|
+
`convert-drive-item-zip`, `get-drive-item-list-item`, `list-folder-files`,
|
|
76
|
+
`list-drive-item-versions`, `list-drive-item-permissions`, and
|
|
77
|
+
`list-drive-item-thumbnails`. Optional everywhere; omit it for your own tenant.
|
|
78
|
+
|
|
79
|
+
Boundaries worth knowing: a link you simply may not read still fails with
|
|
80
|
+
`accessDenied` (a guest token would not help, so one is never requested); a
|
|
81
|
+
tenant that has not consented to this client, or where you are not actually a
|
|
82
|
+
guest, fails with a message naming that tenant; a SharePoint host whose sign-in
|
|
83
|
+
domain differs from its name cannot be resolved and says so (`--tenant-id`
|
|
84
|
+
passed by hand still works there); and `1drv.ms` belongs to no tenant at all,
|
|
85
|
+
so it is unaffected. Elevated commands do not take the flag — the elevated
|
|
86
|
+
token is a home-tenant identity, so "elevated in a partner tenant" does not
|
|
87
|
+
exist. Verified end to end against a real partner tenant.
|
|
88
|
+
|
|
89
|
+
- **`get-user`** looks up a directory user by id, UPN/email, or name — one
|
|
90
|
+
command, two routes. An **Azure AD id, UPN, or email** returns that user's FULL
|
|
91
|
+
profile via `GET /users/{id}` on the elevated M365 token (`User.Read.All`, so
|
|
92
|
+
`jobTitle` / `department` / `officeLocation` / phones are populated); it honours
|
|
93
|
+
`--select` / `--expand` and fail-fasts with `secondary_token_unavailable` when
|
|
94
|
+
the elevated token is cold (run `login --force`). An email that is the user's
|
|
95
|
+
`mail` but not their sign-in UPN — every **guest / B2B** user, whose UPN is the
|
|
96
|
+
`alias_homeorg#EXT#@tenant` form — still resolves: when the direct
|
|
97
|
+
`GET /users/{id}` 404s, the command falls back to
|
|
98
|
+
`GET /users?$filter=mail eq '<email>'` and returns the single match. A bare **name** instead
|
|
99
|
+
searches the signed-in user's relevant-people graph
|
|
100
|
+
(`GET /me/people?$search="name"`) on the basic token — so it works even when the
|
|
101
|
+
elevated token is cold — and returns candidate matches
|
|
102
|
+
(`{ id, displayName, mail, jobTitle, department }`) so the caller can
|
|
103
|
+
disambiguate and re-query by the chosen `id` for the full card. Name search
|
|
104
|
+
covers your people graph, not the whole tenant; `microsoft-search-query` remains
|
|
105
|
+
the broad tenant-wide person search. (181st command.)
|
|
106
|
+
|
|
107
|
+
### Changed
|
|
108
|
+
|
|
109
|
+
- **Command execution is now shared by both front ends.** The per-command
|
|
110
|
+
handler (alias normalization, local-filesystem routing, error-source
|
|
111
|
+
classification, `--output-path` / `--output-dir` persistence) moved out of
|
|
112
|
+
`cli.ts` into `composition/run-registry-command.ts`, which the CLI and the MCP
|
|
113
|
+
gateway both call. Behaviour is unchanged — the existing CLI suite passes
|
|
114
|
+
untouched — but a future fix now reaches both surfaces instead of one.
|
|
115
|
+
- **The output envelope logic is now pure and reusable.** `presenter/output.ts`
|
|
116
|
+
wrote directly to stdout, which an MCP stdio server cannot do (stdout is its
|
|
117
|
+
JSON-RPC channel). The envelope layer moved to `presenter/render-to-string.ts`
|
|
118
|
+
(`renderToString` / `renderErrorToString`); `output.ts` is a thin stdout shim
|
|
119
|
+
over it. Output is byte-identical. MCP callers therefore get the same
|
|
120
|
+
`hint:` / `source:` remedies the terminal does.
|
|
121
|
+
|
|
122
|
+
- **`scopes-check` is now the single detailed token-status view.** Per token
|
|
123
|
+
(basic / elevated / chatsvcagg / ic3) it reports the `available` flag,
|
|
124
|
+
seconds-to-expiry, `refresh` route (`automatic` = self-heals from the shared
|
|
125
|
+
refresh token; `interactive` = the elevated token, needs a browser login), and
|
|
126
|
+
— new — that token's OWN granted scopes, decoded from its `scp` claim. The four
|
|
127
|
+
tokens carry distinct scope sets (basic ~31 Graph scopes, elevated ~20,
|
|
128
|
+
chatsvcagg `user_impersonation`, ic3 `Teams.AccessAsUser.All`), so an agent can
|
|
129
|
+
intersect each tier against a command's `scopesRequired`. Additive and
|
|
130
|
+
non-breaking: the flat top-level `scopes`/`audience`/`expiresAt`/`expiresInSeconds`
|
|
131
|
+
(the basic token) are unchanged. A `hint` field names a forced re-login as the
|
|
132
|
+
single refresh action.
|
|
133
|
+
- **`login` slimmed to an auth confirmation.** It now prints
|
|
134
|
+
`{ status: "authenticated", available: [...], hint }` — which token tiers are
|
|
135
|
+
available, plus a pointer to `scopes-check` (per-token detail) and `login --force`
|
|
136
|
+
(refresh) — instead of the four-token detail block from 2.1.0, and the confusing
|
|
137
|
+
refresh-mechanism hint is gone. The detailed per-token status now lives only in
|
|
138
|
+
`scopes-check`. (Dropping login's `tokens` block is the one breaking-ish change;
|
|
139
|
+
it shipped a single release earlier and the same detail is fully available via
|
|
140
|
+
`scopes-check`.)
|
|
141
|
+
- **The mail read commands now include `conversationId` in their default
|
|
142
|
+
projection.** `list-mail-messages`, `search-mail-messages`, and
|
|
143
|
+
`get-mail-message` add `conversationId` to their slim default `--select`, so a
|
|
144
|
+
caller can group results into a thread — or hand the id straight to
|
|
145
|
+
`list-conversation-messages` — without a second round-trip. The three
|
|
146
|
+
previously-duplicated default-select strings are now one shared constant
|
|
147
|
+
(`mail-message-select.ts`) so they cannot drift apart again. Additive
|
|
148
|
+
(~76 bytes/message); a user-supplied `--select` still overrides entirely.
|
|
149
|
+
- **`resolve-drive-share-link` now resolves a sharing URL to the driveItem in one
|
|
150
|
+
call.** It previously only encoded the URL into the `u!` share token (offline,
|
|
151
|
+
no Graph call), forcing a second `/shares/{token}/driveItem` fetch plus
|
|
152
|
+
hand-parsing `parentReference.driveId`. It now encodes AND fetches, returning
|
|
153
|
+
`{ driveId, itemId, name, webUrl, size, lastModifiedDateTime, shareToken }` — the
|
|
154
|
+
two ids every `*-drive-item` command needs, in one shot (basic token,
|
|
155
|
+
`Files.Read.All`). Shape + behavior change: it now makes a Graph call and no
|
|
156
|
+
longer returns `graphPath`/`originalUrl`; a cross-tenant or no-access link
|
|
157
|
+
surfaces the Graph `accessDenied` / `itemNotFound` instead of a share token that
|
|
158
|
+
would only fail on the follow-up call.
|
|
159
|
+
|
|
160
|
+
### Fixed
|
|
161
|
+
|
|
162
|
+
- **`login` no longer sends you round a loop it cannot break.** A command needing
|
|
163
|
+
the elevated (M365) token failed with "run `ask-marcel-office login`"; `login`
|
|
164
|
+
answered `authenticated` and changed nothing; the command failed identically.
|
|
165
|
+
Forever. The elevated token carries no refresh token of its own, so a plain
|
|
166
|
+
`login` that found a valid cached basic token returned on the cache rung
|
|
167
|
+
without ever re-capturing it — `--force` was the only escape, and the error
|
|
168
|
+
message never said so.
|
|
169
|
+
|
|
170
|
+
`login` now re-captures the elevated token whenever it is missing, and does
|
|
171
|
+
nothing when it is already present (no gratuitous browser). The fail-fast
|
|
172
|
+
message now names the right remedy per tier and explains why: `--force` for
|
|
173
|
+
elevated, plain `login` for the chatsvcagg / ic3 substrate tokens, which
|
|
174
|
+
self-heal from the shared refresh token and never needed a browser at all.
|
|
175
|
+
`scopes-check`'s hint and the README carried the same false "only way" claim and
|
|
176
|
+
are corrected too. `--force` still works and still re-captures every tier.
|
|
177
|
+
|
|
178
|
+
- **The library's documented `AuthManager` example did not compile.** The README's
|
|
179
|
+
"bring your own token" snippet passed `{getAccessToken, logout}` to
|
|
180
|
+
`createGraphClient` and called `AuthManager` "two async methods"; it has ten.
|
|
181
|
+
The snippet now compiles, and declines the tiers a custom token source cannot
|
|
182
|
+
mint rather than omitting them.
|
|
183
|
+
|
|
184
|
+
- **Guest / external-user (B2B) lookups by UPN now resolve.** A guest UPN is
|
|
185
|
+
`alice_contoso.com#EXT#@fabrikam.onmicrosoft.com`, and the `#` is the URL
|
|
186
|
+
fragment delimiter — the seven commands that put a caller-supplied `userId`
|
|
187
|
+
in a `/users/{id}` path (`get-user-manager`, `get-shared-mailbox-message`,
|
|
188
|
+
`list-shared-mailbox-messages`, `list-shared-mailbox-folder-messages`,
|
|
189
|
+
`list-shared-calendar-events`, `list-user-direct-reports`,
|
|
190
|
+
`list-shared-calendar-view`) interpolated it raw, so `fetch` dropped everything
|
|
191
|
+
from the `#` onward and queried the wrong user. The `userId` segment is now
|
|
192
|
+
percent-encoded (`#`→`%23`, `@`→`%40`; GUIDs unchanged), which Microsoft Graph
|
|
193
|
+
requires for B2B UPNs and which is verified live against the directory. `get-user`
|
|
194
|
+
(new this release) already encodes its path.
|
|
195
|
+
- **Cached elevated / substrate tokens now survive a basic-token refresh.** The
|
|
196
|
+
silent basic-token refresh (triggered whenever the Teams token nears expiry)
|
|
197
|
+
rebuilt the cache from only the three basic fields, wiping the cached `elevated`
|
|
198
|
+
/ `chatsvcagg` / `ic3` tokens on every renewal — so `scopes-check` and `login`
|
|
199
|
+
then reported them unavailable with empty scopes, and the elevated token (which
|
|
200
|
+
carries no refresh token of its own) needed a forced re-login to recover. The
|
|
201
|
+
refresh now merges, preserving all four tokens until they each expire.
|
|
202
|
+
- **`scopes-check` explains an unavailable token.** Every tier block whose
|
|
203
|
+
`available` is `false` now carries a `reason` — a one-line note on why it is
|
|
204
|
+
absent and how to restore it (`login --force`; the substrate tiers also self-heal
|
|
205
|
+
on next use) — so an empty `scopes: []` on a missing token is not misread as "no
|
|
206
|
+
scopes". Additive and non-breaking; omitted when the token is available.
|
|
207
|
+
|
|
208
|
+
## 2.1.0
|
|
209
|
+
|
|
210
|
+
### Added
|
|
211
|
+
|
|
212
|
+
- **`create-forward-draft`** creates an UNSENT forward draft of an existing
|
|
213
|
+
message. `POST /me/messages/{id}/createForward` mints the draft (`FW:` subject,
|
|
214
|
+
quoted original) with your comment placed above the quote and the recipients
|
|
215
|
+
set, in one call (`Mail.ReadWrite`, already on the basic token).
|
|
216
|
+
`--to-recipients` is required (a forward with no recipient is not actionable);
|
|
217
|
+
`--cc-recipients` and a `--subject` override are optional. Like the other
|
|
218
|
+
mail-draft commands, it produces an UNSENT draft only; the CLI can never send.
|
|
219
|
+
This is the fourth and last write command, closing the "forward to the right
|
|
220
|
+
owner" gap that `create-reply-draft` (in-thread) could not.
|
|
221
|
+
- **`convert-local-file --include-images`** (a `.zip` only) also extracts every
|
|
222
|
+
archive entry's embedded images (docx/xlsx/pptx OOXML media parts, pdf page
|
|
223
|
+
images), so a screenshot pasted inside a zipped document is reachable in one
|
|
224
|
+
call.
|
|
225
|
+
- **`login` now reports all four cached tokens** (basic, elevated/M365, and the
|
|
226
|
+
two Teams-chat substrate tokens chatsvcagg / ic3) with each one's time-left and
|
|
227
|
+
refresh route, so running `login` while already signed in shows the full token
|
|
228
|
+
picture instead of a bare `{ status: "authenticated" }`. Each token is
|
|
229
|
+
`{ available, expiresInSeconds?, refresh: "automatic" | "interactive", reason? }`:
|
|
230
|
+
basic/chatsvcagg/ic3 refresh automatically from the cached refresh token; the
|
|
231
|
+
elevated token is `interactive` (re-captured only on a browser login).
|
|
232
|
+
- **`login --force`** ignores the cache and re-captures every token via the
|
|
233
|
+
browser in one pass — the only way to refresh the elevated token while the
|
|
234
|
+
basic token is still valid. The persistent browser profile is reused, so you
|
|
235
|
+
are usually not re-prompted for credentials.
|
|
236
|
+
- **`scopes-check` now reports the elevated (M365ChatClient) token** plus the two
|
|
237
|
+
Teams-chat substrate tokens (`chatsvcagg` / `ic3`), each in an
|
|
238
|
+
`{ available, expiresInSeconds? }` block, so a fresh process can pre-flight the
|
|
239
|
+
historical-version download / convert commands instead of discovering a 403
|
|
240
|
+
mid-run. The two substrate blocks are additive; the existing top-level fields
|
|
241
|
+
are unchanged.
|
|
242
|
+
- **Machine-readable `errorCode`s on more error paths** — the elevated /
|
|
243
|
+
substrate fail-fast (`secondary_token_unavailable`) and the client-side
|
|
244
|
+
unsupported-input rejections (`unsupported_image` / `unsupported_format` /
|
|
245
|
+
`unsupported_legacy_office` / `unsupported_document`), so an agent branches on
|
|
246
|
+
a stable code instead of substring-matching the message.
|
|
247
|
+
|
|
248
|
+
### Fixed
|
|
249
|
+
|
|
250
|
+
- **`create-forward-draft` and `create-reply-draft` no longer drop the forwarded
|
|
251
|
+
/ quoted body.** They set the comment via Graph's `comment` parameter on the
|
|
252
|
+
`createForward` / `createReplyAll` POST, which places it above the preserved
|
|
253
|
+
quote. The previous implementation PATCHed `body` with only the comment, which
|
|
254
|
+
**replaced** the whole draft body and dropped the entire forwarded original (a
|
|
255
|
+
forward went out with just the comment, no message). Caught by a live smoke
|
|
256
|
+
test; the fix is live-verified.
|
|
257
|
+
|
|
258
|
+
### Changed
|
|
259
|
+
|
|
260
|
+
- The `parseRecipients` helper shared by the mail-draft write commands moved to
|
|
261
|
+
`parse-recipients.ts` (one definition, three call sites), with no behaviour
|
|
262
|
+
change.
|
|
263
|
+
- `scopes-check` `responseShape` corrected: `elevated.expiresInSeconds` is
|
|
264
|
+
omitted (the key is absent) when no elevated token is cached, not `null`.
|
|
265
|
+
|
|
266
|
+
### Removed
|
|
267
|
+
|
|
268
|
+
- The `--body-content-type` flag on **`create-forward-draft` and
|
|
269
|
+
`create-reply-draft`** is removed. It never affected the quoted body (Graph
|
|
270
|
+
embeds the comment / reply as text above the quote), so it was a no-op on those
|
|
271
|
+
two commands. It remains on `create-mail-draft` and `update-mail-draft`, which
|
|
272
|
+
set the body directly.
|
|
273
|
+
|
|
5
274
|
## 2.0.0
|
|
6
275
|
|
|
7
276
|
Breaking auth simplification, a repo-wide privacy scrub (including a rewrite of
|