ask-marcel-office-cli 2.3.0 → 2.4.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 +121 -0
- package/README.md +45 -26
- package/dist/cli.js +2819 -2207
- package/dist/commands.json +186 -12
- package/dist/composition/run-registry-command.d.ts +4 -4
- package/dist/domain/utilities/base64.d.ts +11 -0
- package/dist/index.js +1983 -1536
- package/dist/infra/auth.d.ts +30 -4
- package/dist/infra/browser-auth.d.ts +21 -1
- package/dist/presenter/output-text.d.ts +1 -1
- package/dist/presenter/output.d.ts +2 -2
- package/dist/presenter/render-to-string.d.ts +14 -6
- package/dist/use-cases/commands/docx-metadata.d.ts +32 -2
- package/dist/use-cases/commands/include-hidden-folders.d.ts +2 -0
- package/dist/use-cases/commands/list-shared-mailbox-child-folders.d.ts +4 -0
- package/dist/use-cases/commands/list-shared-mailbox-folders.d.ts +4 -0
- package/dist/use-cases/commands/login-status.d.ts +2 -0
- package/dist/use-cases/commands/login.d.ts +18 -8
- package/dist/use-cases/commands/next-page.d.ts +1 -0
- package/dist/use-cases/commands/ooxml-xml-walker.d.ts +31 -6
- package/dist/use-cases/commands/pptx-comments.d.ts +8 -5
- package/dist/use-cases/commands/token-tier-capability.d.ts +5 -0
- package/docs/COMMANDS.md +8 -6
- package/docs/USAGE.md +2 -2
- package/package.json +25 -9
package/dist/commands.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "ask-marcel-office-cli",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"generatedAt": "2026-
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"generatedAt": "2026-09-02T16:06:32.040Z",
|
|
5
5
|
"commands": [
|
|
6
6
|
{
|
|
7
7
|
"name": "convert-calendar-event-attachment-to-markdown",
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
"name": "keep-quoted",
|
|
363
363
|
"key": "keepQuoted",
|
|
364
364
|
"required": false,
|
|
365
|
-
"description": "Quoted reply chains and forwarded-message blocks are stripped by default (they duplicate content already present in earlier messages and inflate the context budget). The stripped tail is replaced with a single visible marker naming this flag, so nothing is removed silently. Pass `--keep-quoted true` to preserve the full body. Only well-known structural markers are cut: in HTML bodies Outlook `divRplyFwdMsg` / `appendonsend` / `stopSpelling`, Gmail `gmail_quote`, the Outlook reply border separator (`#E1E1E1` on desktop, `#B5C4DF` on Mac/mobile), and a bold `From:`/`Sent:` header-label pair (localized variants recognized: 发件人/发送时间, De/Envoyé, Von/Gesendet, Da/Inviato, De/Enviado, Van/Verzonden, 差出人/送信日時, 보낸 사람/보낸 날짜 — a lone bolded \"From:\" without its companion label never cuts); in plain-text bodies the `Original Message` banner, the `On … wrote:` attribution line, leading `>` quote lines, and the same localized `From:`+`Sent:` line pairs.",
|
|
365
|
+
"description": "Quoted reply chains and forwarded-message blocks are stripped by default (they duplicate content already present in earlier messages and inflate the context budget). The stripped tail is replaced with a single visible marker naming this flag, so nothing is removed silently. Pass `--keep-quoted true` to preserve the full body. Only well-known structural markers are cut: in HTML bodies Outlook `divRplyFwdMsg` / `appendonsend` / `stopSpelling`, Gmail `gmail_quote`, the Outlook reply border separator (`#E1E1E1` on desktop, `#B5C4DF` on Mac/mobile, in either the hex or the `border-color:rgb(…)` longhand new Outlook writes), the new-Outlook `mail-editor-reference-message-container` but only when a header block opens inside it (the same id also wraps a body that quotes nothing, so the id alone never cuts), and a bold `From:`/`Sent:` header-label pair (localized variants recognized: 发件人/发送时间, De/Envoyé, Von/Gesendet, Da/Inviato, De/Enviado, Van/Verzonden, 差出人/送信日時, 보낸 사람/보낸 날짜 — a lone bolded \"From:\" without its companion label never cuts); in plain-text bodies the `Original Message` banner, the `On … wrote:` attribution line, leading `>` quote lines, and the same localized `From:`+`Sent:` line pairs. When a chain is stripped the `note` reports what share of the body’s readable text went with it (`removed 62% of the body text`), so a caller can tell a normal strip from one that swallowed the message without refetching to compare.",
|
|
366
366
|
"argumentHint": {
|
|
367
367
|
"kind": "magicValue",
|
|
368
368
|
"values": [
|
|
@@ -373,7 +373,7 @@
|
|
|
373
373
|
}
|
|
374
374
|
],
|
|
375
375
|
"example": "ask-marcel-office convert-mail-to-markdown --message-id 'AAMkAD...'",
|
|
376
|
-
"responseShape": "`{ contentType: \"text/markdown\", size, text, note? }` — headers + turndown-rendered body + (when present) a file-attachments list. The optional `note` carries a partial-success hint when the attachments-metadata fetch fails after the body succeeded, and/or a flag that a quoted reply chain was stripped (
|
|
376
|
+
"responseShape": "`{ contentType: \"text/markdown\", size, text, note? }` — headers + turndown-rendered body + (when present) a file-attachments list. The optional `note` carries a partial-success hint when the attachments-metadata fetch fails after the body succeeded, and/or a flag that a quoted reply chain was stripped, carrying the share of the body’s readable text that went with it (`quoted reply chain stripped (removed 62% of the body text) — pass --keep-quoted true to include it`). A share near 100% on a message that should have had a real reply above the quote is the signature of a mis-detected boundary: refetch with `--keep-quoted true` to confirm.",
|
|
377
377
|
"producesBytes": true
|
|
378
378
|
},
|
|
379
379
|
{
|
|
@@ -607,7 +607,7 @@
|
|
|
607
607
|
"name": "include-metadata",
|
|
608
608
|
"key": "includeMetadata",
|
|
609
609
|
"required": false,
|
|
610
|
-
"description": "Pass `--include-metadata true` to surface the side-channel content the rendered body hides. For docx (`## DOCX metadata`): core/app/custom document properties, people registry, external hyperlinks, comments, tracked changes (
|
|
610
|
+
"description": "Pass `--include-metadata true` to surface the side-channel content the rendered body hides. For docx (`## DOCX metadata`): core/app/custom document properties, people registry, external hyperlinks, comments, tracked changes (a deletion sitting next to an insertion by the same author is reported once as a `replacement` carrying `before` + `after`; the halves that pair with nothing stay under `insertions` / `deletions`; `moves` joins the two ends of a moved span by its range name; `formatChanges` names which run or paragraph properties a reviewer altered. Table-structure revisions — cell insert / delete / merge, numbering and section properties — are still unreported), hidden-formatted text (w:vanish), field instructions (MERGEFIELD / HYPERLINK / DOCVARIABLE), bookmarks. For xlsx (`## Workbook metadata`): core/app/custom properties, external relationships, defined names, hidden / very-hidden sheets, legacy cell comments, threaded comments, persons. For pptx (`## PPTX metadata`): properties, external relationships, slide tags, comment authors + comments (legacy + modern), and per-slide title / speaker notes / hidden flag — returned as a standalone document since pptx has no convertible body (use `download-drive-item-as-pdf` for slide visuals). For OpenDocument (`.odt`/`.ods`/`.odp`, `## OpenDocument metadata`): Dublin Core + ODF properties, keywords, user-defined custom fields — appended after the converted body. Each OOXML family also covers its macro-enabled (`.docm` / `.xlsm` / `.pptm`) and template (`.dotx` / `.xltx` / `.potx`, etc.) variants, with a `### Macros (VBA)` section flagging an embedded `vbaProject.bin`. No-op on other sources.",
|
|
611
611
|
"argumentHint": {
|
|
612
612
|
"kind": "magicValue",
|
|
613
613
|
"values": [
|
|
@@ -2466,7 +2466,7 @@
|
|
|
2466
2466
|
},
|
|
2467
2467
|
{
|
|
2468
2468
|
"name": "get-user",
|
|
2469
|
-
"summary": "Look up a directory user. Pass an Azure AD id, UPN, or email as --user-id and get that user's FULL profile (displayName, mail, jobTitle, department, officeLocation, phones) via GET /users/{id} on the elevated M365 token
|
|
2469
|
+
"summary": "Look up a directory user. Pass an Azure AD id, UPN, or email as --user-id and get that user's FULL profile (displayName, mail, jobTitle, department, officeLocation, phones) via GET /users/{id} on the basic token — no elevated login needed. On a tenant that restricts basic directory reads the id path falls back to the elevated M365 token; re-capture that with `ask-marcel-office login` (preflight tiers with `ask-marcel-office scopes-check`, no Graph call). An email resolves even when it is the user's `mail` rather than their sign-in UPN: guest / B2B users carry a `#EXT#` UPN whose local part is NOT their email address, so when the direct lookup 404s the command falls back to `GET /users?$filter=mail eq '<email>'` and returns the single match. Only THIS tenant's directory is queried: a person's home-tenant object id (e.g. a cross-tenant Teams `8:orgid:<home-id>` participant, whose id lives in their own tenant) and any email that is not their `mail`/UPN here are unresolvable by design — reach an external person via their LOCAL guest projection (by name, or by their real `mail`), never by their home id. Pass a NAME instead and it searches your relevant-people graph (GET /me/people) and returns candidate matches (id, displayName, mail, jobTitle, department) so you can pick the right person and re-query. Re-query by the candidate's `id` when it is a directory GUID; an EXTERNAL contact's candidate carries a base64-ish People-API id instead, which nothing can resolve — re-query those by the candidate's `mail` (the CLI rejects an opaque contact id with that remedy rather than returning empty matches). Name search covers colleagues in your people graph, not the whole tenant directory; use `microsoft-search-query` for a broader tenant-wide person search. Without `--select`, the profile ships a default projection of id, displayName, userPrincipalName, mail, jobTitle, department, officeLocation, businessPhones, mobilePhone.",
|
|
2470
2470
|
"category": "user",
|
|
2471
2471
|
"graphMethod": "GET",
|
|
2472
2472
|
"graphPathTemplate": "/users/{user-id} (id / UPN; an email that misses falls back to /users?$filter=mail eq) OR /me/people?$search=\"{user-id}\" (a bare name)",
|
|
@@ -2476,7 +2476,7 @@
|
|
|
2476
2476
|
"name": "user-id",
|
|
2477
2477
|
"key": "userId",
|
|
2478
2478
|
"required": true,
|
|
2479
|
-
"description": "Azure AD user ID, UPN, or email (returns the full profile via the elevated token), OR a display name (returns relevant-people candidates on the basic token). Discover ids via `list-relevant-people` or `microsoft-search-query`."
|
|
2479
|
+
"description": "Azure AD user ID, UPN, or email (returns the full profile via the basic token, falling back to the elevated token only on tenants that restrict basic directory reads), OR a display name (returns relevant-people candidates on the basic token). Discover ids via `list-relevant-people` or `microsoft-search-query`."
|
|
2480
2480
|
},
|
|
2481
2481
|
{
|
|
2482
2482
|
"name": "select",
|
|
@@ -2492,7 +2492,7 @@
|
|
|
2492
2492
|
}
|
|
2493
2493
|
],
|
|
2494
2494
|
"example": "ask-marcel-office get-user --user-id 'alice@contoso.com' --select 'id,displayName,mail,jobTitle,department'",
|
|
2495
|
-
"responseShape": "For an id / UPN / email: a single Microsoft Graph `user` resource projected to the default `$select` (id, displayName, userPrincipalName, mail, jobTitle, department, officeLocation, businessPhones, mobilePhone) unless `--select` overrides it; honours `--expand`. An email is tried against the sign-in UPN first, then falls back to the `mail` attribute (`$filter=mail eq`) so guest/B2B users resolve; an email that matches nobody returns the direct-path 404. For a name: `{ query, matches: [{ id, displayName, mail, jobTitle, department }] }` from the People API (empty `matches` when nobody in your relevant-people graph matches; re-query by a chosen GUID `id`, or by `mail` when the candidate is an external contact with a base64-ish People-API id — passing that id back is rejected with the same remedy). The id path
|
|
2495
|
+
"responseShape": "For an id / UPN / email: a single Microsoft Graph `user` resource projected to the default `$select` (id, displayName, userPrincipalName, mail, jobTitle, department, officeLocation, businessPhones, mobilePhone) unless `--select` overrides it; honours `--expand`. An email is tried against the sign-in UPN first, then falls back to the `mail` attribute (`$filter=mail eq`) so guest/B2B users resolve; an email that matches nobody returns the direct-path 404. For a name: `{ query, matches: [{ id, displayName, mail, jobTitle, department }] }` from the People API (empty `matches` when nobody in your relevant-people graph matches; re-query by a chosen GUID `id`, or by `mail` when the candidate is an external contact with a base64-ish People-API id — passing that id back is rejected with the same remedy). The id path resolves on the basic token; on a tenant that restricts basic directory reads it falls back to the elevated M365 token, which fail-fasts with `secondary_token_unavailable` when that token is cold (preflight with `ask-marcel-office scopes-check`, re-capture with `ask-marcel-office login`).",
|
|
2496
2496
|
"needsElevatedToken": true
|
|
2497
2497
|
},
|
|
2498
2498
|
{
|
|
@@ -3166,7 +3166,7 @@
|
|
|
3166
3166
|
}
|
|
3167
3167
|
],
|
|
3168
3168
|
"example": "ask-marcel-office list-conversation-messages --conversation-id 'AAQkAD...=' --top 5 --select id,subject,receivedDateTime",
|
|
3169
|
-
"responseShape": "collection of Microsoft Graph `message` resources under `value[]` (unordered)",
|
|
3169
|
+
"responseShape": "collection of Microsoft Graph `message` resources under `value[]` (unordered). This command ships no default `$select`, so an unflagged call returns the full `message` resource. `internetMessageHeaders` IS honored here (verified live 2026-08-29): naming it in `--select` returns the raw RFC 5322 headers for every message in the thread in ONE call, so a per-message `get-mail-message --select internetMessageHeaders` follow-up is unnecessary. Use `get-mail-message-mime` when the complete raw source, not just the headers, is needed.",
|
|
3170
3170
|
"pagination": true
|
|
3171
3171
|
},
|
|
3172
3172
|
{
|
|
@@ -4533,6 +4533,19 @@
|
|
|
4533
4533
|
"required": true,
|
|
4534
4534
|
"description": "mailFolder ID. Returned by `ask-marcel-office list-mail-folders`. Well-known names also work, e.g. `inbox`, `sentitems`, `drafts`."
|
|
4535
4535
|
},
|
|
4536
|
+
{
|
|
4537
|
+
"name": "include-hidden-folders",
|
|
4538
|
+
"key": "includeHiddenFolders",
|
|
4539
|
+
"required": false,
|
|
4540
|
+
"description": "Graph HIDES folders flagged as hidden from this listing by default (a mailbox typically hides system folders such as `Social Activity Notifications`, and an Outlook client can hide any folder). Pass `true` to append the plain `includeHiddenFolders=true` query parameter and get them as well; every returned folder then carries `isHidden` to tell the two apart. Omit it for the default visible-only listing.",
|
|
4541
|
+
"argumentHint": {
|
|
4542
|
+
"kind": "magicValue",
|
|
4543
|
+
"values": [
|
|
4544
|
+
"true",
|
|
4545
|
+
"false"
|
|
4546
|
+
]
|
|
4547
|
+
}
|
|
4548
|
+
},
|
|
4536
4549
|
{
|
|
4537
4550
|
"name": "top",
|
|
4538
4551
|
"key": "top",
|
|
@@ -4680,6 +4693,19 @@
|
|
|
4680
4693
|
"graphPathTemplate": "/me/mailFolders",
|
|
4681
4694
|
"graphDocsUrl": "https://learn.microsoft.com/en-us/graph/api/user-list-mailfolders",
|
|
4682
4695
|
"options": [
|
|
4696
|
+
{
|
|
4697
|
+
"name": "include-hidden-folders",
|
|
4698
|
+
"key": "includeHiddenFolders",
|
|
4699
|
+
"required": false,
|
|
4700
|
+
"description": "Graph HIDES folders flagged as hidden from this listing by default (a mailbox typically hides system folders such as `Social Activity Notifications`, and an Outlook client can hide any folder). Pass `true` to append the plain `includeHiddenFolders=true` query parameter and get them as well; every returned folder then carries `isHidden` to tell the two apart. Omit it for the default visible-only listing.",
|
|
4701
|
+
"argumentHint": {
|
|
4702
|
+
"kind": "magicValue",
|
|
4703
|
+
"values": [
|
|
4704
|
+
"true",
|
|
4705
|
+
"false"
|
|
4706
|
+
]
|
|
4707
|
+
}
|
|
4708
|
+
},
|
|
4683
4709
|
{
|
|
4684
4710
|
"name": "top",
|
|
4685
4711
|
"key": "top",
|
|
@@ -5688,6 +5714,80 @@
|
|
|
5688
5714
|
"responseShape": "collection of Microsoft Graph `sharedInsight` resources under `value[]`",
|
|
5689
5715
|
"pagination": true
|
|
5690
5716
|
},
|
|
5717
|
+
{
|
|
5718
|
+
"name": "list-shared-mailbox-child-folders",
|
|
5719
|
+
"summary": "List the subfolders of one mail folder in a shared or delegated mailbox. The `/me` sibling is `list-mail-child-folders`. Walk it from the folder IDs `list-shared-mailbox-folders` returns to reach nested custom folders. 403 if the signed-in user does not have shared access to that mailbox.",
|
|
5720
|
+
"category": "mail",
|
|
5721
|
+
"graphMethod": "GET",
|
|
5722
|
+
"graphPathTemplate": "/users/{user-id}/mailFolders/{mail-folder-id}/childFolders",
|
|
5723
|
+
"graphDocsUrl": "https://learn.microsoft.com/en-us/graph/api/mailfolder-list-childfolders",
|
|
5724
|
+
"options": [
|
|
5725
|
+
{
|
|
5726
|
+
"name": "user-id",
|
|
5727
|
+
"key": "userId",
|
|
5728
|
+
"required": true,
|
|
5729
|
+
"description": "Azure AD user ID or UPN of the shared mailbox or delegated user. The signed-in user must have `Mail.Read.Shared` access (granted by the mailbox owner)."
|
|
5730
|
+
},
|
|
5731
|
+
{
|
|
5732
|
+
"name": "mail-folder-id",
|
|
5733
|
+
"key": "mailFolderId",
|
|
5734
|
+
"required": true,
|
|
5735
|
+
"description": "Mail folder ID or well-known name (`inbox`, `sentitems`, etc.) inside that mailbox. Returned by `ask-marcel-office list-shared-mailbox-folders`."
|
|
5736
|
+
},
|
|
5737
|
+
{
|
|
5738
|
+
"name": "include-hidden-folders",
|
|
5739
|
+
"key": "includeHiddenFolders",
|
|
5740
|
+
"required": false,
|
|
5741
|
+
"description": "Graph HIDES folders flagged as hidden from this listing by default (a mailbox typically hides system folders such as `Social Activity Notifications`, and an Outlook client can hide any folder). Pass `true` to append the plain `includeHiddenFolders=true` query parameter and get them as well; every returned folder then carries `isHidden` to tell the two apart. Omit it for the default visible-only listing.",
|
|
5742
|
+
"argumentHint": {
|
|
5743
|
+
"kind": "magicValue",
|
|
5744
|
+
"values": [
|
|
5745
|
+
"true",
|
|
5746
|
+
"false"
|
|
5747
|
+
]
|
|
5748
|
+
}
|
|
5749
|
+
},
|
|
5750
|
+
{
|
|
5751
|
+
"name": "top",
|
|
5752
|
+
"key": "top",
|
|
5753
|
+
"required": false,
|
|
5754
|
+
"description": "OData $top: maximum number of items to return on this page (positive integer, ≤ 1000). Graph silently caps at 1000 on every collection endpoint, so the CLI rejects larger values with a clear validation error rather than letting the request silently truncate. Combine with `next-page` to paginate beyond the cap."
|
|
5755
|
+
},
|
|
5756
|
+
{
|
|
5757
|
+
"name": "skip",
|
|
5758
|
+
"key": "skip",
|
|
5759
|
+
"required": false,
|
|
5760
|
+
"description": "OData $skip: skip the first N items before returning results (non-negative integer). Useful with $top for offset paging."
|
|
5761
|
+
},
|
|
5762
|
+
{
|
|
5763
|
+
"name": "select",
|
|
5764
|
+
"key": "select",
|
|
5765
|
+
"required": false,
|
|
5766
|
+
"description": "OData $select: comma-separated list of fields to include in each item (e.g. `id,subject,from`). May shrink payloads dramatically — Graph honors $select on most endpoints, but some collections (notably `/me/mailboxSettings`, `/me/outlook/masterCategories`, `/me/mailFolders/inbox/messageRules`) silently ignore it and always return the full resource. Bogus field names are silently dropped by Graph — if a field you asked for is missing from the response, double-check the spelling against the documented `responseShape`."
|
|
5767
|
+
},
|
|
5768
|
+
{
|
|
5769
|
+
"name": "filter",
|
|
5770
|
+
"key": "filter",
|
|
5771
|
+
"required": false,
|
|
5772
|
+
"description": "OData $filter: predicate to narrow results server-side. Quoting rules: string literals MUST use SINGLE quotes (`subject eq 'invoice'`), NOT double quotes — Graph rejects `subject eq \"invoice\"` with `InvalidFilterClause`. To embed a single quote inside a string, double it (`subject eq 'O''Brien'`). Booleans, numbers, and dates are unquoted (`isRead eq false`, `receivedDateTime ge 2026-01-01T00:00:00Z`). Wrap the whole flag value in shell DOUBLE quotes so the inner single quotes survive (`--filter \"subject eq 'invoice'\"`). Same syntax Graph documents per resource type."
|
|
5773
|
+
},
|
|
5774
|
+
{
|
|
5775
|
+
"name": "orderby",
|
|
5776
|
+
"key": "orderby",
|
|
5777
|
+
"required": false,
|
|
5778
|
+
"description": "OData $orderby: sort expression with optional asc/desc (e.g. `receivedDateTime desc`). Some Graph filter combinations are rejected; remove $orderby if InefficientFilter occurs."
|
|
5779
|
+
},
|
|
5780
|
+
{
|
|
5781
|
+
"name": "expand",
|
|
5782
|
+
"key": "expand",
|
|
5783
|
+
"required": false,
|
|
5784
|
+
"description": "OData $expand: navigation properties to include inline (e.g. `attachments`). Increases response size; use sparingly."
|
|
5785
|
+
}
|
|
5786
|
+
],
|
|
5787
|
+
"example": "ask-marcel-office list-shared-mailbox-child-folders --user-id 'shared-mailbox@contoso.com' --mail-folder-id 'inbox'",
|
|
5788
|
+
"responseShape": "collection of Microsoft Graph `mailFolder` resources under `value[]`",
|
|
5789
|
+
"pagination": true
|
|
5790
|
+
},
|
|
5691
5791
|
{
|
|
5692
5792
|
"name": "list-shared-mailbox-folder-messages",
|
|
5693
5793
|
"summary": "List messages in a single folder of a shared / delegated mailbox.",
|
|
@@ -5749,6 +5849,74 @@
|
|
|
5749
5849
|
"responseShape": "collection of Microsoft Graph `message` resources under `value[]`",
|
|
5750
5850
|
"pagination": true
|
|
5751
5851
|
},
|
|
5852
|
+
{
|
|
5853
|
+
"name": "list-shared-mailbox-folders",
|
|
5854
|
+
"summary": "List the top-level mail folders of a shared or delegated mailbox. The `/me` sibling is `list-mail-folders`. Use it to discover the folder IDs that `list-shared-mailbox-folder-messages` needs: without it only the well-known names (`inbox`, `sentitems`, `drafts`, …) are reachable, so custom folders are invisible. 403 if the signed-in user does not have shared access to that mailbox.",
|
|
5855
|
+
"category": "mail",
|
|
5856
|
+
"graphMethod": "GET",
|
|
5857
|
+
"graphPathTemplate": "/users/{user-id}/mailFolders",
|
|
5858
|
+
"graphDocsUrl": "https://learn.microsoft.com/en-us/graph/api/user-list-mailfolders",
|
|
5859
|
+
"options": [
|
|
5860
|
+
{
|
|
5861
|
+
"name": "user-id",
|
|
5862
|
+
"key": "userId",
|
|
5863
|
+
"required": true,
|
|
5864
|
+
"description": "Azure AD user ID or UPN of the shared mailbox or delegated user. The signed-in user must have `Mail.Read.Shared` access (granted by the mailbox owner)."
|
|
5865
|
+
},
|
|
5866
|
+
{
|
|
5867
|
+
"name": "include-hidden-folders",
|
|
5868
|
+
"key": "includeHiddenFolders",
|
|
5869
|
+
"required": false,
|
|
5870
|
+
"description": "Graph HIDES folders flagged as hidden from this listing by default (a mailbox typically hides system folders such as `Social Activity Notifications`, and an Outlook client can hide any folder). Pass `true` to append the plain `includeHiddenFolders=true` query parameter and get them as well; every returned folder then carries `isHidden` to tell the two apart. Omit it for the default visible-only listing.",
|
|
5871
|
+
"argumentHint": {
|
|
5872
|
+
"kind": "magicValue",
|
|
5873
|
+
"values": [
|
|
5874
|
+
"true",
|
|
5875
|
+
"false"
|
|
5876
|
+
]
|
|
5877
|
+
}
|
|
5878
|
+
},
|
|
5879
|
+
{
|
|
5880
|
+
"name": "top",
|
|
5881
|
+
"key": "top",
|
|
5882
|
+
"required": false,
|
|
5883
|
+
"description": "OData $top: maximum number of items to return on this page (positive integer, ≤ 1000). Graph silently caps at 1000 on every collection endpoint, so the CLI rejects larger values with a clear validation error rather than letting the request silently truncate. Combine with `next-page` to paginate beyond the cap."
|
|
5884
|
+
},
|
|
5885
|
+
{
|
|
5886
|
+
"name": "skip",
|
|
5887
|
+
"key": "skip",
|
|
5888
|
+
"required": false,
|
|
5889
|
+
"description": "OData $skip: skip the first N items before returning results (non-negative integer). Useful with $top for offset paging."
|
|
5890
|
+
},
|
|
5891
|
+
{
|
|
5892
|
+
"name": "select",
|
|
5893
|
+
"key": "select",
|
|
5894
|
+
"required": false,
|
|
5895
|
+
"description": "OData $select: comma-separated list of fields to include in each item (e.g. `id,subject,from`). May shrink payloads dramatically — Graph honors $select on most endpoints, but some collections (notably `/me/mailboxSettings`, `/me/outlook/masterCategories`, `/me/mailFolders/inbox/messageRules`) silently ignore it and always return the full resource. Bogus field names are silently dropped by Graph — if a field you asked for is missing from the response, double-check the spelling against the documented `responseShape`."
|
|
5896
|
+
},
|
|
5897
|
+
{
|
|
5898
|
+
"name": "filter",
|
|
5899
|
+
"key": "filter",
|
|
5900
|
+
"required": false,
|
|
5901
|
+
"description": "OData $filter: predicate to narrow results server-side. Quoting rules: string literals MUST use SINGLE quotes (`subject eq 'invoice'`), NOT double quotes — Graph rejects `subject eq \"invoice\"` with `InvalidFilterClause`. To embed a single quote inside a string, double it (`subject eq 'O''Brien'`). Booleans, numbers, and dates are unquoted (`isRead eq false`, `receivedDateTime ge 2026-01-01T00:00:00Z`). Wrap the whole flag value in shell DOUBLE quotes so the inner single quotes survive (`--filter \"subject eq 'invoice'\"`). Same syntax Graph documents per resource type."
|
|
5902
|
+
},
|
|
5903
|
+
{
|
|
5904
|
+
"name": "orderby",
|
|
5905
|
+
"key": "orderby",
|
|
5906
|
+
"required": false,
|
|
5907
|
+
"description": "OData $orderby: sort expression with optional asc/desc (e.g. `receivedDateTime desc`). Some Graph filter combinations are rejected; remove $orderby if InefficientFilter occurs."
|
|
5908
|
+
},
|
|
5909
|
+
{
|
|
5910
|
+
"name": "expand",
|
|
5911
|
+
"key": "expand",
|
|
5912
|
+
"required": false,
|
|
5913
|
+
"description": "OData $expand: navigation properties to include inline (e.g. `attachments`). Increases response size; use sparingly."
|
|
5914
|
+
}
|
|
5915
|
+
],
|
|
5916
|
+
"example": "ask-marcel-office list-shared-mailbox-folders --user-id 'shared-mailbox@contoso.com'",
|
|
5917
|
+
"responseShape": "collection of Microsoft Graph `mailFolder` resources under `value[]`",
|
|
5918
|
+
"pagination": true
|
|
5919
|
+
},
|
|
5752
5920
|
{
|
|
5753
5921
|
"name": "list-shared-mailbox-messages",
|
|
5754
5922
|
"summary": "List messages from a shared or delegated mailbox the signed-in user has read access to. Same shape as `list-mail-messages` but scoped to a specific mailbox owner. 403 if the signed-in user does not have shared access to that mailbox.",
|
|
@@ -6947,7 +7115,7 @@
|
|
|
6947
7115
|
},
|
|
6948
7116
|
{
|
|
6949
7117
|
"name": "next-page",
|
|
6950
|
-
"summary": "Fetch the next page of a paginated Graph response. Pass the cursor the previous command emitted — in text mode the `---` footer prints the whole ready-to-run command (`next: ask-marcel-office next-page --url '<url>'`), so copy the line as-is (the URL is single-quoted because it contains `$`); in JSON mode use the top-level `nextLink` field. Never reach into `data[\"@odata.nextLink\"]`; the CLI strips that and surfaces it as a first-class envelope/footer field. Automatically signs `/me/chats` and `/chats/...` cursors with the M365ChatClient elevated token to match the chat-metadata commands.",
|
|
7118
|
+
"summary": "Fetch the next page of a paginated Graph response. Pass the cursor the previous command emitted — in text mode the `---` footer prints the whole ready-to-run command (`next: ask-marcel-office next-page --url '<url>'`), so copy the line as-is (the URL is single-quoted because it contains `$`); in JSON mode use the top-level `nextLink` field. Never reach into `data[\"@odata.nextLink\"]`; the CLI strips that and surfaces it as a first-class envelope/footer field. Automatically signs `/me/chats` and `/chats/...` cursors with the M365ChatClient elevated token to match the chat-metadata commands. When the cursor came from a partner-tenant (guest) drive listing, pass the same `--tenant-id` you used on the originating command, since the cursor carries no tenant and without it page 2 fails with `invalidAudienceUri`.",
|
|
6951
7119
|
"category": "meta",
|
|
6952
7120
|
"graphMethod": "GET",
|
|
6953
7121
|
"graphPathTemplate": "{url}",
|
|
@@ -6961,6 +7129,12 @@
|
|
|
6961
7129
|
"argumentHint": {
|
|
6962
7130
|
"kind": "graphSubpath"
|
|
6963
7131
|
}
|
|
7132
|
+
},
|
|
7133
|
+
{
|
|
7134
|
+
"name": "tenant-id",
|
|
7135
|
+
"key": "tenantId",
|
|
7136
|
+
"required": false,
|
|
7137
|
+
"description": "Tenant GUID of a PARTNER tenant you are a guest in. Pass it only to continue a partner-tenant drive listing (a folder / `*-drive-item` cursor whose file lives in another tenant), using the same `--tenant-id` you gave the originating command (ultimately from `resolve-drive-share-link`). The page is then signed with a guest token for that tenant; without it a partner-tenant cursor 401s with `invalidAudienceUri` on page 2 even though page 1 succeeded. Omit it for your own tenant and for every `/me/...` and chat cursor (the normal case)."
|
|
6964
7138
|
}
|
|
6965
7139
|
],
|
|
6966
7140
|
"example": "ask-marcel-office next-page --url 'https://graph.microsoft.com/v1.0/me/messages?$skip=10'",
|
|
@@ -7091,14 +7265,14 @@
|
|
|
7091
7265
|
},
|
|
7092
7266
|
{
|
|
7093
7267
|
"name": "scopes-check",
|
|
7094
|
-
"summary": "Decode the cached Teams web client access token and return its scopes, audience, and expiry without making a Graph call. Use this as a self-test before running a command an LLM expects to fail with `accessDenied` — if the required scope isn't in the returned list, the call will reject regardless of tenant config. Each command's `scopesRequired` field in `help-json` lists the scopes that command needs; intersect with the array returned here for a pre-flight check (pipe both through `jq` and diff). The `expiresInSeconds` field lets an LLM decide pre-emptively to `login` again — typically worth doing under ~5 minutes (300 s) so a long-running session doesn't hit the wall mid-command. The `elevated` block reports whether the *separate* M365ChatClient-elevated token (needed by the historical-version download / convert commands) is cached and still usable — so a fresh process can pre-flight `deep-scan`-style workloads instead of discovering a 403 mid-run; `available:false` when it is absent, expired, or within the same 5-minute buffer the download path applies. The `chatsvcagg` and `ic3` blocks report the two Teams-chat substrate tokens (used by `list-teams-chat*` / `find-chats-with-user`) the same way; both self-heal from the shared refresh token, so they are informational rather than a preflight gate. Every tier block also lists that token's OWN granted scopes (decoded from its `scp` claim, distinct per token) and its `refresh` route (`automatic` = self-heals from the shared refresh token; `interactive` = the elevated tier, which carries no refresh token of its own and is re-captured by a browser login); the `hint` field says how to refresh them.",
|
|
7268
|
+
"summary": "Decode the cached Teams web client access token and return its scopes, audience, and expiry without making a Graph call. It never opens a browser: an expired or missing session is reported as such (negative `expiresInSeconds`, or a not-signed-in error), and `login` is the command that refreshes it. Use this as a self-test before running a command an LLM expects to fail with `accessDenied` — if the required scope isn't in the returned list, the call will reject regardless of tenant config. Each command's `scopesRequired` field in `help-json` lists the scopes that command needs; intersect with the array returned here for a pre-flight check (pipe both through `jq` and diff). The `expiresInSeconds` field lets an LLM decide pre-emptively to `login` again — typically worth doing under ~5 minutes (300 s) so a long-running session doesn't hit the wall mid-command. The `elevated` block reports whether the *separate* M365ChatClient-elevated token (needed by the historical-version download / convert commands) is cached and still usable — so a fresh process can pre-flight `deep-scan`-style workloads instead of discovering a 403 mid-run; `available:false` when it is absent, expired, or within the same 5-minute buffer the download path applies. The `chatsvcagg` and `ic3` blocks report the two Teams-chat substrate tokens (used by `list-teams-chat*` / `find-chats-with-user`) the same way; both self-heal from the shared refresh token, so they are informational rather than a preflight gate. Every tier block also lists that token's OWN granted scopes (decoded from its `scp` claim, distinct per token) and its `refresh` route (`automatic` = self-heals from the shared refresh token; `interactive` = the elevated tier, which carries no refresh token of its own and is re-captured by a browser login); the `hint` field says how to refresh them.",
|
|
7095
7269
|
"category": "meta",
|
|
7096
7270
|
"graphMethod": "GET",
|
|
7097
7271
|
"graphPathTemplate": "(meta) cached-token introspection — no Graph endpoint",
|
|
7098
7272
|
"graphDocsUrl": "https://learn.microsoft.com/en-us/graph/permissions-reference",
|
|
7099
7273
|
"options": [],
|
|
7100
7274
|
"example": "ask-marcel-office scopes-check",
|
|
7101
|
-
"responseShape": "`{ scopes: string[], audience: string, expiresAt: string (ISO 8601), expiresInSeconds: number, elevated: TokenTier, chatsvcagg: TokenTier, ic3: TokenTier, hint: string }` where `TokenTier = { available: boolean, expiresInSeconds?: number, scopes: string[], refresh: \"automatic\" | \"interactive\", reason?: string }`. Top-level `scopes`/`audience`/`expiresAt`/`expiresInSeconds` describe the basic Teams token (back-compat). Each tier block also carries that token's OWN `scopes` (decoded from its `scp` claim: elevated ~20 Graph scopes, chatsvcagg `user_impersonation`, ic3 `Teams.AccessAsUser.All`) and its `refresh` route (`automatic` = self-heals from the shared refresh token; `interactive` = the elevated token, re-captured only by a browser login). `available` is `true` only when the token is present and beyond the 5-minute buffer; `expiresInSeconds` is the raw remaining seconds (negative when expired) and is omitted when the token is absent. `reason` is present ONLY when `available` is `false` — a plain-language note on why the tier is missing and how to restore it (so an empty `scopes: []` on an absent token is not mistaken for \"no scopes\"); it is omitted when the token is available. `hint` names `login --force` as the single refresh action."
|
|
7275
|
+
"responseShape": "`{ scopes: string[], audience: string, expiresAt: string (ISO 8601), expiresInSeconds: number, elevated: TokenTier, chatsvcagg: TokenTier, ic3: TokenTier, hint: string }` where `TokenTier = { available: boolean, expiresInSeconds?: number, scopes: string[], refresh: \"automatic\" | \"interactive\", reason?: string }`. Top-level `scopes`/`audience`/`expiresAt`/`expiresInSeconds` describe the basic Teams token (back-compat). Each tier block also carries that token's OWN `scopes` (decoded from its `scp` claim: elevated ~20 Graph scopes, chatsvcagg `user_impersonation`, ic3 `Teams.AccessAsUser.All`) and its `refresh` route (`automatic` = self-heals from the shared refresh token; `interactive` = the elevated token, re-captured only by a browser login). `available` is `true` only when the token is present and beyond the 5-minute buffer; `expiresInSeconds` is the raw remaining seconds (negative when expired) and is omitted when the token is absent. `reason` is present ONLY when `available` is `false` — a plain-language note on why the tier is missing and how to restore it (so an empty `scopes: []` on an absent token is not mistaken for \"no scopes\"); it is omitted when the token is available. `hint` names `login --force` as the single refresh action. EVERY tier — `basic` included — is reported as its own block with the SAME shape, and each block carries a `reads` line: a plain-language description of what that tier lets you read (e.g. `chatsvcagg` -> \"Teams chat message content\"), so a caller never has to know what a tier codename means. The `basic` block mirrors the loose top-level `scopes`/`expiresAt`/`expiresInSeconds` fields, which are still emitted unchanged for back-compat; prefer the block. `basic.available` applies the same 300-second freshness buffer as the other tiers, so `available` means the same thing everywhere."
|
|
7102
7276
|
},
|
|
7103
7277
|
{
|
|
7104
7278
|
"name": "search-all-accessible-sites",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GraphClient } from '../infra/graph-client.js';
|
|
2
2
|
import type { ErrorSource } from '../presenter/error-hints.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { RenderContext, RenderSurface } from '../presenter/render-to-string.js';
|
|
4
4
|
import type { Command } from '../use-cases/commands/command-types.js';
|
|
5
5
|
import type { OutputDirError, OutputPathError } from '../use-cases/commands/output-path.js';
|
|
6
6
|
import type { Result } from '../domain/result.js';
|
|
@@ -8,9 +8,9 @@ import type { FileSystem } from '../use-cases/ports/filesystem.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* The facts the oversized-response banner needs to name a remedy this caller
|
|
10
10
|
* can actually use. Derived from the manifest, so a new command gets an honest
|
|
11
|
-
* banner for free. See `
|
|
11
|
+
* banner for free. See `RenderContext` in the presenter for the why.
|
|
12
12
|
*/
|
|
13
|
-
declare const
|
|
13
|
+
declare const buildRenderContext: (commandName: string, command: Command, surface: RenderSurface, params: Record<string, unknown>) => RenderContext;
|
|
14
14
|
declare const formatOutputPathError: (error: OutputPathError, commandName: string, surface: RenderSurface) => string;
|
|
15
15
|
declare const formatOutputDirError: (error: OutputDirError, commandName: string) => string;
|
|
16
16
|
export type RunRegistryCommandDeps = {
|
|
@@ -40,4 +40,4 @@ export type RunRegistryCommandFailure = {
|
|
|
40
40
|
readonly retryAfterSeconds?: number;
|
|
41
41
|
};
|
|
42
42
|
export declare const runRegistryCommand: (deps: RunRegistryCommandDeps, request: RunRegistryCommandRequest) => Promise<Result<unknown, RunRegistryCommandFailure>>;
|
|
43
|
-
export {
|
|
43
|
+
export { buildRenderContext, formatOutputDirError, formatOutputPathError };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode raw bytes to standard base64.
|
|
3
|
+
*
|
|
4
|
+
* Built byte-by-byte into a Latin-1 binary string so every value stays in
|
|
5
|
+
* `btoa`'s 0x00-0xFF input range — `btoa` throws on any code unit above 0xFF,
|
|
6
|
+
* and the char-at-a-time loop avoids the call-stack blow-up of
|
|
7
|
+
* `String.fromCharCode(...bytes)` on large inputs.
|
|
8
|
+
*
|
|
9
|
+
* Callers that need base64url apply the `+`/`/`/`=` transform themselves.
|
|
10
|
+
*/
|
|
11
|
+
export declare const bytesToBase64: (bytes: Uint8Array) => string;
|