ask-marcel-office-cli 0.5.5 → 1.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.
Files changed (197) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +147 -221
  3. package/dist/cli.js +99348 -14825
  4. package/dist/commands.json +5870 -789
  5. package/dist/composition/build-deps.d.ts +1 -0
  6. package/dist/composition/cli.d.ts +2 -0
  7. package/dist/domain/iso-datetime.d.ts +30 -0
  8. package/dist/index.js +97246 -13703
  9. package/dist/infra/auth.d.ts +61 -3
  10. package/dist/infra/browser-auth.d.ts +163 -1
  11. package/dist/infra/graph-client.d.ts +99 -2
  12. package/dist/infra/mammoth-adapter.d.ts +4 -0
  13. package/dist/infra/network-error.d.ts +9 -0
  14. package/dist/infra/sheetjs-adapter.d.ts +9 -0
  15. package/dist/infra/turndown-adapter.d.ts +4 -0
  16. package/dist/presenter/error-hints.d.ts +41 -0
  17. package/dist/presenter/output-text.d.ts +2 -0
  18. package/dist/presenter/output.d.ts +5 -2
  19. package/dist/use-cases/commands/build-command.d.ts +67 -1
  20. package/dist/use-cases/commands/command-types.d.ts +108 -3
  21. package/dist/use-cases/commands/convert-mail-attachment-to-markdown.d.ts +11 -0
  22. package/dist/use-cases/commands/convert-mail-attachment-to-pdf.d.ts +11 -0
  23. package/dist/use-cases/commands/convert-mail-to-markdown.d.ts +14 -0
  24. package/dist/use-cases/commands/docs-render.d.ts +10 -1
  25. package/dist/use-cases/commands/docs.d.ts +44 -1
  26. package/dist/use-cases/commands/docx-to-markdown.d.ts +11 -0
  27. package/dist/use-cases/commands/{download-drive-item-version-content.d.ts → download-drive-item-as-markdown.d.ts} +0 -1
  28. package/dist/use-cases/commands/download-drive-item-as-pdf.d.ts +11 -0
  29. package/dist/use-cases/commands/download-drive-item-version.d.ts +17 -0
  30. package/dist/use-cases/commands/download-onedrive-file-content.d.ts +2 -2
  31. package/dist/use-cases/commands/embedded-item-to-markdown.d.ts +59 -0
  32. package/dist/use-cases/commands/excel-error.d.ts +6 -0
  33. package/dist/use-cases/commands/extract-sharepoint-links-in-mail.d.ts +26 -0
  34. package/dist/use-cases/commands/fetch-raw-bytes.d.ts +56 -0
  35. package/dist/use-cases/commands/find-chats-with-user.d.ts +10 -0
  36. package/dist/use-cases/commands/format-zod-error.d.ts +14 -0
  37. package/dist/use-cases/commands/get-calendar-event.d.ts +1 -5
  38. package/dist/use-cases/commands/get-calendar-view.d.ts +1 -6
  39. package/dist/use-cases/commands/get-channel-files-folder.d.ts +4 -0
  40. package/dist/use-cases/commands/get-chat.d.ts +4 -0
  41. package/dist/use-cases/commands/get-current-user.d.ts +1 -3
  42. package/dist/use-cases/commands/get-drive-delta.d.ts +1 -6
  43. package/dist/use-cases/commands/get-drive-item-analytics.d.ts +9 -0
  44. package/dist/use-cases/commands/get-drive-item-created-by-user.d.ts +4 -0
  45. package/dist/use-cases/commands/get-drive-item-last-modified-by-user.d.ts +4 -0
  46. package/dist/use-cases/commands/get-drive-item-list-item.d.ts +4 -0
  47. package/dist/use-cases/commands/get-drive-item.d.ts +1 -6
  48. package/dist/use-cases/commands/get-drive-root-delta.d.ts +4 -0
  49. package/dist/use-cases/commands/get-drive-root-item.d.ts +1 -5
  50. package/dist/use-cases/commands/get-drive-special-folder.d.ts +4 -0
  51. package/dist/use-cases/commands/get-excel-range.d.ts +1 -1
  52. package/dist/use-cases/commands/get-excel-table.d.ts +1 -1
  53. package/dist/use-cases/commands/get-excel-used-range.d.ts +15 -0
  54. package/dist/use-cases/commands/get-group-calendar-view.d.ts +4 -0
  55. package/dist/use-cases/commands/get-group.d.ts +4 -0
  56. package/dist/use-cases/commands/get-mail-attachment.d.ts +4 -2
  57. package/dist/use-cases/commands/get-mail-message-mime.d.ts +8 -0
  58. package/dist/use-cases/commands/get-mail-message.d.ts +1 -5
  59. package/dist/use-cases/commands/get-mail-rule.d.ts +9 -0
  60. package/dist/use-cases/commands/get-mailbox-settings.d.ts +1 -1
  61. package/dist/use-cases/commands/get-my-calendar.d.ts +4 -0
  62. package/dist/use-cases/commands/get-my-manager.d.ts +9 -0
  63. package/dist/use-cases/commands/get-onenote-page-as-markdown.d.ts +10 -0
  64. package/dist/use-cases/commands/get-organization.d.ts +4 -0
  65. package/dist/use-cases/commands/get-shared-calendar-view.d.ts +4 -0
  66. package/dist/use-cases/commands/get-shared-mailbox-message.d.ts +4 -0
  67. package/dist/use-cases/commands/get-sharepoint-list-column.d.ts +4 -0
  68. package/dist/use-cases/commands/get-sharepoint-site-drive-by-id.d.ts +1 -6
  69. package/dist/use-cases/commands/get-sharepoint-site-list-item.d.ts +1 -7
  70. package/dist/use-cases/commands/get-sharepoint-site-list.d.ts +1 -6
  71. package/dist/use-cases/commands/get-sharepoint-site-onenote-page-content.d.ts +9 -0
  72. package/dist/use-cases/commands/get-sharepoint-site.d.ts +1 -5
  73. package/dist/use-cases/commands/get-site-analytics.d.ts +8 -0
  74. package/dist/use-cases/commands/get-specific-calendar-event.d.ts +1 -6
  75. package/dist/use-cases/commands/get-specific-calendar-view.d.ts +1 -7
  76. package/dist/use-cases/commands/get-team-channel.d.ts +3 -7
  77. package/dist/use-cases/commands/get-team-primary-channel.d.ts +4 -0
  78. package/dist/use-cases/commands/get-team.d.ts +1 -5
  79. package/dist/use-cases/commands/get-teams-chat-message.d.ts +9 -0
  80. package/dist/use-cases/commands/get-todo-task.d.ts +1 -6
  81. package/dist/use-cases/commands/get-user-manager.d.ts +10 -0
  82. package/dist/use-cases/commands/graph-scopes.d.ts +29 -0
  83. package/dist/use-cases/commands/inline-image-embedder.d.ts +20 -0
  84. package/dist/use-cases/commands/iso-datetime-schema.d.ts +21 -0
  85. package/dist/use-cases/commands/link-shape.d.ts +13 -0
  86. package/dist/use-cases/commands/list-all-onenote-sections.d.ts +1 -3
  87. package/dist/use-cases/commands/list-calendar-event-instances.d.ts +3 -9
  88. package/dist/use-cases/commands/list-calendar-events-delta.d.ts +5 -3
  89. package/dist/use-cases/commands/list-calendar-events.d.ts +1 -3
  90. package/dist/use-cases/commands/list-calendar-group-calendars.d.ts +4 -0
  91. package/dist/use-cases/commands/list-calendar-groups.d.ts +4 -0
  92. package/dist/use-cases/commands/list-calendar-view-delta.d.ts +5 -4
  93. package/dist/use-cases/commands/list-calendars.d.ts +1 -3
  94. package/dist/use-cases/commands/list-chat-members.d.ts +3 -6
  95. package/dist/use-cases/commands/list-chats.d.ts +4 -0
  96. package/dist/use-cases/commands/list-conversation-messages.d.ts +12 -0
  97. package/dist/use-cases/commands/list-drive-item-permissions.d.ts +1 -6
  98. package/dist/use-cases/commands/list-drive-item-thumbnails.d.ts +4 -0
  99. package/dist/use-cases/commands/list-drive-item-versions.d.ts +1 -6
  100. package/dist/use-cases/commands/list-drives.d.ts +1 -3
  101. package/dist/use-cases/commands/list-excel-comments.d.ts +5 -0
  102. package/dist/use-cases/commands/list-excel-defined-names.d.ts +5 -0
  103. package/dist/use-cases/commands/list-excel-table-rows.d.ts +2 -7
  104. package/dist/use-cases/commands/list-excel-tables.d.ts +2 -6
  105. package/dist/use-cases/commands/list-excel-worksheet-charts.d.ts +5 -0
  106. package/dist/use-cases/commands/list-excel-worksheet-pivot-tables.d.ts +5 -0
  107. package/dist/use-cases/commands/list-excel-worksheets.d.ts +2 -6
  108. package/dist/use-cases/commands/list-focused-inbox-overrides.d.ts +4 -0
  109. package/dist/use-cases/commands/list-folder-files.d.ts +1 -6
  110. package/dist/use-cases/commands/list-followed-drive-items.d.ts +4 -0
  111. package/dist/use-cases/commands/list-group-conversations.d.ts +4 -0
  112. package/dist/use-cases/commands/list-group-events.d.ts +4 -0
  113. package/dist/use-cases/commands/list-group-members.d.ts +4 -0
  114. package/dist/use-cases/commands/list-group-owners.d.ts +4 -0
  115. package/dist/use-cases/commands/list-group-threads.d.ts +4 -0
  116. package/dist/use-cases/commands/list-groups.d.ts +4 -0
  117. package/dist/use-cases/commands/list-incomplete-planner-tasks.d.ts +10 -3
  118. package/dist/use-cases/commands/list-incomplete-todo-tasks.d.ts +8 -2
  119. package/dist/use-cases/commands/list-mail-attachments.d.ts +8 -2
  120. package/dist/use-cases/commands/list-mail-child-folders.d.ts +1 -5
  121. package/dist/use-cases/commands/list-mail-folder-messages-delta.d.ts +4 -0
  122. package/dist/use-cases/commands/list-mail-folder-messages.d.ts +1 -5
  123. package/dist/use-cases/commands/list-mail-folders.d.ts +1 -3
  124. package/dist/use-cases/commands/list-mail-messages.d.ts +1 -3
  125. package/dist/use-cases/commands/list-mail-rules.d.ts +1 -1
  126. package/dist/use-cases/commands/list-my-direct-reports.d.ts +13 -0
  127. package/dist/use-cases/commands/list-my-memberships.d.ts +4 -0
  128. package/dist/use-cases/commands/list-my-transitive-memberships.d.ts +4 -0
  129. package/dist/use-cases/commands/list-onenote-notebook-sections.d.ts +1 -5
  130. package/dist/use-cases/commands/list-onenote-notebooks.d.ts +1 -3
  131. package/dist/use-cases/commands/list-onenote-section-pages.d.ts +1 -5
  132. package/dist/use-cases/commands/list-outlook-categories.d.ts +6 -0
  133. package/dist/use-cases/commands/list-plan-buckets.d.ts +1 -5
  134. package/dist/use-cases/commands/list-planner-plans.d.ts +1 -3
  135. package/dist/use-cases/commands/list-planner-tasks.d.ts +1 -3
  136. package/dist/use-cases/commands/list-recent-files.d.ts +4 -0
  137. package/dist/use-cases/commands/list-recently-used-insights.d.ts +4 -0
  138. package/dist/use-cases/commands/list-relevant-people.d.ts +4 -0
  139. package/dist/use-cases/commands/list-room-lists.d.ts +4 -0
  140. package/dist/use-cases/commands/list-rooms.d.ts +4 -0
  141. package/dist/use-cases/commands/list-sensitivity-labels.d.ts +4 -0
  142. package/dist/use-cases/commands/list-shared-calendar-events.d.ts +4 -0
  143. package/dist/use-cases/commands/list-shared-insights.d.ts +4 -0
  144. package/dist/use-cases/commands/list-shared-mailbox-folder-messages.d.ts +4 -0
  145. package/dist/use-cases/commands/list-shared-mailbox-messages.d.ts +4 -0
  146. package/dist/use-cases/commands/list-shared-with-me.d.ts +6 -0
  147. package/dist/use-cases/commands/list-sharepoint-list-columns.d.ts +4 -0
  148. package/dist/use-cases/commands/list-sharepoint-list-item-versions.d.ts +4 -0
  149. package/dist/use-cases/commands/list-sharepoint-site-drives.d.ts +1 -5
  150. package/dist/use-cases/commands/list-sharepoint-site-list-items.d.ts +1 -6
  151. package/dist/use-cases/commands/list-sharepoint-site-lists.d.ts +7 -2
  152. package/dist/use-cases/commands/list-sharepoint-site-onenote-notebook-sections.d.ts +5 -0
  153. package/dist/use-cases/commands/list-sharepoint-site-onenote-notebooks.d.ts +5 -0
  154. package/dist/use-cases/commands/list-sharepoint-site-onenote-section-pages.d.ts +5 -0
  155. package/dist/use-cases/commands/list-sharepoint-site-pages.d.ts +4 -0
  156. package/dist/use-cases/commands/list-site-columns.d.ts +4 -0
  157. package/dist/use-cases/commands/list-site-content-types.d.ts +4 -0
  158. package/dist/use-cases/commands/list-specific-calendar-events.d.ts +1 -5
  159. package/dist/use-cases/commands/list-team-channels.d.ts +1 -5
  160. package/dist/use-cases/commands/list-team-installed-apps.d.ts +8 -0
  161. package/dist/use-cases/commands/list-teams-chat-history.d.ts +16 -0
  162. package/dist/use-cases/commands/list-teams-chat-messages.d.ts +8 -0
  163. package/dist/use-cases/commands/list-teams-chats-with-messages.d.ts +9 -0
  164. package/dist/use-cases/commands/list-todo-linked-resources.d.ts +1 -6
  165. package/dist/use-cases/commands/list-todo-task-lists.d.ts +1 -3
  166. package/dist/use-cases/commands/list-todo-tasks-delta.d.ts +8 -0
  167. package/dist/use-cases/commands/list-todo-tasks.d.ts +8 -2
  168. package/dist/use-cases/commands/list-trending-insights.d.ts +4 -0
  169. package/dist/use-cases/commands/list-user-direct-reports.d.ts +4 -0
  170. package/dist/use-cases/commands/markdown-pipeline.d.ts +23 -0
  171. package/dist/use-cases/commands/microsoft-search-query.d.ts +8 -0
  172. package/dist/use-cases/commands/my-quick-context.d.ts +6 -0
  173. package/dist/use-cases/commands/next-page.d.ts +2 -2
  174. package/dist/use-cases/commands/odata-query.d.ts +90 -0
  175. package/dist/use-cases/commands/office-to-markdown.d.ts +6 -0
  176. package/dist/use-cases/commands/onenote-5k-limit.d.ts +3 -0
  177. package/dist/use-cases/commands/output-path.d.ts +37 -0
  178. package/dist/use-cases/commands/resolve-calendar-link.d.ts +8 -0
  179. package/dist/use-cases/commands/resolve-drive-share-link.d.ts +8 -0
  180. package/dist/use-cases/commands/resolve-mail-link.d.ts +8 -0
  181. package/dist/use-cases/commands/resolve-teams-link.d.ts +8 -0
  182. package/dist/use-cases/commands/scopes-check.d.ts +6 -0
  183. package/dist/use-cases/commands/search-mail-messages.d.ts +3 -6
  184. package/dist/use-cases/commands/search-my-documents.d.ts +1 -5
  185. package/dist/use-cases/commands/search-onedrive-files.d.ts +1 -8
  186. package/dist/use-cases/commands/search-onenote-pages.d.ts +7 -2
  187. package/dist/use-cases/commands/search-sharepoint-sites-by-name.d.ts +1 -5
  188. package/dist/use-cases/commands/sharepoint-link-extractor.d.ts +14 -0
  189. package/dist/use-cases/commands/text-passthrough.d.ts +24 -0
  190. package/dist/use-cases/commands/version-id.d.ts +19 -0
  191. package/dist/use-cases/commands/xlsx-to-markdown.d.ts +6 -0
  192. package/dist/use-cases/ports/filesystem.d.ts +8 -0
  193. package/docs/COMMANDS.md +243 -0
  194. package/docs/USAGE.md +249 -0
  195. package/docs/commands.json +5870 -789
  196. package/package.json +11 -2
  197. /package/dist/use-cases/commands/{search-sharepoint-sites.d.ts → list-mail-folders-delta.d.ts} +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,92 @@
1
+ # Changelog
2
+
3
+ All notable changes to `ask-marcel-office-cli` are documented here.
4
+
5
+ ## 1.0.0
6
+
7
+ The first stable release. Two breaking changes consolidate the public output
8
+ contract; the rest is additive.
9
+
10
+ ### Breaking — output contract
11
+
12
+ - **Errors emit on stdout**, not stderr. `process.exitCode = 1` still
13
+ distinguishes failure, so shell scripts that branch on the exit code keep
14
+ working — but anything that read errors from stderr (`cmd 2>err.json`) needs
15
+ to merge streams or read stdout instead. An LLM piping `ask-marcel <cmd>
16
+ | jq` no longer needs `2>&1`.
17
+ - **Every command output is wrapped in the v1 envelope**:
18
+ - Success: `{ ok: true, data: <payload>, nextLink?: string, count?: number }`
19
+ - Error: `{ ok: false, error: "<message>" }`
20
+
21
+ `@odata.nextLink` and `@odata.count` from the underlying Graph payload are
22
+ lifted to the top of the envelope and removed from `data`. Consumers who
23
+ parsed `value[0]` as the first item now read `data.value[0]`.
24
+
25
+ ### Added — OData query passthrough on every list/search command
26
+
27
+ Every `list-*` / `search-*` / `get-*-delta` command now accepts the six
28
+ standard OData query parameters as optional flags, so an LLM can shrink large
29
+ responses on the fly:
30
+
31
+ ```
32
+ --top <n> maximum items per page
33
+ --skip <n> offset
34
+ --select <csv> comma-separated field list (huge payload-size win)
35
+ --filter <kql> server-side predicate
36
+ --orderby <kql> sort expression
37
+ --expand <nav> inline navigation properties
38
+ ```
39
+
40
+ Four commands keep `buildCommand` because their hard-coded `$filter` would
41
+ collide with a user-supplied `--filter`: `list-conversation-messages`,
42
+ `list-incomplete-todo-tasks`, `list-incomplete-planner-tasks`,
43
+ `search-onenote-pages`.
44
+
45
+ ### Added — `my-quick-context`
46
+
47
+ New meta command that issues five Graph calls in parallel (`/me`, `/me/drive`,
48
+ `/me/mailFolders/inbox`, `/me/todo/lists`, `/me/calendar`) and returns
49
+ `{ user, primaryDriveId, inboxId, todoLists, primaryCalendarId }` in one
50
+ round trip. Replaces the audit's 5-call discovery chain.
51
+
52
+ ### Fixed
53
+
54
+ - `microsoft-search-query` no longer 400s. Splits `entityTypes` into two
55
+ `requests[]` entries so Graph stops rejecting `person` mixed with
56
+ file/mail/event types.
57
+ - `list-conversation-messages` no longer trips Graph's `InefficientFilter`.
58
+ Drops the `$orderby=receivedDateTime` from the OData query.
59
+ - `list-sharepoint-site-items` is removed. Microsoft Graph has no list-less
60
+ site/items collection endpoint; `get-sharepoint-site-item`'s docstring now
61
+ points at the two-step discovery chain
62
+ (`list-sharepoint-site-lists` → `list-sharepoint-site-list-items`) that
63
+ Graph actually supports.
64
+ - `list-groups` summary no longer advertises a `--top` flag it didn't
65
+ register. Project-wide invariant added so every `--flag` mentioned in any
66
+ command summary must be a real option or alias on that command.
67
+ - `next-page` routes nextLinks under `/me/chats` and `/chats/...` via the
68
+ elevated M365ChatClient token. Chat pagination no longer 403s.
69
+ - `search-onenote-pages` accepts `--query` as an alias for
70
+ `--title-substring`, matching the convention used by every other search
71
+ command.
72
+
73
+ ### Added — flag aliases
74
+
75
+ - `--todo-list-id` is now accepted by every command that takes
76
+ `--todo-task-list-id` (`--task-list-id` alias preserved).
77
+ - `get-sharepoint-site-item` accepts `--list-item-id` (alias for `--item-id`).
78
+ `get-sharepoint-site-list-item` accepts `--item-id` (alias for
79
+ `--list-item-id`). LLMs that write either spelling from memory now hit the
80
+ right flag.
81
+
82
+ ### Quality
83
+
84
+ - Bun `JSON.stringify` already escapes every U+0000–U+001F control character
85
+ and U+2028 / U+2029 separator. The audit's "raw control chars" claim in
86
+ the four insight commands does not reproduce against the actual code path;
87
+ regression-guard tests pin the contract.
88
+
89
+ ## Older
90
+
91
+ Earlier history is in the git log. See `git log --oneline` for individual
92
+ commits up to and including v0.11.0.
package/README.md CHANGED
@@ -1,265 +1,191 @@
1
1
  # ask-marcel-office-cli
2
2
 
3
- Microsoft Graph CLI designed for LLM consumption via skills. Explicit commands, compact JSON output, zero interactive prompts beyond auth.
4
-
5
- ## Commands
6
-
7
- ### Authentication
8
-
9
- | Command | Description |
10
- |---------|-------------|
11
- | `login` | Authenticate (cached → refresh → browser fallback) |
12
- | `logout` | Clear cached tokens |
13
- | `update` | Update ask-marcel to the latest version on npm (auto-detects npm vs bun) |
14
- | `docs <cmd>` | Print Markdown docs for a single command (full machine-readable manifest at [`docs/commands.json`](docs/commands.json) or via `import manifest from 'ask-marcel-office-cli/commands.json'`) |
15
-
16
- <!-- AUTO-GENERATED-COMMANDS:BEGIN -->
17
-
18
- ### OneDrive Files
19
-
20
- | Command | Description | Required params | Graph endpoint |
21
- |---------|-------------|-----------------|----------------|
22
- | `download-drive-item-version-content` | Download the binary content of a specific *non-current* historical version of a OneDrive / SharePoint file. Graph refuses to serve the current version through this endpoint with "You cannot get the content of the current version" — for the current version use `download-onedrive-file-content` instead. Same envelope as that command (302 → downloadUrl, or base64 bytes). | `--drive-id`, `--item-id`, `--version-id` | `GET /drives/{drive-id}/items/{item-id}/versions/{version-id}/content` |
23
- | `download-onedrive-file-content` | Download the binary content of a file stored in OneDrive / SharePoint. Graph normally returns a 302 redirect to a pre-signed CDN URL, surfaced as `@microsoft.graph.downloadUrl`; if it returns bytes directly they are base64-encoded for safe JSON output. | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}/content` |
24
- | `get-drive-delta` | Get the incremental change set (added / modified / deleted items) under a OneDrive / SharePoint folder. Use the `@odata.deltaLink` from a previous response to resume. | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}/delta()` |
25
- | `get-drive-item` | Get the metadata (driveItem resource) of a single file or folder in OneDrive / SharePoint. | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}` |
26
- | `get-drive-root-item` | Get the root folder (driveItem) of a OneDrive / SharePoint drive. | `--drive-id` | `GET /drives/{drive-id}/root` |
27
- | `list-drive-item-permissions` | List the sharing permissions on a OneDrive / SharePoint file or folder. | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}/permissions` |
28
- | `list-drive-item-versions` | List the historical versions of a OneDrive / SharePoint file (each save creates a new version). | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}/versions` |
29
- | `list-drives` | List all OneDrive / SharePoint drives the signed-in user has access to. | _(none)_ | `GET /me/drives` |
30
- | `list-folder-files` | List the children (files and subfolders) of a folder in OneDrive / SharePoint. | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}/children` |
31
- | `search-my-documents` | Search the signed-in user’s default OneDrive for documents matching a free-text query (filename, content, metadata). | `--query` | `GET /me/drive/search(q='{query}')` |
32
- | `search-onedrive-files` | Search a single OneDrive / SharePoint drive for files and folders matching a free-text query. | `--drive-id`, `--query` | `GET /drives/{drive-id}/search(q='{query}')` |
33
-
34
- ### Excel (workbook files)
35
-
36
- | Command | Description | Required params | Graph endpoint |
37
- |---------|-------------|-----------------|----------------|
38
- | `get-excel-range` | Get the cell values, formulas, and formats of a specific Excel range (e.g. `A1:C10`). | `--drive-id`, `--item-id`, `--worksheet-id`, `--address` | `GET /drives/{drive-id}/items/{item-id}/workbook/worksheets/{worksheet-id}/range(address='{address}')` |
39
- | `get-excel-table` | Get the metadata (style, header row, total row) of a single named Excel table. | `--drive-id`, `--item-id`, `--table-id` | `GET /drives/{drive-id}/items/{item-id}/workbook/tables/{table-id}` |
40
- | `list-excel-table-rows` | List the data rows of a named Excel table (excluding the header row). | `--drive-id`, `--item-id`, `--table-id` | `GET /drives/{drive-id}/items/{item-id}/workbook/tables/{table-id}/rows` |
41
- | `list-excel-tables` | List the named tables across every worksheet in an Excel workbook. | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}/workbook/tables` |
42
- | `list-excel-worksheets` | List the worksheets (tabs) inside an Excel workbook stored in OneDrive / SharePoint. | `--drive-id`, `--item-id` | `GET /drives/{drive-id}/items/{item-id}/workbook/worksheets` |
43
-
44
- ### SharePoint Sites
45
-
46
- | Command | Description | Required params | Graph endpoint |
47
- |---------|-------------|-----------------|----------------|
48
- | `get-sharepoint-site` | Get the metadata of a single SharePoint site by its site ID. | `--site-id` | `GET /sites/{site-id}` |
49
- | `get-sharepoint-site-by-path` | Resolve a SharePoint site by its hostname + server-relative path. Use this when you have a SharePoint URL (e.g. `https://contoso.sharepoint.com/sites/Marketing`) but no site ID. | `--hostname`, `--path` | `GET /sites/{hostname}:{path}` |
50
- | `get-sharepoint-site-drive-by-id` | Get the metadata of a single document library (drive) on a SharePoint site by drive ID. | `--site-id`, `--drive-id` | `GET /sites/{site-id}/drives/{drive-id}` |
51
- | `get-sharepoint-site-list` | Get the metadata (display name, template, columns) of a single SharePoint list. | `--site-id`, `--list-id` | `GET /sites/{site-id}/lists/{list-id}` |
52
- | `get-sharepoint-site-list-item` | Get a single row (listItem) of a SharePoint list by ID. | `--site-id`, `--list-id`, `--list-item-id` | `GET /sites/{site-id}/lists/{list-id}/items/{list-item-id}` |
53
- | `list-sharepoint-site-drives` | List the document libraries (drives) attached to a SharePoint site. | `--site-id` | `GET /sites/{site-id}/drives` |
54
- | `list-sharepoint-site-list-items` | List the rows (listItem resources) of a single SharePoint list. | `--site-id`, `--list-id` | `GET /sites/{site-id}/lists/{list-id}/items` |
55
- | `list-sharepoint-site-lists` | List all SharePoint lists (custom + built-in document libraries) on a site. | `--site-id` | `GET /sites/{site-id}/lists` |
56
- | `search-sharepoint-sites` | List the SharePoint sites the signed-in user has access to (returns the followed sites by default). | _(none)_ | `GET /sites` |
57
- | `search-sharepoint-sites-by-name` | Search the tenant for SharePoint sites whose display name or description matches a free-text query (returns up to 25). | `--query` | `GET /sites?search={query}` |
58
-
59
- ### Tasks (To Do + Planner)
60
-
61
- | Command | Description | Required params | Graph endpoint |
62
- |---------|-------------|-----------------|----------------|
63
- | `get-planner-bucket` | Get the metadata of a single Microsoft Planner bucket (column / lane). | `--planner-bucket-id` | `GET /planner/buckets/{planner-bucket-id}` |
64
- | `get-planner-plan` | Get the metadata of a single Microsoft Planner plan (title, owner group, container). | `--planner-plan-id` | `GET /planner/plans/{planner-plan-id}` |
65
- | `get-planner-task` | Get the metadata of a single Microsoft Planner task (title, assignees, dates, completion). | `--planner-task-id` | `GET /planner/tasks/{planner-task-id}` |
66
- | `get-planner-task-details` | Get the rich details (description, checklist, references) of a Microsoft Planner task. | `--planner-task-id` | `GET /planner/tasks/{planner-task-id}/details` |
67
- | `get-todo-task` | Get a single Microsoft To Do task by its ID and its parent list ID. | `--todo-task-list-id`, `--todo-task-id` | `GET /me/todo/lists/{todo-task-list-id}/tasks/{todo-task-id}` |
68
- | `list-incomplete-planner-tasks` | List every incomplete Microsoft Planner task assigned to or owned by the signed-in user, across every plan. | _(none)_ | `GET /me/planner/tasks?$filter=percentComplete ne 100` |
69
- | `list-incomplete-todo-tasks` | List every incomplete Microsoft To Do task in a given list (status not equal to `completed`). | `--todo-task-list-id` | `GET /me/todo/lists/{todo-task-list-id}/tasks?$filter=status ne 'completed'` |
70
- | `list-plan-buckets` | List the buckets (columns / lanes) of a Microsoft Planner plan. | `--planner-plan-id` | `GET /planner/plans/{planner-plan-id}/buckets` |
71
- | `list-plan-tasks` | List every task within a Microsoft Planner plan, regardless of completion status (Graph orders by `orderHint`). Use `list-incomplete-planner-tasks` for the across-plans incomplete view. | `--planner-plan-id` | `GET /planner/plans/{planner-plan-id}/tasks` |
72
- | `list-planner-plans` | List every Microsoft Planner plan the signed-in user has access to (across every group). Use this to discover plan IDs without needing an existing task as the entry point. | _(none)_ | `GET /me/planner/plans` |
73
- | `list-planner-tasks` | List every Microsoft Planner task assigned to or owned by the signed-in user, across all plans. | _(none)_ | `GET /me/planner/tasks` |
74
- | `list-todo-linked-resources` | List the linked resources (URLs, emails, files) attached to a Microsoft To Do task. | `--todo-task-list-id`, `--todo-task-id` | `GET /me/todo/lists/{todo-task-list-id}/tasks/{todo-task-id}/linkedResources` |
75
- | `list-todo-task-lists` | List the signed-in user’s Microsoft To Do task lists (e.g. `Tasks`, `Flagged Emails`, custom lists). | _(none)_ | `GET /me/todo/lists` |
76
- | `list-todo-tasks` | List every task in a single Microsoft To Do task list, regardless of completion status. Use `list-incomplete-todo-tasks` if you only want the open ones. | `--todo-task-list-id` | `GET /me/todo/lists/{todo-task-list-id}/tasks` |
77
-
78
- ### Mail
79
-
80
- | Command | Description | Required params | Graph endpoint |
81
- |---------|-------------|-----------------|----------------|
82
- | `get-mail-attachment` | Get a single attachment on an Outlook message (metadata, plus the base64 `contentBytes` for file attachments). | `--message-id`, `--attachment-id` | `GET /me/messages/{message-id}/attachments/{attachment-id}` |
83
- | `get-mail-message` | Get a single Outlook message by ID, including subject, sender, body, and flags. | `--message-id` | `GET /me/messages/{message-id}` |
84
- | `get-mailbox-settings` | Get the signed-in user’s Outlook mailbox settings (timezone, working hours, automatic replies). | _(none)_ | `GET /me/mailboxSettings` |
85
- | `list-mail-attachments` | List the attachments (file, item, reference) on a single Outlook message. | `--message-id` | `GET /me/messages/{message-id}/attachments` |
86
- | `list-mail-child-folders` | List the subfolders of a single Outlook mail folder (e.g. subfolders of Inbox). | `--mail-folder-id` | `GET /me/mailFolders/{mail-folder-id}/childFolders` |
87
- | `list-mail-folder-messages` | List the messages inside a specific Outlook mail folder (Inbox, custom folder, etc.). | `--mail-folder-id` | `GET /me/mailFolders/{mail-folder-id}/messages` |
88
- | `list-mail-folders` | List the top-level mail folders in the signed-in user’s Outlook mailbox (Inbox, Sent Items, etc.). | _(none)_ | `GET /me/mailFolders` |
89
- | `list-mail-messages` | List the most recent messages from across the signed-in user’s entire Outlook mailbox (every folder including Sent, Archive, Junk; default sort `receivedDateTime` desc). Use `list-mail-folder-messages` to scope to a single folder such as Inbox. | _(none)_ | `GET /me/messages` |
90
- | `list-mail-rules` | List the inbox / folder rules attached to a single Outlook mail folder. | `--mail-folder-id` | `GET /me/mailFolders/{mail-folder-id}/messageRules` |
91
- | `search-mail-messages` | Search the signed-in user’s entire Outlook mailbox using KQL or free text. Results are ranked by Graph relevance. | `--query` | `GET /me/messages?$search="{query}"` |
92
-
93
- ### Notes (OneNote)
94
-
95
- | Command | Description | Required params | Graph endpoint |
96
- |---------|-------------|-----------------|----------------|
97
- | `get-onenote-page-content` | Get the HTML body of a single OneNote page. Returned in a JSON envelope so the HTML survives transport. | `--onenote-page-id` | `GET /me/onenote/pages/{onenote-page-id}/content` |
98
- | `list-all-onenote-sections` | List every OneNote section the signed-in user can see, across all notebooks. | _(none)_ | `GET /me/onenote/sections` |
99
- | `list-onenote-notebook-sections` | List the top-level sections of a single OneNote notebook (flat — does NOT recurse into section groups; use `list-all-onenote-sections` to flatten every notebook the user has access to). | `--notebook-id` | `GET /me/onenote/notebooks/{notebook-id}/sections` |
100
- | `list-onenote-notebooks` | List the OneNote notebooks the signed-in user owns or has access to (sorted by `createdDateTime` desc by Graph; soft-deleted notebooks excluded). | _(none)_ | `GET /me/onenote/notebooks` |
101
- | `list-onenote-section-pages` | List the pages inside a single OneNote section. | `--onenote-section-id` | `GET /me/onenote/sections/{onenote-section-id}/pages` |
102
- | `search-onenote-pages` | Find OneNote pages whose title contains a substring (case-sensitive — page content is NOT searched). Microsoft removed full-text OneNote `?search=` from v1.0 Graph; only $filter against `title` remains, which is what this command runs. | `--title-substring` | `GET /me/onenote/pages?$filter=contains(title,'{title-substring}')` |
103
-
104
- ### User
105
-
106
- | Command | Description | Required params | Graph endpoint |
107
- |---------|-------------|-----------------|----------------|
108
- | `get-current-user` | Return the signed-in user’s Microsoft Graph profile (id, displayName, mail, jobTitle, etc.). | _(none)_ | `GET /me` |
109
- | `get-my-profile-photo` | Download the signed-in user’s profile photo (largest available size). Returned as a base64 envelope so the binary survives JSON output. | _(none)_ | `GET /me/photo/$value` |
110
-
111
- ### Calendar
112
-
113
- | Command | Description | Required params | Graph endpoint |
114
- |---------|-------------|-----------------|----------------|
115
- | `get-calendar-event` | Fetch a single calendar event by ID from the signed-in user’s default calendar. | `--event-id` | `GET /me/events/{event-id}` |
116
- | `get-calendar-view` | List the signed-in user’s default-calendar events with recurrence expanded into individual occurrences in a date range. Both ISO date-time params are required by Graph. | `--start-date-time`, `--end-date-time` | `GET /me/calendarView?startDateTime={start-date-time}&endDateTime={end-date-time}` |
117
- | `get-specific-calendar-event` | Fetch a single calendar event by ID from a specific (non-default) calendar. | `--calendar-id`, `--event-id` | `GET /me/calendars/{calendar-id}/events/{event-id}` |
118
- | `get-specific-calendar-view` | List the events in a specific (non-default) calendar with recurrence expanded into individual occurrences in a date range. Both ISO date-time params are required by Graph. | `--calendar-id`, `--start-date-time`, `--end-date-time` | `GET /me/calendars/{calendar-id}/calendarView?startDateTime={start-date-time}&endDateTime={end-date-time}` |
119
- | `list-calendar-event-instances` | List the individual occurrences of a recurring calendar event over a date range. Both ISO date-time params are required by Graph. | `--calendar-id`, `--event-id`, `--start-date-time`, `--end-date-time` | `GET /me/calendars/{calendar-id}/events/{event-id}/instances?startDateTime={start-date-time}&endDateTime={end-date-time}` |
120
- | `list-calendar-events` | List the events in the signed-in user’s default calendar (does not expand recurrences). | _(none)_ | `GET /me/events` |
121
- | `list-calendar-events-delta` | Get the incremental change set (added / modified / deleted events) for the signed-in user’s default calendar. Use the `@odata.deltaLink` from a previous response to resume. | _(none)_ | `GET /me/events/delta()` |
122
- | `list-calendar-view-delta` | Get the first page of the incremental change set of expanded calendar-view occurrences over a date range. Subsequent pages: feed the returned `@odata.nextLink` to `next-page`; resume later via the `@odata.deltaLink`. | `--start-date-time`, `--end-date-time` | `GET /me/calendarView/delta()?startDateTime={start-date-time}&endDateTime={end-date-time}` |
123
- | `list-calendars` | List the calendars in the signed-in user’s mailbox (default + secondary calendars + shared calendars). | _(none)_ | `GET /me/calendars` |
124
- | `list-specific-calendar-events` | List the events in a specific (non-default) calendar (does not expand recurrences). | `--calendar-id` | `GET /me/calendars/{calendar-id}/events` |
125
-
126
- ### Chats
127
-
128
- | Command | Description | Required params | Graph endpoint |
129
- |---------|-------------|-----------------|----------------|
130
- | `list-chat-members` | List the members of a single Microsoft Teams chat. | `--chat-id` | `GET /chats/{chat-id}/members` |
131
-
132
- ### Teams
133
-
134
- | Command | Description | Required params | Graph endpoint |
135
- |---------|-------------|-----------------|----------------|
136
- | `get-team` | Get the metadata of a single Microsoft Team (display name, settings, member-settings, owner group). | `--team-id` | `GET /teams/{team-id}` |
137
- | `get-team-channel` | Get the metadata of a single channel inside a Microsoft Team. | `--team-id`, `--channel-id` | `GET /teams/{team-id}/channels/{channel-id}` |
138
- | `list-joined-teams` | List the Microsoft Teams the signed-in user is a member of. | _(none)_ | `GET /me/joinedTeams` |
139
- | `list-team-channels` | List the channels (standard, private, shared) inside a single Microsoft Team. | `--team-id` | `GET /teams/{team-id}/channels` |
140
-
141
- ### Meta / Pagination
142
-
143
- | Command | Description | Required params | Graph endpoint |
144
- |---------|-------------|-----------------|----------------|
145
- | `next-page` | Fetch the next page of a paginated Graph response. Pass the `@odata.nextLink` value returned by any list / search / delta command to walk pagination yourself. | `--url` | `GET {url}` |
146
-
147
- <!-- AUTO-GENERATED-COMMANDS:END -->
148
- ## Install
149
-
150
- Requires Node ≥20 **or** Bun ≥1.0 on the user's machine. Works on Windows, macOS, and Linux.
3
+ **A Microsoft Graph CLI built for LLMs.** 165 read-only commands across Mail, Calendar, OneDrive, SharePoint, Excel, Teams chats, Planner / To-Do, OneNote, and directory. Sign in once with your Microsoft 365 account — no Azure app registration, no admin consent, no client secrets.
151
4
 
152
5
  ```bash
153
- npm i -g ask-marcel-office-cli # any platform with Node
154
- # or
155
- bun add -g ask-marcel-office-cli # any platform with Bun
6
+ npm i -g ask-marcel-office-cli
7
+ ask-marcel login # browser opens once, token cached
8
+ ask-marcel my-quick-context # who am I + my IDs, in one round trip
9
+ ask-marcel list-mail-messages --top 5
156
10
  ```
157
11
 
158
- The first launch prints a one-time notice if a newer version is on npm; update with the same command above plus `@latest`.
12
+ ---
159
13
 
160
- ## Usage (CLI)
14
+ ## Why it exists
15
+
16
+ LLM tool-loops keep hitting the same three walls with Microsoft Graph:
17
+
18
+ 1. **Auth is a project.** Register an app, get tenant-admin consent, manage secrets, refresh tokens — before the first API call.
19
+ 2. **Default payloads are tuned for backend services, not context windows.** Listing endpoints return every field on every item, used-range Excel calls return four redundant 2D arrays, attachment endpoints inline base64 by default. An agent that reads "what's in my inbox" without trimming burns its budget on metadata it never needed.
20
+ 3. **Errors are opaque.** `BadRequest: Invalid filter clause` doesn't tell a model what to fix.
21
+
22
+ `ask-marcel` fixes all three at the CLI layer, so the model just calls commands and reads back-pressure-friendly responses.
23
+
24
+ ## What you get
25
+
26
+ ### Read-only by design
27
+
28
+ **This is the most important property.** 164 GET endpoints + 1 POST (search). No `send-mail`, no `create-event`, no `upload-file`, no `delete-anything`. A hallucinated command can't break anything — the worst case is a 404. Safe default for autonomous agents, MCP servers, and "let Claude poke around my mailbox" sessions where you can't fully review every tool call.
29
+
30
+ ### One call gets the full email context
31
+
32
+ A typical "read this email" loop in raw Graph: GET the message → GET the attachments list → GET each attachment's bytes → scan the body HTML for `sharepoint.com` URLs → resolve each URL to a driveItem → GET each driveItem. Six round-trips minimum, plus HTML-to-text conversion the LLM has to do itself.
33
+
34
+ `convert-mail-to-markdown` collapses that into one call:
35
+
36
+ - Body rendered as markdown (turndown pipeline)
37
+ - Inline images embedded as base64 `data:` URIs (size-capped per image — opt out with `--inline-images false` to keep raw `cid:` refs)
38
+ - File attachments listed below the body with id + name + size, ready for follow-up calls
39
+ - Pair with `extract-sharepoint-links-in-mail` to resolve every SharePoint URL in the body to its driveItem in parallel (capped at 25 unique URLs per call)
40
+
41
+ ### Office docs → markdown or PDF on the fly
42
+
43
+ Feed any Office-shaped file (docx, xlsx, pptx, csv, rtf, odt, …) into the local conversion pipeline OR through Graph's `?format=pdf` when slide layout and images matter:
44
+
45
+ - `download-drive-item-as-markdown` — docx via mammoth (with inline images as data URIs), xlsx as one markdown table per sheet, csv as a table, plain-text passthrough
46
+ - `download-drive-item-as-pdf` — Graph PDF conversion for anything it supports (preserves slide layout, images, charts — the right call for pptx and image-heavy docs)
47
+ - `convert-mail-attachment-to-markdown` / `convert-mail-attachment-to-pdf` — same pipelines but starting from an email attachment
48
+
49
+ The CLI follows any SharePoint media-transform redirect internally, so the LLM never has to fetch an external URL.
50
+
51
+ ### Browser-OAuth at first launch
52
+
53
+ No Azure app, no tenant admin. The CLI captures the same token the Teams web client uses — works for any Microsoft 365 account, personal or enterprise.
54
+
55
+ ### Stable error envelope with actionable hints
56
+
57
+ Every failure — Graph, CLI parser, Zod validation, substrate — comes back as `{ok: false, error, errorCode?, hint?, source}`. The `hint` field tells the model *what to do next* (e.g. "string literals MUST use single quotes; embed one by doubling it") and `source` tells it where the failure came from. Curated rules for 20+ recurring Graph errors plus cross-resolver pointers (passed a Teams URL to `resolve-mail-link`? Hint says "re-run with `resolve-teams-link`").
58
+
59
+ ### Lean responses
60
+
61
+ Listings ship with hand-tuned `--select` defaults — a mail listing returns id, subject, from, to, cc, dates, read-state, importance, bodyPreview rather than every field on every message. `get-excel-used-range` returns the `values` array instead of the four 2D arrays Graph emits. Opt out per call with `--full true`, or override with your own `--select id,subject,body`.
62
+
63
+ ### Saves big binaries to disk so they never hit the model's context
64
+
65
+ A typical conversion command returns multi-MB PDF bytes. Sending 5 MB of base64 through stdout would blow most context windows AND quadruple the token bill.
66
+
67
+ Two flag patterns avoid the round-trip:
68
+
69
+ - **`--output-path /path/to/file.pdf`** — the CLI decodes the bytes, writes them to disk, and replaces `base64: "..."` in the envelope with `savedTo: "/path/to/file.pdf"`. The LLM sees a 3-line confirmation instead of a 7-million-character payload. Works on every command that returns binary or text content; rejected with a clear error on plain-JSON commands so a misapplied flag is never silent.
70
+ - **No flag, text mode** — binary commands print a one-line summary (`binary: application/pdf, 4837291 bytes — use --output-path to save`) instead of spilling base64 to stdout. The LLM sees a hint without ever pulling the bytes.
71
+
72
+ ### Relative dates on calendar windows
73
+
74
+ `--start-date-time "start-of-week" --end-date-time "+7d"`. No timestamp math before answering "what's on my calendar this week".
75
+
76
+ ## 30-second quickstart
161
77
 
162
78
  ```bash
79
+ # install (Bun ≥1.0 or Node ≥20)
80
+ npm i -g ask-marcel-office-cli
81
+
163
82
  # authenticate (cached → refresh → browser fallback)
164
83
  ask-marcel login
165
84
 
166
- # list drives
85
+ # everything else is read-only and discoverable from --help
167
86
  ask-marcel list-drives
87
+ ask-marcel search-onedrive-files --drive-id "b!abc..." --query "Q3 budget"
88
+ ask-marcel convert-mail-to-markdown --message-id "AAMkAD..."
89
+ ask-marcel list-calendar-view --start-date-time today --end-date-time +7d
90
+ ask-marcel convert-mail-attachment-to-pdf \
91
+ --message-id "AAMkAD..." --attachment-id "AAMkAD...attach1" \
92
+ --output-path /tmp/deck.pdf
93
+ ```
168
94
 
169
- # search for files
170
- ask-marcel search-onedrive-files --drive-id abc123 --query "report"
95
+ ## Asking the CLI what it can do
171
96
 
172
- # get Excel table data
173
- ask-marcel list-excel-table-rows --drive-id abc123 --item-id xyz789 --table-id table1
97
+ Five discovery surfaces, each tuned for a different audience and token budget:
174
98
 
175
- # search SharePoint sites
176
- ask-marcel search-sharepoint-sites
99
+ | When you want | Run | Returns |
100
+ |---|---|---|
101
+ | Help with a single command | `ask-marcel <command> --help` | Required flags, optional flags, an example, pagination notes |
102
+ | A scan of every command | `ask-marcel --help` | One-sentence summary per command, grouped by category |
103
+ | The slim LLM-friendly index | `ask-marcel help-json --terse` | JSON manifest with heavy fields (options, response shape) stripped — best first-call for an agent meeting the CLI for the first time |
104
+ | The slim index for one domain | `ask-marcel help-json --terse --category mail` | Same as above, filtered to one of 12 categories — keeps the response tiny when the agent already knows the domain |
105
+ | Rich docs for one command | `ask-marcel docs <command>` | Full Markdown to stdout (response shape, examples, the underlying Graph endpoint, Microsoft Learn link) |
177
106
 
178
- # list SharePoint site lists
179
- ask-marcel list-sharepoint-site-lists --site-id contoso.sharepoint.com,1234-5678
107
+ Pair `help-json --terse --category <name>` with `docs <command>` for the canonical agent loop: scan the category, pick a command, fetch its full docs, then call it.
180
108
 
181
- # update to the latest version (auto-detects npm vs bun)
182
- ask-marcel update
109
+ ## Use it from Claude Code, Cursor, Cline, or any tool-calling LLM
183
110
 
184
- # clear tokens
185
- ask-marcel logout
111
+ Most agents already know how to read JSON from stdout. Two patterns work:
186
112
 
187
- # see all commands
188
- ask-marcel --help
189
- ```
113
+ **1. Drop in as a shell tool** — the agent learns the manifest, then runs `ask-marcel <command> --output json`. The slim defaults + structured error hints mean it can self-recover from typos.
190
114
 
191
- `ask-marcel update` auto-detects whether the CLI was installed via npm or bun (based on the bin path) and reinstalls globally with the matching tool. You can still run the install manually: `npm i -g ask-marcel-office-cli@latest` or `bun add -g ask-marcel-office-cli@latest`.
115
+ **2. Embed as a library** every command is exported. Compose it inside your own MCP server, Claude Agent, or LangChain tool:
192
116
 
193
- During development from a clone you can keep using `bun run src/main.ts <command>`.
117
+ ```ts
118
+ import { commands, buildDeps } from 'ask-marcel-office-cli';
119
+
120
+ const { graph } = buildDeps();
121
+ const result = await commands['list-mail-messages'].execute(graph, { top: '10' });
122
+ if (result.ok) {
123
+ // result.value is the Graph payload — typed Result<unknown, GraphError>
124
+ }
125
+ ```
194
126
 
195
- ## Usage (library)
127
+ ### Auth — two paths
196
128
 
197
- The package exports a typed library API for embedding inside your own CLI, agent, or service.
129
+ **Most users use the built-in browser-OAuth ladder:**
198
130
 
199
131
  ```ts
200
- import { commands, createGraphClient, buildDeps, type Result } from 'ask-marcel-office-cli';
132
+ import { buildDeps } from 'ask-marcel-office-cli';
201
133
 
202
- // option 1 — full ladder with built-in OAuth and file cache
203
134
  const { graph } = buildDeps();
204
- const result = await commands['list-drives'].execute(graph, {});
205
- if (result.ok) console.log(result.value);
206
-
207
- // option 2 — bring your own AuthManager / token
208
- const graph = createGraphClient({
209
- getAccessToken: async () => ({ ok: true, value: process.env.MS_GRAPH_TOKEN as never }),
210
- logout: async () => ({ ok: true, value: undefined }),
211
- });
212
- const me = await commands['get-current-user'].execute(graph, {});
135
+ // First call triggers cache → refresh → headed-Chromium fallback automatically.
136
+ // Tokens cached at ~/.ask-marcel/token-cache.json for subsequent calls.
213
137
  ```
214
138
 
215
- The full export list (registry, factories, `Result`, branded types, ports) is in [src/index.ts](src/index.ts).
139
+ **Agents / CI / MCP servers bring your own token:**
216
140
 
217
- ## Architecture
141
+ ```ts
142
+ import { createGraphClient } from 'ask-marcel-office-cli';
218
143
 
219
- ```
220
- src/
221
- domain/ — Result<T,E>, branded value-object types (AccessToken, EnvVar), JWT utilities, format-error
222
- infra/ — Auth recovery ladder (cache → refresh → Playwright browser), Graph API HTTP client, Winston logger
223
- use-cases/ — Commands (schemas + execute functions), ports
224
- composition/ — CLI wiring (Commander), dependency graph
225
- presenter/ — Compact JSON output formatting
144
+ const graph = createGraphClient({
145
+ getAccessToken: async () => ({
146
+ ok: true,
147
+ value: await fetchTokenFromYourVault(),
148
+ }),
149
+ logout: async () => ({ ok: true, value: undefined }),
150
+ });
226
151
  ```
227
152
 
228
- - **Auth**: Three-rung recovery ladderfile-based cached JWT OAuth refresh_token exchange Playwright browser intercepting Teams login
229
- - **Client ID**: `5e3ce6c0-2b1f-4285-8d4b-75ee78787346` (Teams Web)
230
- - **Scopes**: `https://graph.microsoft.com/.default openid profile offline_access`
231
- - **Token cache**: `~/.ask-marcel/token-cache.json` (overridable via `BuildDepsConfig.cachePath`)
232
- - **Browser profile**: `~/.ask-marcel/browser-profile` (overridable via `ASKMARCEL_BROWSER_PROFILE`)
233
- - **Output**: Compact JSON via `JSON.stringify` — no indentation, optimised for LLM token efficiency
153
+ The `AuthManager` interface is two async methods that return `Result<T, AuthError>`. Plug in any token source Azure Managed Identity, a secrets vault, an on-behalf-of flow, hand-pasted JWTs in tests. The Graph client doesn't care where the token came from.
234
154
 
235
- ## Configuration
155
+ ## Deep docs
236
156
 
237
- Environment variables read at composition time:
157
+ - **[All 165 commands](docs/COMMANDS.md)** per-category tables with required params + Graph endpoint
158
+ - **[Usage guide](docs/USAGE.md)** — output formats, OData passthrough, `--output-path`, pagination, library API, architecture, configuration, quality gates
159
+ - **[Machine-readable manifest](docs/commands.json)** — JSON for programmatic discovery (LLM tool-loops, IDE plugins, MCP servers); also importable via `import manifest from 'ask-marcel-office-cli/commands.json'`
238
160
 
239
- | Variable | Used by | Default |
240
- |---|---|---|
241
- | `ASKMARCEL_LOG_LEVEL` | Winston logger; all output goes to **stderr** (stdout reserved for command JSON). Namespaced so a generic `LOG_LEVEL` exported by another tool in your shell does not leak into ours. | `error` (use `info` or `debug` for troubleshooting) |
242
- | `HOME` / `USERPROFILE` | Default cache and browser-profile paths | _(required)_ |
243
- | `ASKMARCEL_BROWSER_PROFILE` | Override Playwright user-data-dir | _(none)_ |
161
+ ## Roadmap
244
162
 
245
- `HTTP_PROXY` / `HTTPS_PROXY` / `http_proxy` / `https_proxy` are stripped from the process environment immediately before launching Playwright (see `src/infra/browser-auth.ts`).
163
+ Read-only stays the default forever. The list below is additive coverage and convenience concrete next steps, ordered by how often they come up in real LLM workflows:
246
164
 
247
- ## Quality gates (atelier four-check loop)
165
+ - **Zip archive support** — extract a `.zip` from an email attachment or OneDrive item and run every contained file through the right conversion pipeline (markdown or PDF), so an agent reading "the project handover archive" doesn't have to shell out to `unzip`.
166
+ - **SharePoint links inside documents** — today `extract-sharepoint-links-in-mail` finds and resolves every `sharepoint.com` URL in an email body; the same pass for inline links in docx/xlsx/pptx would close the loop on "follow every reference in this document".
167
+ - **Bulk folder conversion** — `convert-folder --drive-id … --item-id <folder> --format markdown` walks a folder and converts every Office file in one call, returning the combined markdown (or writing per-file outputs to a directory via `--output-path`).
168
+ - **OneNote end-to-end markdown** — OneNote page reads currently return raw HTML; chaining the turndown pipeline (same one `convert-mail-to-markdown` uses) would give parity with the docx route.
169
+ - **PowerPoint as slide images** — for vision-capable LLMs, render each pptx slide as a PNG alongside the existing PDF route so the model can reason about layout + diagrams without going through PDF parsing.
170
+ - **Calendar meeting attachments** — `/events/{id}/attachments` isn't exposed yet; would pair with the existing `convert-mail-attachment-to-*` shape so "summarize the deck attached to my 3pm" is a single call.
171
+ - **Multi-tenant auth profiles** — `ask-marcel login --profile work` / `--profile personal` with separate token caches at `~/.ask-marcel/<profile>/`, for consultants and contractors who routinely switch tenants.
172
+ - **Streaming pagination output** — write each page to stdout as it arrives instead of accumulating, so long delta walks don't hold gigabytes in memory and an agent can start processing page 1 while page 2 is in flight.
173
+ - **Excel charts as PNG** — `list-excel-worksheet-charts` already returns chart metadata; add `get-excel-chart-image` that renders the chart as a base64 PNG via Graph's `.../charts/{id}/image()` endpoint so vision-capable models can read the chart, not just its title.
174
+ - **Yammer / Viva Engage** — community posts, threads, and replies. Currently unscoped on the Teams web-client token, so this would need a scope investigation first.
175
+ - **Federated tenant search expansion** — `microsoft-search-query` exists but covers a narrow entity set; expanding to people, sites, lists, and chat messages would unify discovery across all of M365 behind one search call.
176
+ - **Quoted-text stripping in `convert-mail-to-markdown`** — collapse "On Tuesday Alice wrote..." reply chains so long threads stop blowing the context budget on duplicated quoted content.
177
+ - **`--diff` flag on `download-drive-item-version` markdown** — render the markdown diff between two historical versions of the same file (e.g. "what changed in the spec last week"), so an agent can answer revision questions without manually comparing two long markdown blobs.
178
+ - **Full-fidelity document context** — extract every piece of written information in a document: reviewer comments, threaded comments, slide notes, revision marks, descriptions, custom metadata, and sensitivity labels — folded into the markdown output so an LLM reads the whole story of a document, not just its body text.
179
+ - **OCR for images and image-only PDFs** — pipe images (jpg/png attachments, inline email images) and scanned PDFs through a local OCR pass, returning extracted text as markdown. Closes the loop on screenshots, scanned receipts, and image-only attachments that today come back as opaque bytes.
248
180
 
249
- ```bash
250
- bun test # full suite
251
- bun run lint # ESLint (0 warnings, 0 errors)
252
- bun run typecheck # tsc --noEmit
253
- bun run coverage # per-tier gates (100% on every tier: domain, use-cases, infra, composition, presenter)
254
- bun run mutate:changed # mutation testing on changed domain/use-case files (>90% kill threshold)
255
- ```
181
+ Suggestions, requests, and pull requests welcome — see the [issues page](https://github.com/vdelacou/ask-marcel-office-cli/issues).
256
182
 
257
- ### Pre-commit hook (atelier 8 gates)
183
+ ## Built with
258
184
 
259
- The repo ships an 8-gate hook at `.githooks/pre-commit` (commit size package.json gitleaks tests strict lint typecheck coverage mutation). Install once per clone:
185
+ - **Bun + TypeScript** single binary install, Node ≥20 fallback. `Result<T, E>` at every IO boundary, branded value-object types at trust boundaries, classicist outside-in TDD, zero lint warnings, 100% coverage on every tier.
186
+ - **Microsoft Graph v1.0** — the public API surface, no beta endpoints in production code.
187
+ - **Playwright** — headed Chromium for the first-launch browser-OAuth dance.
260
188
 
261
- ```bash
262
- git config core.hooksPath .githooks
263
- ```
189
+ ## License
264
190
 
265
- Optional but recommended: install [gitleaks](https://github.com/gitleaks/gitleaks) (`brew install gitleaks`) to enable gate 3. The hook degrades gracefully if it's missing.
191
+ MIT © Vincent Delacourt