@rubytech/create-maxy-code 0.1.452 → 0.1.454
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/package.json +1 -1
- package/payload/platform/lib/graph-style/dist/index.d.ts +19 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +111 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/icons.test.ts +63 -0
- package/payload/platform/lib/graph-style/src/index.ts +112 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +6 -4
- package/payload/platform/plugins/cloudflare/references/api.md +1 -1
- package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/admin-ui.md +2 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +3 -3
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +3 -3
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/complete-registration.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/complete-registration.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/complete-registration.test.js +131 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/complete-registration.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-paged.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-paged.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-paged.test.js +55 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client-paged.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list-paging.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list-paging.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list-paging.test.js +120 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list-paging.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-search-paging.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-search-paging.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-search-paging.test.js +66 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-search-paging.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-scan.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-scan.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-scan.test.js +48 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pending-scan.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/complete-registration.d.ts +31 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/complete-registration.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/complete-registration.js +47 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/complete-registration.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-scan.d.ts +11 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-scan.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-scan.js +31 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pending-scan.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +13 -9
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +19 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +14 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/scripts/complete-registration.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/scripts/complete-registration.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/scripts/complete-registration.js +111 -0
- package/payload/platform/plugins/outlook/mcp/dist/scripts/complete-registration.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +16 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +37 -7
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts +12 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +16 -12
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +2 -0
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +20 -9
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +7 -7
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +3 -3
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/scripts/lib/__tests__/account-settings-askgate.test.sh +144 -0
- package/payload/platform/scripts/lib/account-settings-askgate.sh +98 -0
- package/payload/platform/scripts/setup-account.sh +14 -0
- package/payload/platform/scripts/smoke-boot-services.sh +1 -0
- package/payload/platform/scripts/voice-mirror-audit.sh +70 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/config.js +7 -1
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +72 -6
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.d.ts +17 -0
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.js +14 -0
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/plugin-enum.d.ts +34 -0
- package/payload/platform/services/claude-session-manager/dist/plugin-enum.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/plugin-enum.js +66 -0
- package/payload/platform/services/claude-session-manager/dist/plugin-enum.js.map +1 -0
- package/payload/server/public/assets/{AdminLoginScreens-Bvf4HKmq.js → AdminLoginScreens-5R0A40Is.js} +1 -1
- package/payload/server/public/assets/AdminShell-B2HeKInP.js +2 -0
- package/payload/server/public/assets/{Checkbox-2SswiICc.js → Checkbox-yjCERU3B.js} +1 -1
- package/payload/server/public/assets/{admin-B7GPCzpr.js → admin-BDio0OER.js} +1 -1
- package/payload/server/public/assets/{browser-CodHC_MO.js → browser-bSeWXhod.js} +1 -1
- package/payload/server/public/assets/{calendar-DH7cyXKF.js → calendar-MNAmsxL3.js} +1 -1
- package/payload/server/public/assets/chat-Bq4Uq3Ht.js +1 -0
- package/payload/server/public/assets/chevron-left-D5XwUM2x.js +1 -0
- package/payload/server/public/assets/data-DY10qHOy.js +1 -0
- package/payload/server/public/assets/{graph-BToB78LG.js → graph-DRWRwsUZ.js} +7 -7
- package/payload/server/public/assets/graph-labels-Cy7GyGln.js +1 -0
- package/payload/server/public/assets/{maximize-2-CEQRv64B.js → maximize-2-BRHW5pKh.js} +1 -1
- package/payload/server/public/assets/{operator-Cx8cq2wf.js → operator-Dv2k2JaW.js} +1 -1
- package/payload/server/public/assets/{page-TCsQy4eP.js → page-BZRZZcqM.js} +1 -1
- package/payload/server/public/assets/{page-D6kzsk18.js → page-CoBzMOIg.js} +2 -2
- package/payload/server/public/assets/{public-B61xcmAr.js → public-CirpatD6.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-7_3ol0Z4.js → rotate-ccw-tv1JxnBS.js} +1 -1
- package/payload/server/public/assets/{tasks-BY9ajKXf.js → tasks-B8iXIZ3w.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-DmNhIJnM.js → time-entry-format-MGC9wMbZ.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-C5-GOl5l.js → useCopyFeedback-DdwMdpa-.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-UOUVnP9B.js → useSelectionMode-DyoyGg7d.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-CMi8n_8S.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-BAP3yGxs.js → useVoiceRecorder-BDtJhfh_.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +10 -10
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +219 -22
- package/payload/server/public/assets/AdminShell-CaGc8ODG.js +0 -2
- package/payload/server/public/assets/chat-BQyPAEMv.js +0 -1
- package/payload/server/public/assets/chevron-left-Cmev0R9v.js +0 -1
- package/payload/server/public/assets/data-DWxZzMfL.js +0 -1
- package/payload/server/public/assets/graph-labels-JRGrstzM.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-DgM1ergs.css +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-DF6bohQA.js → useSubAccountSwitcher-B_zj_dY-.js} +0 -0
|
@@ -65,6 +65,8 @@ either is a regression.
|
|
|
65
65
|
|
|
66
66
|
**Row title resolution.** Both the sidebar (`/sidebar-sessions`) and the manager's own row payload resolve a row's title in the same order: operator rename → Claude Code `ai-title` → first non-CLI user message → 8-char sessionId prefix. The operator-rename tier is the on-disk `<accountDir>/session-titles.json` (the manager's `UserTitleStore`), keyed by the CC sessionId — the sidebar reads that same file, so a write to the store lights up both surfaces with one write.
|
|
67
67
|
|
|
68
|
+
**Session row delete, rename, and select mode.** The sidebar Sessions row supports three operator affordances. **Rename** (kebab → Rename) opens the inline edit input with its existing title pre-selected on focus, so the first keystroke replaces the whole title rather than appending to it — on both the sidebar (`conv-name-edit`) and the `/chat` Conversations flyout (`op-conv-rename-input`). **Delete** (kebab → Delete) arms a confirm that names the session (its label plus the 8-char id prefix) and renders as a popover anchored beside the row it will delete — reusing the same `AnchoredPopover` the kebab menu uses, not the shared bottom-right info dock — so the target is unambiguous; it still states the delete is permanent and, for a live row, that the session is stopped first. **Select mode** (a "Select" control in the Sessions header) turns each row into a checkbox and shows a "Delete N" bar; the row body toggles selection instead of opening, the per-row kebab is hidden, and confirming a count-based `BulkDeleteConfirmModal` (which names N and how many are live) loops the single-id `session-delete` over the selection (no route change — `/api/admin/session-delete` stays single-id). Bulk deletion is instrumented with a `[admin-ui] sidebar-bulk-delete op=start count=<N> live=<M>` / `op=done requested=<N> ok=<X> failed=<Y>` envelope around the per-session `sidebar-session-delete` lines, so a silently-dropped item shows as `ok+failed < requested`. Outside select mode the single-delete and row-open behaviours are unchanged.
|
|
69
|
+
|
|
68
70
|
**`/chat` empty-state starter chips.** While the admin webchat has no conversation JSONL yet (`GET /api/webchat/session` → `projectDir:null`), `app/chat/page.tsx` renders a "What's up next, {givenName}?" greeting (given name = first whitespace token of the admin session's `userName`; degrades to "What's up next?" without one) above three one-tap starter chips (replacing the earlier read-only lists): **Catch me up** (`Inbox`), **My day** (`CalendarClock`), **To-do** (`ListTodo`). `GreetingPanel` no longer fetches `GET /api/webchat/greeting` — the headline reads from the `userName` prop alone. A tap dispatches the chip's verbatim prompt through the existing `send()` with zero typing, via the same arm/fire path as the `?q=` handoff seed: `dispatchChip` logs `[admin-ui] op=chip-tap id=<catch-up|my-day|to-do> chars=<n>`, sets the composer text, and arms a one-shot effect that fires `send()` once the text matches. Chips gate to `variant` admin/operator; the public surface shows the headline alone. The first journaled turn replaces the panel with the transcript. The `/api/webchat/greeting` route (`server/routes/webchat-greeting.ts`) and `specialist-roster.ts` are now unconsumed by this surface — slimming them is a separate follow-up. The full webchat architecture lives in `.docs/admin-webchat-native-channel.md`.
|
|
69
71
|
|
|
70
72
|
**`/chat` composer transport button.** The button at the end of the input shows a **microphone** while the box is empty — tap it to record a voice note. Type a character, or attach a file, and the same button becomes the **send** arrow; while the agent is replying it becomes a **stop** square. So an empty composer offers voice, a composer with something to send offers send, and a running turn offers stop — one button, three states. The microphone-device chooser (which input to record from) stays in the row of icons below the box. This is the same on the admin `/chat`, the public chat, and the maxy-lite webchat.
|
|
@@ -6,7 +6,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
6
6
|
|
|
7
7
|
1. **No app to register.** The client ID ships with the brand (`brand.json#outlookClientId`), and the installer stamps it onto the session-manager env, so there is zero operator Azure work. On a brand that ships an empty value the plugin stays inert until its vendor app exists — see `platform/plugins/outlook/references/auth.md` for the one-time-per-brand vendor provisioning runbook (a vendor step, not an operator or end-user step), including setting **Allow public client flows** to Yes.
|
|
8
8
|
2. **Per account: register the Outlook account** — in admin chat, ask the agent to "register my Outlook account". The agent runs `outlook-account-register`, which returns a short user code and the URL `microsoft.com/devicelogin` and relays both to you.
|
|
9
|
-
3. **Consent on any device** — open `microsoft.com/devicelogin` on your phone or laptop, enter the code, sign in to your Microsoft account, and consent to the requested scopes (`offline_access`, `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `Calendars.ReadWrite`, `Contacts.Read`). `Mail.Send` enables sending and `Calendars.ReadWrite` enables calendar control; a mailbox last consented on an older read-only scope set must re-register before send or any calendar write succeeds.
|
|
9
|
+
3. **Consent on any device** — open `microsoft.com/devicelogin` on your phone or laptop, enter the code, sign in to your Microsoft account, and consent to the requested scopes (`offline_access`, `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `Calendars.ReadWrite`, `Contacts.Read`). `Mail.Send` enables sending and `Calendars.ReadWrite` enables calendar control; a mailbox last consented on an older read-only scope set must re-register before send or any calendar write succeeds. Nothing else to do: the server finishes the sign-in automatically once you consent, usually within about half a minute, and the mailbox is then connected.
|
|
10
10
|
4. **Done.** Subsequent tool calls (mail, calendar, contacts) use the persisted refresh token transparently.
|
|
11
11
|
5. **Several mailboxes on one account.** Register again to add a second mailbox; it is stored alongside the first, not in place of it. Every operational tool then takes an optional `mailbox` argument — an email address or graphUserId. With one mailbox, omit it. With two or more, name it; omitting it returns a refusal listing the connected mailboxes. `outlook-mailbox-list` shows every connected mailbox. Only one registration runs at a time per account; starting another while one is live returns an in-progress signal.
|
|
12
12
|
|
|
@@ -16,8 +16,8 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
16
16
|
|------|---------|
|
|
17
17
|
| `outlook-account-register` | Start the device-code flow for this account. Returns a user code + `microsoft.com/devicelogin` URL immediately; does not block. |
|
|
18
18
|
| `outlook-account-register-poll` | Finish the device-code flow. One check per call: returns pending, registered, expired, or denied. |
|
|
19
|
-
| `outlook-mail-list` |
|
|
20
|
-
| `outlook-mail-search` | Microsoft Graph `$search` over the mailbox. Preview-only, like `outlook-mail-list`. |
|
|
19
|
+
| `outlook-mail-list` | Mail newest-first. Default top=25, folder=Inbox. `folder` takes a well-known name or alias (case/space-insensitive): Inbox, Sent Items/sent, Drafts, Deleted Items/trash, Junk Email/junk/spam, Archive, Outbox — or a raw folder id. Custom folder names are not resolved. `since`/`before` (ISO date-times) narrow by receipt date. The 250-per-page figure is not a ceiling: the reply carries `nextCursor` when older mail remains — pass it back as `cursor` to page further back. Filter by sender/subject via `outlook-mail-search`. |
|
|
20
|
+
| `outlook-mail-search` | Microsoft Graph `$search` (KQL) over the mailbox. Preview-only, like `outlook-mail-list`, and pages the same way via `nextCursor`/`cursor`. Put sender/subject/recipient/date filters in the query: `from:`, `to:`, `subject:`, `received>=`. Graph forbids `$search`+`$filter`, so a strict date window is on `outlook-mail-list`. |
|
|
21
21
|
| `outlook-mail-fetch-body` | Complete body of one message by id (`GET /me/messages/{id}?$select=body`). HTML decoded to text, no preview cap. The full-read path — list/search return Microsoft's ~255-char preview only. Returns the envelope plus the whole body. |
|
|
22
22
|
| `outlook-mail-reply` | Reply in-thread (`createReply` / `createReplyAll` + send). Threads natively via conversationId. `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file; files over Graph's ~3 MB inline limit use a chunked upload session). Body is plain text, prepended above the quote. Needs `Mail.ReadWrite` + `Mail.Send`. |
|
|
23
23
|
| `outlook-mail-delete` | Move messages to Deleted Items (`POST /me/messages/{id}/move`). Recoverable — never hard-deletes. An id already gone is counted not-moved. Needs `Mail.ReadWrite`. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "outlook",
|
|
3
|
-
"description": "Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview); outlook-mail-fetch-body: full body of one message by id; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.",
|
|
3
|
+
"description": "Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview) with a date window (list) or KQL from:/subject: filters (search) and a nextCursor for paging beyond the newest page; outlook-mail-fetch-body: full body of one message by id; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: outlook
|
|
3
|
-
description: Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview); outlook-mail-fetch-body: full body of one message by id; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.
|
|
3
|
+
description: Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview) with a date window (list) or KQL from:/subject: filters (search) and a nextCursor for paging beyond the newest page; outlook-mail-fetch-body: full body of one message by id; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.
|
|
4
4
|
tools:
|
|
5
5
|
- name: outlook-account-register
|
|
6
6
|
publicAllowlist: false
|
|
@@ -93,9 +93,9 @@ Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes. Mail is read (
|
|
|
93
93
|
|
|
94
94
|
## Capabilities
|
|
95
95
|
|
|
96
|
-
- **Register:** `outlook-account-register` — starts the OAuth device-code flow against the brand's Entra app and returns a user code + verification URL immediately. The operator opens `microsoft.com/devicelogin` on any device, enters the code, and consents. `outlook-account-register-poll`
|
|
96
|
+
- **Register:** `outlook-account-register` — starts the OAuth device-code flow against the brand's Entra app and returns a user code + verification URL immediately. The operator opens `microsoft.com/devicelogin` on any device, enters the code, and consents. The always-running server then completes registration on its own: a self-contained dispatcher (`dist/scripts/complete-registration.js`), armed on a 30 s interval, polls every live pending code to a terminal outcome, so a mailbox connects once the operator consents with no agent action. `outlook-account-register-poll` remains the manual path and the fallback, one check per call. Registering a second mailbox on the same account adds it alongside the first rather than replacing it; each mailbox holds its own encrypted tokens (AES-256-CBC) under a shared account key. One device-code flow runs at a time per account; starting another while one is live returns an `in-progress` signal. Observability: `devicecode-autopoll` per poll cycle and an independent `devicecode-stranded` audit for any pending code left un-harvested past its lifetime.
|
|
97
97
|
- **Multiple mailboxes:** one account can hold several mailboxes. Every operational tool takes an optional `mailbox` argument (email or graphUserId). With one mailbox, omit it. With two or more, name it — omitting it returns a refusal listing the connected mailboxes. `outlook-mailbox-list` shows them all.
|
|
98
|
-
- **Mail (read):** `outlook-mail-list` returns
|
|
98
|
+
- **Mail (read):** `outlook-mail-list` returns messages newest-first with an optional `since`/`before` date window, and `outlook-mail-search` runs a Graph `$search` (KQL) query where `from:`/`to:`/`subject:`/`received>=` filters live — both return Microsoft's short `bodyPreview` (~255 chars) for cheap triage and a `nextCursor` for paging beyond the 250-per-page window (the cursor is an opaque `@odata.nextLink`; pass it back as `cursor`). Graph forbids `$search`+`$filter`, so the strict date window is on the list tool and sender/subject filtering is on the search tool. `outlook-mail-fetch-body` reads the COMPLETE body of one message by id (HTML decoded to text, no preview cap) — the full-read path.
|
|
99
99
|
- **Mail (reply / delete):** `outlook-mail-reply` replies in-thread (native `conversationId` threading) with `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file — files over Graph's ~3 MB inline limit stream through a chunked upload session); the body is prepended above the quoted original. `outlook-mail-delete` moves messages to Deleted Items (recoverable — never hard-deletes). Both require `Mail.ReadWrite` (reply also needs `Mail.Send`).
|
|
100
100
|
- **Mail (OTP):** `outlook-mail-otp-extract` polls the inbox for a one-time verification code from a named sender, reading full bodies to extract it — used during service authentication.
|
|
101
101
|
- **Mail (send / draft):** `outlook-mail-send` sends via `POST /me/sendMail` (success asserted on Graph 202); `outlook-draft` creates a draft and returns its id; `outlook-draft-edit` updates an existing draft in place (Graph drafts are mutable); `outlook-draft-send` sends an existing draft by id. Recipients are to/cc/bcc arrays; body is HTML or plain text via `isHtml`. Requires `Mail.Send` + `Mail.ReadWrite`; a mailbox on the old read-only consent returns an actionable "re-register to grant send" signal.
|
package/payload/platform/plugins/outlook/mcp/dist/__tests__/complete-registration.test.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complete-registration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/complete-registration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { PendingStore } from "../auth/pending-store.js";
|
|
7
|
+
import { MailboxRegistry } from "../auth/mailbox-registry.js";
|
|
8
|
+
import { pollRegistrationToTerminal } from "../auth/complete-registration.js";
|
|
9
|
+
const mk = () => {
|
|
10
|
+
const dir = mkdtempSync(join(tmpdir(), "outlook-complete-"));
|
|
11
|
+
return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
|
|
12
|
+
};
|
|
13
|
+
function withFetch(seq) {
|
|
14
|
+
const original = globalThis.fetch;
|
|
15
|
+
let i = 0;
|
|
16
|
+
globalThis.fetch = (async () => seq[Math.min(i++, seq.length - 1)]());
|
|
17
|
+
return () => {
|
|
18
|
+
globalThis.fetch = original;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function captureStderr() {
|
|
22
|
+
const chunks = [];
|
|
23
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
24
|
+
process.stderr.write = (s) => {
|
|
25
|
+
chunks.push(String(s));
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
return { text: () => chunks.join(""), restore: () => { process.stderr.write = orig; } };
|
|
29
|
+
}
|
|
30
|
+
const json = (o, status = 200) => new Response(JSON.stringify(o), { status, headers: { "Content-Type": "application/json" } });
|
|
31
|
+
const cfg = { clientId: "cid", tenantId: "common" };
|
|
32
|
+
const pendingEntry = () => ({
|
|
33
|
+
deviceCode: "DEV",
|
|
34
|
+
scopes: ["Mail.Read"],
|
|
35
|
+
intervalSec: 5,
|
|
36
|
+
expiresAtMs: Date.now() + 900_000,
|
|
37
|
+
issuedAtMs: Date.now(),
|
|
38
|
+
});
|
|
39
|
+
const noSleep = async () => { };
|
|
40
|
+
test("polls to registered with no manual poll call and logs devicecode-autopoll", async () => {
|
|
41
|
+
const { dir, cleanup } = mk();
|
|
42
|
+
const ps = new PendingStore("acct-1", dir);
|
|
43
|
+
ps.write(pendingEntry());
|
|
44
|
+
const reg = new MailboxRegistry("acct-1", dir);
|
|
45
|
+
const restore = withFetch([
|
|
46
|
+
() => json({ error: "authorization_pending" }, 400), // one wait...
|
|
47
|
+
() => json({ access_token: "AT", refresh_token: "RT", expires_in: 3600, token_type: "Bearer", scope: "Mail.Read" }),
|
|
48
|
+
() => json({ id: "gid-1", mail: "info@ecolecltd.co.uk" }), // /me
|
|
49
|
+
]);
|
|
50
|
+
const cap = captureStderr();
|
|
51
|
+
try {
|
|
52
|
+
const res = await pollRegistrationToTerminal({ accountId: "acct-1", ...cfg, registry: reg, pendingStore: ps, sleep: noSleep });
|
|
53
|
+
cap.restore();
|
|
54
|
+
assert.equal(res.status, "registered");
|
|
55
|
+
assert.equal(ps.read(), null, "pending cleared on completion");
|
|
56
|
+
assert.equal(reg.storeFor("gid-1").read()?.accessToken, "AT");
|
|
57
|
+
assert.match(cap.text(), /devicecode-autopoll account=acct-1/);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
cap.restore();
|
|
61
|
+
restore();
|
|
62
|
+
cleanup();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
test("expired_token terminates as expired with pending cleared", async () => {
|
|
66
|
+
const { dir, cleanup } = mk();
|
|
67
|
+
const ps = new PendingStore("acct-2", dir);
|
|
68
|
+
ps.write(pendingEntry());
|
|
69
|
+
const restore = withFetch([() => json({ error: "expired_token" }, 400)]);
|
|
70
|
+
try {
|
|
71
|
+
const res = await pollRegistrationToTerminal({ accountId: "acct-2", ...cfg, registry: new MailboxRegistry("acct-2", dir), pendingStore: ps, sleep: noSleep });
|
|
72
|
+
assert.equal(res.status, "expired");
|
|
73
|
+
assert.equal(ps.census(), 0);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
restore();
|
|
77
|
+
cleanup();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
test("access_denied terminates as denied with pending cleared", async () => {
|
|
81
|
+
const { dir, cleanup } = mk();
|
|
82
|
+
const ps = new PendingStore("acct-3", dir);
|
|
83
|
+
ps.write(pendingEntry());
|
|
84
|
+
const restore = withFetch([() => json({ error: "access_denied" }, 400)]);
|
|
85
|
+
try {
|
|
86
|
+
const res = await pollRegistrationToTerminal({ accountId: "acct-3", ...cfg, registry: new MailboxRegistry("acct-3", dir), pendingStore: ps, sleep: noSleep });
|
|
87
|
+
assert.equal(res.status, "denied");
|
|
88
|
+
assert.equal(ps.census(), 0);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
restore();
|
|
92
|
+
cleanup();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
test("slow_down bumps the wait interval before the next poll", async () => {
|
|
96
|
+
const { dir, cleanup } = mk();
|
|
97
|
+
const ps = new PendingStore("acct-4", dir);
|
|
98
|
+
ps.write(pendingEntry()); // intervalSec 5
|
|
99
|
+
const sleeps = [];
|
|
100
|
+
const restore = withFetch([
|
|
101
|
+
() => json({ error: "slow_down" }, 400), // bumps stored interval 5 -> 10
|
|
102
|
+
() => json({ error: "authorization_pending" }, 400),
|
|
103
|
+
() => json({ access_token: "AT", refresh_token: "RT", expires_in: 3600, token_type: "Bearer", scope: "Mail.Read" }),
|
|
104
|
+
() => json({ id: "gid-4", mail: "a@b.com" }),
|
|
105
|
+
]);
|
|
106
|
+
try {
|
|
107
|
+
await pollRegistrationToTerminal({
|
|
108
|
+
accountId: "acct-4", ...cfg, registry: new MailboxRegistry("acct-4", dir), pendingStore: ps,
|
|
109
|
+
sleep: async (ms) => { sleeps.push(ms); },
|
|
110
|
+
});
|
|
111
|
+
assert.equal(sleeps[0], 10_000, "slow_down must bump the first wait from 5s to 10s");
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
restore();
|
|
115
|
+
cleanup();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
test("no pending entry terminates as expired without throwing", async () => {
|
|
119
|
+
const { dir, cleanup } = mk();
|
|
120
|
+
try {
|
|
121
|
+
const res = await pollRegistrationToTerminal({
|
|
122
|
+
accountId: "acct-5", ...cfg, registry: new MailboxRegistry("acct-5", dir),
|
|
123
|
+
pendingStore: new PendingStore("acct-5", dir), sleep: noSleep,
|
|
124
|
+
});
|
|
125
|
+
assert.equal(res.status, "expired");
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
cleanup();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
//# sourceMappingURL=complete-registration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complete-registration.test.js","sourceRoot":"","sources":["../../src/__tests__/complete-registration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-client-paged.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/graph-client-paged.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { TokenStore } from "../auth/token-store.js";
|
|
7
|
+
import { callGraphPaged } from "../lib/graph-client.js";
|
|
8
|
+
function configWithToken(accountId) {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "outlook-paged-"));
|
|
10
|
+
const tokenStore = new TokenStore(accountId, dir);
|
|
11
|
+
tokenStore.store("access-token", "refresh-token", 3600, { scopes: ["Mail.Read"] });
|
|
12
|
+
return { clientId: "test-client", tenantId: "common", accountId, tokenStore };
|
|
13
|
+
}
|
|
14
|
+
function mockGraph(status, bodyObj) {
|
|
15
|
+
const calls = [];
|
|
16
|
+
const original = globalThis.fetch;
|
|
17
|
+
globalThis.fetch = (async (input, init) => {
|
|
18
|
+
const req = input instanceof Request ? input : null;
|
|
19
|
+
const url = req ? req.url : typeof input === "string" ? input : input.toString();
|
|
20
|
+
if (!url.includes("graph.microsoft.com"))
|
|
21
|
+
return original(input, init);
|
|
22
|
+
calls.push(url);
|
|
23
|
+
const payload = bodyObj === null ? null : JSON.stringify(bodyObj);
|
|
24
|
+
const headers = bodyObj === null ? undefined : { "content-type": "application/json" };
|
|
25
|
+
return new Response(payload, { status, headers });
|
|
26
|
+
});
|
|
27
|
+
return { restore: () => { globalThis.fetch = original; }, calls };
|
|
28
|
+
}
|
|
29
|
+
test("callGraphPaged returns value and nextLink when present", async () => {
|
|
30
|
+
const mock = mockGraph(200, {
|
|
31
|
+
value: [{ id: "A" }, { id: "B" }],
|
|
32
|
+
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/messages?$skiptoken=XYZ",
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const page = await callGraphPaged(configWithToken("acct-p1"), (client) => client.api("/me/messages").top(2), { tool: "test" });
|
|
36
|
+
assert.deepEqual(page.value, [{ id: "A" }, { id: "B" }]);
|
|
37
|
+
assert.equal(page.nextLink, "https://graph.microsoft.com/v1.0/me/messages?$skiptoken=XYZ");
|
|
38
|
+
assert.equal(mock.calls.length, 1);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
mock.restore();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
test("callGraphPaged returns nextLink null and empty value defaults", async () => {
|
|
45
|
+
const mock = mockGraph(200, {});
|
|
46
|
+
try {
|
|
47
|
+
const page = await callGraphPaged(configWithToken("acct-p2"), (client) => client.api("/me/messages"), { tool: "test" });
|
|
48
|
+
assert.deepEqual(page.value, []);
|
|
49
|
+
assert.equal(page.nextLink, null);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
mock.restore();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=graph-client-paged.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-client-paged.test.js","sourceRoot":"","sources":["../../src/__tests__/graph-client-paged.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-list-paging.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mail-list-paging.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { TokenStore } from "../auth/token-store.js";
|
|
7
|
+
import { runMailList } from "../tools/mail-list.js";
|
|
8
|
+
function configWithToken(accountId) {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "outlook-mllist-"));
|
|
10
|
+
const tokenStore = new TokenStore(accountId, dir);
|
|
11
|
+
tokenStore.store("access-token", "refresh-token", 3600, { scopes: ["Mail.Read"] });
|
|
12
|
+
return { clientId: "test-client", tenantId: "common", accountId, tokenStore };
|
|
13
|
+
}
|
|
14
|
+
function mockGraph(bodyObj) {
|
|
15
|
+
const urls = [];
|
|
16
|
+
const original = globalThis.fetch;
|
|
17
|
+
globalThis.fetch = (async (input, init) => {
|
|
18
|
+
const req = input instanceof Request ? input : null;
|
|
19
|
+
const url = req ? req.url : typeof input === "string" ? input : input.toString();
|
|
20
|
+
if (!url.includes("graph.microsoft.com"))
|
|
21
|
+
return original(input, init);
|
|
22
|
+
urls.push(url);
|
|
23
|
+
return new Response(JSON.stringify(bodyObj), { status: 200, headers: { "content-type": "application/json" } });
|
|
24
|
+
});
|
|
25
|
+
return { restore: () => { globalThis.fetch = original; }, urls };
|
|
26
|
+
}
|
|
27
|
+
const EMPTY = { value: [] };
|
|
28
|
+
test("since builds a receivedDateTime ge filter", async () => {
|
|
29
|
+
const mock = mockGraph(EMPTY);
|
|
30
|
+
try {
|
|
31
|
+
await runMailList(configWithToken("a1"), { since: "2024-01-01" });
|
|
32
|
+
const filter = new URL(mock.urls[0]).searchParams.get("$filter");
|
|
33
|
+
assert.equal(filter, "receivedDateTime ge 2024-01-01T00:00:00.000Z");
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
mock.restore();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
test("since and before AND-combine into one filter", async () => {
|
|
40
|
+
const mock = mockGraph(EMPTY);
|
|
41
|
+
try {
|
|
42
|
+
await runMailList(configWithToken("a2"), { since: "2024-01-01", before: "2024-02-01" });
|
|
43
|
+
const filter = new URL(mock.urls[0]).searchParams.get("$filter");
|
|
44
|
+
assert.equal(filter, "receivedDateTime ge 2024-01-01T00:00:00.000Z and receivedDateTime le 2024-02-01T00:00:00.000Z");
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
mock.restore();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
test("no date args -> no $filter (unchanged behaviour)", async () => {
|
|
51
|
+
const mock = mockGraph(EMPTY);
|
|
52
|
+
try {
|
|
53
|
+
const r = await runMailList(configWithToken("a3"), {});
|
|
54
|
+
assert.equal(new URL(mock.urls[0]).searchParams.get("$filter"), null);
|
|
55
|
+
assert.equal(r.nextCursor, null);
|
|
56
|
+
assert.deepEqual(r.items, []);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
mock.restore();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
test("nextCursor surfaced from @odata.nextLink", async () => {
|
|
63
|
+
const link = "https://graph.microsoft.com/v1.0/me/mailFolders/Inbox/messages?$skiptoken=ABC";
|
|
64
|
+
const mock = mockGraph({ value: [], "@odata.nextLink": link });
|
|
65
|
+
try {
|
|
66
|
+
const r = await runMailList(configWithToken("a4"), {});
|
|
67
|
+
assert.equal(r.nextCursor, link);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
mock.restore();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
test("cursor delegates to the nextLink URL and builds no filter", async () => {
|
|
74
|
+
const cursor = "https://graph.microsoft.com/v1.0/me/mailFolders/Inbox/messages?$skiptoken=ABC";
|
|
75
|
+
const mock = mockGraph(EMPTY);
|
|
76
|
+
try {
|
|
77
|
+
await runMailList(configWithToken("a5"), { cursor, since: "2024-01-01" });
|
|
78
|
+
assert.equal(mock.urls.length, 1);
|
|
79
|
+
assert.match(mock.urls[0], /\$skiptoken=ABC/);
|
|
80
|
+
assert.equal(new URL(mock.urls[0]).searchParams.get("$filter"), null, "cursor path must not add a filter");
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
mock.restore();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
test("unparseable since throws before any Graph call", async () => {
|
|
87
|
+
const mock = mockGraph(EMPTY);
|
|
88
|
+
try {
|
|
89
|
+
await assert.rejects(runMailList(configWithToken("a6"), { since: "not-a-date" }), /not a valid date/);
|
|
90
|
+
assert.equal(mock.urls.length, 0);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
mock.restore();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
test("unparseable before throws before any Graph call", async () => {
|
|
97
|
+
const mock = mockGraph(EMPTY);
|
|
98
|
+
try {
|
|
99
|
+
await assert.rejects(runMailList(configWithToken("a7"), { before: "nope" }), /not a valid date/);
|
|
100
|
+
assert.equal(mock.urls.length, 0);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
mock.restore();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
test("cursor path ignores an invalid since (all other filter args are ignored)", async () => {
|
|
107
|
+
const cursor = "https://graph.microsoft.com/v1.0/me/mailFolders/Inbox/messages?$skiptoken=ABC";
|
|
108
|
+
const mock = mockGraph(EMPTY);
|
|
109
|
+
try {
|
|
110
|
+
// A paging call carries a cursor; a stale/garbage since must not abort it,
|
|
111
|
+
// because the nextLink already bakes in the original window.
|
|
112
|
+
await runMailList(configWithToken("a8"), { cursor, since: "not-a-date" });
|
|
113
|
+
assert.equal(mock.urls.length, 1);
|
|
114
|
+
assert.match(mock.urls[0], /\$skiptoken=ABC/);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
mock.restore();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=mail-list-paging.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-list-paging.test.js","sourceRoot":"","sources":["../../src/__tests__/mail-list-paging.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-search-paging.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mail-search-paging.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { TokenStore } from "../auth/token-store.js";
|
|
7
|
+
import { runMailSearch } from "../tools/mail-search.js";
|
|
8
|
+
function configWithToken(accountId) {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "outlook-mlsearch-"));
|
|
10
|
+
const tokenStore = new TokenStore(accountId, dir);
|
|
11
|
+
tokenStore.store("access-token", "refresh-token", 3600, { scopes: ["Mail.Read"] });
|
|
12
|
+
return { clientId: "test-client", tenantId: "common", accountId, tokenStore };
|
|
13
|
+
}
|
|
14
|
+
function mockGraph(bodyObj) {
|
|
15
|
+
const urls = [];
|
|
16
|
+
const original = globalThis.fetch;
|
|
17
|
+
globalThis.fetch = (async (input, init) => {
|
|
18
|
+
const req = input instanceof Request ? input : null;
|
|
19
|
+
const url = req ? req.url : typeof input === "string" ? input : input.toString();
|
|
20
|
+
if (!url.includes("graph.microsoft.com"))
|
|
21
|
+
return original(input, init);
|
|
22
|
+
urls.push(url);
|
|
23
|
+
return new Response(JSON.stringify(bodyObj), { status: 200, headers: { "content-type": "application/json" } });
|
|
24
|
+
});
|
|
25
|
+
return { restore: () => { globalThis.fetch = original; }, urls };
|
|
26
|
+
}
|
|
27
|
+
test("search surfaces nextCursor from @odata.nextLink", async () => {
|
|
28
|
+
const link = "https://graph.microsoft.com/v1.0/me/messages?$search=%22invoice%22&$skiptoken=ABC";
|
|
29
|
+
const mock = mockGraph({ value: [], "@odata.nextLink": link });
|
|
30
|
+
try {
|
|
31
|
+
const r = await runMailSearch(configWithToken("s1"), { query: "invoice" });
|
|
32
|
+
assert.equal(r.nextCursor, link);
|
|
33
|
+
assert.match(mock.urls[0], /\$search=/);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
mock.restore();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
test("cursor delegates to the nextLink and re-applies ConsistencyLevel", async () => {
|
|
40
|
+
const cursor = "https://graph.microsoft.com/v1.0/me/messages?$search=%22invoice%22&$skiptoken=ABC";
|
|
41
|
+
let sawConsistency = false;
|
|
42
|
+
const original = globalThis.fetch;
|
|
43
|
+
const urls = [];
|
|
44
|
+
globalThis.fetch = (async (input, init) => {
|
|
45
|
+
const req = input instanceof Request ? input : null;
|
|
46
|
+
const url = req ? req.url : typeof input === "string" ? input : input.toString();
|
|
47
|
+
if (!url.includes("graph.microsoft.com"))
|
|
48
|
+
return original(input, init);
|
|
49
|
+
urls.push(url);
|
|
50
|
+
const headerVal = req
|
|
51
|
+
? req.headers.get("ConsistencyLevel")
|
|
52
|
+
: init?.headers?.["ConsistencyLevel"];
|
|
53
|
+
if (headerVal === "eventual")
|
|
54
|
+
sawConsistency = true;
|
|
55
|
+
return new Response(JSON.stringify({ value: [] }), { status: 200, headers: { "content-type": "application/json" } });
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
await runMailSearch(configWithToken("s2"), { query: "ignored-when-cursor", cursor });
|
|
59
|
+
assert.match(urls[0], /\$skiptoken=ABC/);
|
|
60
|
+
assert.equal(sawConsistency, true, "ConsistencyLevel: eventual must be re-applied on the cursor request");
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
globalThis.fetch = original;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=mail-search-paging.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-search-paging.test.js","sourceRoot":"","sources":["../../src/__tests__/mail-search-paging.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,SAAS,eAAe,CAAC,SAAiB;IACxC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAClD,UAAU,CAAC,KAAK,CAAC,cAAc,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACnF,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAChF,CAAC;AAED,SAAS,SAAS,CAAC,OAAgB;IACjC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;IAClC,UAAU,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,KAA6B,EAAE,IAAkB,EAAE,EAAE;QAC9E,MAAM,GAAG,GAAG,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QACpD,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjF,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,qBAAqB,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAuC,EAAE,IAAI,CAAC,CAAC;QACzG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;IACjH,CAAC,CAAiB,CAAC;IACnB,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AACnE,CAAC;AAED,IAAI,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;IACjE,MAAM,IAAI,GAAG,mFAAmF,CAAC;IACjG,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAC1C,CAAC;YAAS,CAAC;QAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AAC/B,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;IAClF,MAAM,MAAM,GAAG,mFAAmF,CAAC;IACnG,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;IAClC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,UAAU,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,KAA6B,EAAE,IAAkB,EAAE,EAAE;QAC9E,MAAM,GAAG,GAAG,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QACpD,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACjF,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,qBAAqB,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAuC,EAAE,IAAI,CAAC,CAAC;QACzG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,SAAS,GAAG,GAAG;YACnB,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;YACrC,CAAC,CAAE,IAAI,EAAE,OAA8C,EAAE,CAAC,kBAAkB,CAAC,CAAC;QAChF,IAAI,SAAS,KAAK,UAAU;YAAE,cAAc,GAAG,IAAI,CAAC;QACpD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;IACvH,CAAC,CAAiB,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,CAAC;QACrF,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,qEAAqE,CAAC,CAAC;IAC5G,CAAC;YAAS,CAAC;QAAC,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC;IAAC,CAAC;AAC5C,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-scan.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pending-scan.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { PendingStore } from "../auth/pending-store.js";
|
|
7
|
+
import { listAccountsWithLivePending } from "../auth/pending-scan.js";
|
|
8
|
+
const mk = () => {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "outlook-scan-"));
|
|
10
|
+
return { dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
|
|
11
|
+
};
|
|
12
|
+
const entry = (expiresAtMs) => ({
|
|
13
|
+
deviceCode: "DEV",
|
|
14
|
+
scopes: ["Mail.Read"],
|
|
15
|
+
intervalSec: 5,
|
|
16
|
+
expiresAtMs,
|
|
17
|
+
issuedAtMs: Date.now(),
|
|
18
|
+
});
|
|
19
|
+
test("returns only accounts whose pending entry is live", () => {
|
|
20
|
+
const { dir, cleanup } = mk();
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
try {
|
|
23
|
+
new PendingStore("11111111-1111-1111-1111-111111111111", dir).write(entry(now + 900_000)); // live
|
|
24
|
+
new PendingStore("22222222-2222-2222-2222-222222222222", dir).write(entry(now - 1)); // expired
|
|
25
|
+
// 3333... has no pending file at all.
|
|
26
|
+
new PendingStore("33333333-3333-3333-3333-333333333333", dir);
|
|
27
|
+
const live = listAccountsWithLivePending(dir, now);
|
|
28
|
+
assert.deepEqual(live, ["11111111-1111-1111-1111-111111111111"]);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
cleanup();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
test("non-UUID directory names are ignored", () => {
|
|
35
|
+
const { dir, cleanup } = mk();
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
try {
|
|
38
|
+
new PendingStore("not-a-uuid", dir).write(entry(now + 900_000));
|
|
39
|
+
assert.deepEqual(listAccountsWithLivePending(dir, now), []);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
cleanup();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
test("missing accounts dir returns empty, never throws", () => {
|
|
46
|
+
assert.deepEqual(listAccountsWithLivePending(join(tmpdir(), "does-not-exist-xyz"), Date.now()), []);
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=pending-scan.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-scan.test.js","sourceRoot":"","sources":["../../src/__tests__/pending-scan.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAEtE,MAAM,EAAE,GAAG,GAAG,EAAE;IACd,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;IACzD,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/E,CAAC,CAAC;AACF,MAAM,KAAK,GAAG,CAAC,WAAmB,EAAE,EAAE,CAAC,CAAC;IACtC,UAAU,EAAE,KAAK;IACjB,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,WAAW,EAAE,CAAC;IACd,WAAW;IACX,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;CACvB,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,YAAY,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO;QAClG,IAAI,YAAY,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU;QAC/F,sCAAsC;QACtC,IAAI,YAAY,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,2BAA2B,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACnD,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,sCAAsC,CAAC,CAAC,CAAC;IACnE,CAAC;YAAS,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sCAAsC,EAAE,GAAG,EAAE;IAChD,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,YAAY,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC;QAChE,MAAM,CAAC,SAAS,CAAC,2BAA2B,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9D,CAAC;YAAS,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAC5D,MAAM,CAAC,SAAS,CAAC,2BAA2B,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AACtG,CAAC,CAAC,CAAC"}
|