@rubytech/create-maxy-code 0.1.440 → 0.1.441
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/plugins/admin/skills/platform-architecture/SKILL.md +28 -7
- package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +15 -5
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/functions/api/calendar/free-busy.ts +152 -0
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/public/index.html +6 -3
- package/payload/platform/plugins/cloudflare/skills/calendar-site/template/schema.sql +11 -0
- package/payload/platform/plugins/docs/references/calendar-booking.md +1 -1
- package/payload/platform/plugins/docs/references/graph.md +19 -3
- package/payload/platform/plugins/docs/references/outlook-guide.md +7 -2
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +7 -3
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js +100 -10
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/account-register.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js +64 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-list.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js +193 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mailbox-registry.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +58 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts +81 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js +171 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/mailbox-registry.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +16 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +25 -3
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +118 -49
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +8 -4
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +22 -3
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts +26 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js +28 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-list.js.map +1 -0
- package/payload/platform/plugins/outlook/references/auth.md +36 -8
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts +27 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js +241 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/publish-availability.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/public/assets/{graph-NgQDY0Pb.js → graph-CboWM2_-.js} +1 -1
- package/payload/server/public/graph.html +1 -1
- package/payload/server/server.js +69 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:9f7fc73400d2f9cf942e2df6f544bc69b95abe820ac1ff9b529807c3c7fd6407
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -2031,6 +2031,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2031
2031
|
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.
|
|
2032
2032
|
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. The agent finishes by calling `outlook-account-register-poll`.
|
|
2033
2033
|
4. **Done.** Subsequent tool calls (mail, calendar, contacts) use the persisted refresh token transparently.
|
|
2034
|
+
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.
|
|
2034
2035
|
|
|
2035
2036
|
## Tools
|
|
2036
2037
|
|
|
@@ -2048,7 +2049,8 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2048
2049
|
| `outlook-calendar-respond` | Accept, decline, or tentatively accept an invite. |
|
|
2049
2050
|
| `outlook-calendar-freebusy` | Availability for one or more addresses over a window. |
|
|
2050
2051
|
| `outlook-contacts-list` | Top contacts. Default top=50. |
|
|
2051
|
-
| `outlook-mailbox-info` | Health probe — auth state, refresh-window, folder count. |
|
|
2052
|
+
| `outlook-mailbox-info` | Health probe for one mailbox — auth state, refresh-window, folder count. Name the mailbox with `mailbox` when several are attached. |
|
|
2053
|
+
| `outlook-mailbox-list` | Every connected mailbox with its email, scopes, and token health. Local only — no network — so it also flags a mailbox whose stored credentials cannot be read. |
|
|
2052
2054
|
| `outlook-mail-send` | Send a message (`POST /me/sendMail`). to/cc/bcc arrays, subject, body, isHtml. Success asserted on Graph 202. Needs `Mail.Send`. |
|
|
2053
2055
|
| `outlook-draft` | Create a draft in Drafts (`POST /me/messages`). Same body model. Returns the draft id. Needs `Mail.ReadWrite`. |
|
|
2054
2056
|
| `outlook-draft-send` | Send an existing draft by id (`POST /me/messages/{id}/send`). Graph consumes the draft, returns 202. Needs `Mail.Send`. |
|
|
@@ -2066,7 +2068,9 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
2066
2068
|
| Pending-store census | `pending-store-census account=<id> live=<N>` |
|
|
2067
2069
|
| Pending cleared | `pending-cleared account=<id> verified=<bool>` |
|
|
2068
2070
|
| /me failed post-token | `me-failed account=<id> status=<N>` |
|
|
2069
|
-
| Auth ok | `auth-ok account=<id> graphUserId=<id> scopes=<csv> tokenExpSec=<N>` |
|
|
2071
|
+
| Auth ok | `auth-ok account=<id> graphUserId=<id> mail=<addr> scopes=<csv> tokenExpSec=<N>` |
|
|
2072
|
+
| Registration already in flight | `register-in-progress account=<id>` (start refused; one device-code flow at a time) |
|
|
2073
|
+
| Credential resolve | `cred-resolve account=<id> mailbox=<id-or-empty> source=arg|sole|none|refused` — names the resolved mailbox and how it was chosen; `source=refused` is the ambiguity/not-found refusal |
|
|
2070
2074
|
| Token refreshed | `token-refreshed account=<id> oldExpSec=<N> newExpSec=<N>` |
|
|
2071
2075
|
| Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
|
|
2072
2076
|
| Mail list | `mail-list op=resolve account=<id> folderIn=<name> folderResolved=<token> asRawId=<bool> count=<N> elapsedMs=<N>` (on error, `error=<msg>` replaces `count`) |
|
|
@@ -2081,6 +2085,7 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
2081
2085
|
| Scope insufficient | `scope-insufficient tool=<t> account=<id> missing=Calendars.ReadWrite` (stale read-only consent) |
|
|
2082
2086
|
| Contacts list | `contacts-list account=<id> count=<N> elapsedMs=<N>` |
|
|
2083
2087
|
| Mailbox info | `mailbox-info account=<id> tokenWithinRefreshWindow=<bool> folderCount=<N>` |
|
|
2088
|
+
| Mailbox list | `mailbox-list account=<id> count=<N> unreadable=<N>` (unreadable>0 flags an orphaned or corrupt mailbox store) |
|
|
2084
2089
|
| Graph error | `graph-error account=<id> status=<N> code=<graphErrorCode> retryAfterMs=<N-or-null>` — writes flow through the same `callGraph` loop, so a failed write logs `graph-error` too |
|
|
2085
2090
|
| On-prem rejected | `on-prem-rejected account=<id> mailServer=<host>` (terminal) |
|
|
2086
2091
|
| Scope insufficient | `scope-insufficient account=<id> tool=<t> missing=Mail.Send` (terminal — re-register to grant send) |
|
|
@@ -2292,7 +2297,7 @@ A `pendingUnreconciled` number that stays above zero across several runs means t
|
|
|
2292
2297
|
|
|
2293
2298
|
## What this does not do
|
|
2294
2299
|
|
|
2295
|
-
One meeting type only (one length, one weekly window, one buffer). No syncing to Google or iCloud calendars: your meetings in Maxy are the source of truth. No rescheduling or cancellation links, reminders, or visitor time-zone selection. The booking page shows
|
|
2300
|
+
One meeting type only (one length, one weekly window, one buffer). No syncing to Google or iCloud calendars: your meetings in Maxy are the source of truth. No rescheduling or cancellation links, reminders, or visitor time-zone selection. The booking page shows open times from a snapshot your device publishes to the edge on a short timer, so the page keeps showing availability and taking bookings even while your device is offline — a slot that gets booked drops off at once, and times that have passed are never shown. If your device stays offline a long time the open times can go stale, but they never vanish; they refresh the moment it is back online.
|
|
2296
2301
|
|
|
2297
2302
|
---
|
|
2298
2303
|
# LinkedIn Extension
|
|
@@ -3182,8 +3187,10 @@ Source: https://docs.getmaxy.com/graph.md
|
|
|
3182
3187
|
# Graph View
|
|
3183
3188
|
|
|
3184
3189
|
The **Graph** admin page (`/graph`) renders a force-directed view of your
|
|
3185
|
-
account's Neo4j subgraph.
|
|
3186
|
-
|
|
3190
|
+
account's Neo4j subgraph. Node captions are hidden on first load so the
|
|
3191
|
+
whole-graph overview stays uncluttered, and appear the moment you interact.
|
|
3192
|
+
Once shown, captions follow the zoom level so you see the most useful identity
|
|
3193
|
+
at every scale.
|
|
3187
3194
|
|
|
3188
3195
|
## Default view
|
|
3189
3196
|
|
|
@@ -3239,11 +3246,25 @@ This is an in-page overlay, so the browser tab and address bar stay put. It is
|
|
|
3239
3246
|
not the operating system's full-screen mode. The maximize button shows only on
|
|
3240
3247
|
the `/graph` page, not where the graph is embedded in another pane.
|
|
3241
3248
|
|
|
3249
|
+
## Captions hidden on first load
|
|
3250
|
+
|
|
3251
|
+
When you open `/graph` (or refresh the page) the whole graph auto-fits into the
|
|
3252
|
+
viewport, which on a busy graph would render every caption at once and overlap
|
|
3253
|
+
them into unreadable clutter. So captions start hidden. They appear on your
|
|
3254
|
+
first interaction of any kind — zoom, drag, a filter chip, a search, clicking a
|
|
3255
|
+
node — and stay visible for the rest of that visit. To get the clean
|
|
3256
|
+
caption-free overview back, reload the page.
|
|
3257
|
+
|
|
3258
|
+
This is a one-off marker tied to page load, not a zoom threshold: after the
|
|
3259
|
+
first interaction, captions follow the zoom tiers below and do not hide again
|
|
3260
|
+
until the next reload.
|
|
3261
|
+
|
|
3242
3262
|
## Conversation label tiers
|
|
3243
3263
|
|
|
3244
3264
|
Conversation nodes carry the most operator-meaningful identity in the
|
|
3245
3265
|
subgraph (the conversation name or summary, the date it started, the message
|
|
3246
|
-
count).
|
|
3266
|
+
count). Once captions are showing they render in one of three tiers, switched
|
|
3267
|
+
by canvas scale:
|
|
3247
3268
|
|
|
3248
3269
|
| Zoom | Label shape | Example |
|
|
3249
3270
|
|------|-------------|---------|
|
|
@@ -24,7 +24,11 @@ A booking visitor and the admin look at the booking from opposite sides — the
|
|
|
24
24
|
| Admin calendar (4 views) | the install, at `/calendar` on the admin host (resolve via `admin-hostname`) | admin sign-in | every time-bound item for the account — booked and imported meetings, agent-scheduled appointments, and tasks with a due date |
|
|
25
25
|
| Public booking page | Cloudflare Pages, on the booking domain | open | free/busy only, never any meeting detail |
|
|
26
26
|
|
|
27
|
-
Only the public page is deployed here, because it must stay reachable and keep capturing bookings even when the install device is
|
|
27
|
+
Only the public page is deployed here, because it must stay reachable and keep capturing bookings even when the install device is offline. The admin calendar is dynamic, account-scoped, and authenticated, so it is served by the install over the tunnel and is same-origin with its data. Do not try to put the admin calendar on Pages.
|
|
28
|
+
|
|
29
|
+
## Availability survives the device going offline
|
|
30
|
+
|
|
31
|
+
The booking page does not fetch open times from the device. It reads them same-origin from its own edge Function, `functions/api/calendar/free-busy.ts`, which serves a snapshot stored in the D1 `availability_snapshot` table minus any slot already booked (so a taken slot disappears at once, and two visitors never see the same slot) and with past slots filtered out (so a stale snapshot never offers an expired time). The snapshot is kept fresh by a publish job the platform server arms on a short timer: while the device is online it pushes the account's computed free/busy into `availability_snapshot`; while the device is off it simply does not run and the edge keeps serving the last snapshot until the device returns. There is no per-account cron or setup; the server arms it for the account it resolves, which is the one account whose booking site this install serves.
|
|
28
32
|
|
|
29
33
|
## Set the availability window first
|
|
30
34
|
|
|
@@ -34,7 +38,7 @@ The public page shows open slots computed as the weekly window minus busy `:Meet
|
|
|
34
38
|
<accountDir>/calendar-availability.json
|
|
35
39
|
```
|
|
36
40
|
|
|
37
|
-
Collect from the operator, then write the file: the account `timezone` (IANA), the meeting `durationMins`, the `bufferMins` between meetings, and the `weekly` window as `{ mon: [["09:00","17:00"]], ... }` with an empty array for days that are closed. Also collect `daysAhead` (the booking horizon — a positive integer count of days the page lets visitors book into, e.g. `14`) and `allowMultiSlot` (a boolean: may a visitor combine consecutive open slots into one longer back-to-back booking? default `false`). Add the `
|
|
41
|
+
Collect from the operator, then write the file: the account `timezone` (IANA), the meeting `durationMins`, the `bufferMins` between meetings, and the `weekly` window as `{ mon: [["09:00","17:00"]], ... }` with an empty array for days that are closed. Also collect `daysAhead` (the booking horizon — a positive integer count of days the page lets visitors book into, e.g. `14`) and `allowMultiSlot` (a boolean: may a visitor combine consecutive open slots into one longer back-to-back booking? default `false`). Add the `bookingDomain` (the custom domain the page will answer on) and `bookingDbName` (the D1 database name chosen below). Re-running this skill with new numbers is how availability — including the horizon and the multi-slot choice — is changed later; there is no separate editor. The device-side job that keeps the edge snapshot fresh is armed by the always-running platform server, so there is nothing per-account to schedule.
|
|
38
42
|
|
|
39
43
|
## Assemble the canonical tree
|
|
40
44
|
|
|
@@ -44,11 +48,11 @@ Copy the skill template into the one canonical Pages source path `site-deploy` e
|
|
|
44
48
|
<accountDir>/pages/<project>/
|
|
45
49
|
```
|
|
46
50
|
|
|
47
|
-
The template ships `public/index.html`, `public/booking.js`, `public/booking.css`, `functions/api/book.ts`, `wrangler.toml`, and `schema.sql`. The substitutions that make it concrete are: in `public/index.html`, set
|
|
51
|
+
The template ships `public/index.html`, `public/booking.js`, `public/booking.css`, `functions/api/book.ts`, `functions/api/calendar/free-busy.ts`, `wrangler.toml`, and `schema.sql`. The substitutions that make it concrete are: in `public/index.html`, set `<meta name="days-ahead">` content to `daysAhead` and set `<meta name="allow-multi-slot">` content to `allowMultiSlot` as the literal `true` or `false`; and fill `wrangler.toml`'s `__PROJECT_NAME__`, `__D1_DATABASE_NAME__`, and `__D1_DATABASE_ID__` from the project name and the database created below. Leave `<meta name="api-base">` empty as it ships; the page reads availability same-origin from the site's own `free-busy` edge Function (see the snapshot section below), never the device tunnel. Leave no `__...__` placeholder in the assembled tree.
|
|
48
52
|
|
|
49
53
|
## Provision the D1 bookings table
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
The booking-specific schema ships with the template: `schema.sql` creates both the `bookings` table (written by the booking page) and the `availability_snapshot` table (the single-row free/busy cache the publish job upserts and the `free-busy` edge Function reads). Applying `schema.sql` provisions both. Load the stable `<brand>-pages-d1` token from the account secrets file, minting it once only if absent and persisting it, exactly as `references/api.md` and `references/d1-data-capture.md` prescribe; the token needs both Pages Edit and D1 Edit. Create the database, record its id into `wrangler.toml`, and apply `schema.sql` to the remote database. The token is never written into the tree, never committed, never echoed.
|
|
52
56
|
|
|
53
57
|
## Deploy through site-deploy
|
|
54
58
|
|
|
@@ -59,7 +63,13 @@ Hand the assembled tree to `cloudflare:site-deploy`: it builds nothing for a sta
|
|
|
59
63
|
Done is a live behavioural signal on the booking domain, surfaced verbatim:
|
|
60
64
|
|
|
61
65
|
- The booking domain returns `HTTP/2 200` on a cache-busted request.
|
|
62
|
-
- The page's free/busy call succeeds: `curl` the
|
|
66
|
+
- The page's free/busy call succeeds same-origin: `curl` the booking domain's own `free-busy` edge Function and confirm it returns a `slots` array (with `updatedAt`) and carries no meeting title, description, or location field. Immediately after deploy it may return `{"slots":[],"updatedAt":null,"stale":true}` until the platform server's publish job runs once (within ~5 minutes); re-check until `updatedAt` is fresh and `slots` is populated. A response missing the `slots` field means the edge Function did not deploy; empty-with-`stale` after several minutes means the publish job is not reaching D1 (check the brand journal for `[calendar-availability]`).
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
curl -s "https://<booking-domain>/api/calendar/free-busy?from=$(date -u +%Y-%m-%dT00:00:00Z)&to=$(date -u -v+14d +%Y-%m-%dT00:00:00Z)" | head -c 400
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The device's own free/busy (the publish job's source) should also return a `slots` array; a `404` there means the availability window was not written:
|
|
63
73
|
|
|
64
74
|
```bash
|
|
65
75
|
curl -s "https://<brand-tunnel-domain>/api/calendar/free-busy?from=$(date -u +%Y-%m-%dT00:00:00Z)&to=$(date -u -v+14d +%Y-%m-%dT00:00:00Z)" | head -c 400
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Edge free/busy Function (calendar-site skill) — offline-resilient availability.
|
|
2
|
+
//
|
|
3
|
+
// Served at GET /api/calendar/free-busy on the booking domain, same-origin with
|
|
4
|
+
// the page. It serves open times from a snapshot the device publishes into D1
|
|
5
|
+
// on a short timer (platform/ui server → publish-availability), MINUS any slot
|
|
6
|
+
// already booked in the D1 `bookings` table:
|
|
7
|
+
// - Serving the snapshot means the public page keeps showing availability
|
|
8
|
+
// while the install device is offline (the device's own tunnel free/busy is
|
|
9
|
+
// then unreachable, but the snapshot survives on the edge).
|
|
10
|
+
// - Subtracting accepted bookings here — at the edge — means a booked slot
|
|
11
|
+
// disappears the instant it is taken, with no wait for the device to
|
|
12
|
+
// reconcile and republish, so two visitors are never shown the same slot.
|
|
13
|
+
// - Past slots are always filtered out, so a stale snapshot never offers a
|
|
14
|
+
// time that has already passed.
|
|
15
|
+
//
|
|
16
|
+
// Split like book.ts so the logic is verifiable without the Pages runtime:
|
|
17
|
+
// computeFreeBusy takes an injected DB + logger and returns the HTTP shape.
|
|
18
|
+
|
|
19
|
+
interface D1PreparedStatement {
|
|
20
|
+
bind: (...values: unknown[]) => D1PreparedStatement
|
|
21
|
+
first: <T = Record<string, unknown>>() => Promise<T | null>
|
|
22
|
+
all: <T = Record<string, unknown>>() => Promise<{ results?: T[] }>
|
|
23
|
+
}
|
|
24
|
+
interface D1Database {
|
|
25
|
+
prepare: (query: string) => D1PreparedStatement
|
|
26
|
+
}
|
|
27
|
+
interface FreeBusyEnv {
|
|
28
|
+
DB: D1Database
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Slot {
|
|
32
|
+
start: string
|
|
33
|
+
end: string
|
|
34
|
+
}
|
|
35
|
+
interface Snapshot {
|
|
36
|
+
durationMins?: number
|
|
37
|
+
timezone?: string
|
|
38
|
+
slots?: Slot[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Logger = (line: string) => void
|
|
42
|
+
|
|
43
|
+
interface FreeBusyQuery {
|
|
44
|
+
from?: string | null
|
|
45
|
+
to?: string | null
|
|
46
|
+
nowMs: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Default horizon when `to` is absent — a generous ceiling so a snapshot always
|
|
50
|
+
// covers the page's own (operator-set) horizon.
|
|
51
|
+
const DEFAULT_HORIZON_MS = 31 * 24 * 3600 * 1000
|
|
52
|
+
|
|
53
|
+
export interface FreeBusyResult {
|
|
54
|
+
durationMins: number | null
|
|
55
|
+
timezone: string | null
|
|
56
|
+
slots: Slot[]
|
|
57
|
+
updatedAt: string | null
|
|
58
|
+
stale?: boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function computeFreeBusy(
|
|
62
|
+
env: FreeBusyEnv,
|
|
63
|
+
query: FreeBusyQuery,
|
|
64
|
+
log: Logger,
|
|
65
|
+
): Promise<FreeBusyResult> {
|
|
66
|
+
const { nowMs } = query
|
|
67
|
+
const from = query.from ? Date.parse(query.from) : nowMs
|
|
68
|
+
const to = query.to ? Date.parse(query.to) : nowMs + DEFAULT_HORIZON_MS
|
|
69
|
+
|
|
70
|
+
let row: { payload?: string; updatedAt?: string } | null = null
|
|
71
|
+
try {
|
|
72
|
+
row = await env.DB
|
|
73
|
+
.prepare('SELECT payload, updatedAt FROM availability_snapshot WHERE id = 1')
|
|
74
|
+
.first<{ payload?: string; updatedAt?: string }>()
|
|
75
|
+
} catch {
|
|
76
|
+
row = null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// No snapshot published yet (first deploy) is distinct from a corrupt payload
|
|
80
|
+
// below — both otherwise collapse to empty slots, so name them apart in the log.
|
|
81
|
+
if (!row || !row.payload) {
|
|
82
|
+
log('[calendar-availability] op=serve result=no-snapshot slots=0')
|
|
83
|
+
return { durationMins: null, timezone: null, slots: [], updatedAt: null, stale: true }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let snap: Snapshot
|
|
87
|
+
try {
|
|
88
|
+
snap = JSON.parse(row.payload) as Snapshot
|
|
89
|
+
} catch {
|
|
90
|
+
log('[calendar-availability] op=serve result=corrupt slots=0')
|
|
91
|
+
return { durationMins: null, timezone: null, slots: [], updatedAt: row.updatedAt ?? null, stale: true }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const lower = Math.max(Number.isNaN(from) ? nowMs : from, nowMs)
|
|
95
|
+
const upper = Number.isNaN(to) ? nowMs + DEFAULT_HORIZON_MS : to
|
|
96
|
+
|
|
97
|
+
// Slots already taken (optimistic subtraction). Bound the scan to bookings at
|
|
98
|
+
// or after `lower`: reconcile only marks rows swept, never de-accepts them, so
|
|
99
|
+
// without this the accepted set grows without bound over the calendar's life —
|
|
100
|
+
// and a past booking can never match a snapshot slot that survives the
|
|
101
|
+
// start >= lower filter below, so the bound drops nothing. A failed read
|
|
102
|
+
// serves the snapshot as-is rather than 500 — the booking page still works.
|
|
103
|
+
const booked = new Set<string>()
|
|
104
|
+
try {
|
|
105
|
+
const res = await env.DB
|
|
106
|
+
.prepare("SELECT slotStart FROM bookings WHERE status = 'accepted' AND slotStart >= ?1")
|
|
107
|
+
.bind(new Date(lower).toISOString())
|
|
108
|
+
.all<{ slotStart?: string }>()
|
|
109
|
+
for (const b of res.results || []) {
|
|
110
|
+
if (b && typeof b.slotStart === 'string') booked.add(b.slotStart)
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
/* keep the snapshot as-is */
|
|
114
|
+
}
|
|
115
|
+
const slots = (snap.slots || []).filter((s) => {
|
|
116
|
+
const t = Date.parse(s.start)
|
|
117
|
+
return !Number.isNaN(t) && t >= lower && t < upper && !booked.has(s.start)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const ageMs = row.updatedAt ? nowMs - Date.parse(row.updatedAt) : null
|
|
121
|
+
log(`[calendar-availability] op=serve result=ok slots=${slots.length} ageMs=${ageMs ?? 'na'}`)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
durationMins: snap.durationMins ?? null,
|
|
125
|
+
timezone: snap.timezone ?? null,
|
|
126
|
+
slots,
|
|
127
|
+
updatedAt: row.updatedAt ?? null,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface PagesContext {
|
|
132
|
+
request: Request
|
|
133
|
+
env: FreeBusyEnv
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function onRequestGet(context: PagesContext): Promise<Response> {
|
|
137
|
+
const url = new URL(context.request.url)
|
|
138
|
+
const result = await computeFreeBusy(
|
|
139
|
+
context.env,
|
|
140
|
+
{
|
|
141
|
+
from: url.searchParams.get('from'),
|
|
142
|
+
to: url.searchParams.get('to'),
|
|
143
|
+
nowMs: Date.now(),
|
|
144
|
+
},
|
|
145
|
+
(line) => console.log(line),
|
|
146
|
+
)
|
|
147
|
+
// Free/busy carries no credentials; a wildcard origin keeps it readable if a
|
|
148
|
+
// page ever fetches it cross-origin, matching the device endpoint's CORS.
|
|
149
|
+
return Response.json(result, {
|
|
150
|
+
headers: { 'Access-Control-Allow-Origin': '*' },
|
|
151
|
+
})
|
|
152
|
+
}
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<!--
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
<!-- Availability is read same-origin from this site's own edge Function
|
|
7
|
+
(functions/api/calendar/free-busy.ts), which serves the snapshot the
|
|
8
|
+
device publishes to D1. Empty = same-origin, so the page keeps working
|
|
9
|
+
when the install device is offline. Left blank deliberately; do not point
|
|
10
|
+
it at the device tunnel. -->
|
|
11
|
+
<meta name="api-base" content="" />
|
|
9
12
|
<!-- Operator-set booking horizon (positive integer days). The skill rewrites
|
|
10
13
|
the content; booking.js falls back to 14 when it is left unsubstituted. -->
|
|
11
14
|
<meta name="days-ahead" content="__DAYS_AHEAD__" />
|
|
@@ -16,3 +16,14 @@ CREATE TABLE IF NOT EXISTS bookings (
|
|
|
16
16
|
);
|
|
17
17
|
|
|
18
18
|
CREATE INDEX IF NOT EXISTS bookings_unswept ON bookings (status, swept);
|
|
19
|
+
|
|
20
|
+
-- Single-row cache of the account's computed free/busy, published by the device
|
|
21
|
+
-- on a short timer (platform/ui server → publish-availability). The booking
|
|
22
|
+
-- page's edge free/busy Function serves open times from this snapshot minus any
|
|
23
|
+
-- accepted booking, so the public page keeps showing availability while the
|
|
24
|
+
-- device is offline. id is pinned to 1: one account per booking site, one row.
|
|
25
|
+
CREATE TABLE IF NOT EXISTS availability_snapshot (
|
|
26
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
27
|
+
payload TEXT,
|
|
28
|
+
updatedAt TEXT
|
|
29
|
+
);
|
|
@@ -55,4 +55,4 @@ A `pendingUnreconciled` number that stays above zero across several runs means t
|
|
|
55
55
|
|
|
56
56
|
## What this does not do
|
|
57
57
|
|
|
58
|
-
One meeting type only (one length, one weekly window, one buffer). No syncing to Google or iCloud calendars: your meetings in Maxy are the source of truth. No rescheduling or cancellation links, reminders, or visitor time-zone selection. The booking page shows
|
|
58
|
+
One meeting type only (one length, one weekly window, one buffer). No syncing to Google or iCloud calendars: your meetings in Maxy are the source of truth. No rescheduling or cancellation links, reminders, or visitor time-zone selection. The booking page shows open times from a snapshot your device publishes to the edge on a short timer, so the page keeps showing availability and taking bookings even while your device is offline — a slot that gets booked drops off at once, and times that have passed are never shown. If your device stays offline a long time the open times can go stale, but they never vanish; they refresh the moment it is back online.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Graph View
|
|
2
2
|
|
|
3
3
|
The **Graph** admin page (`/graph`) renders a force-directed view of your
|
|
4
|
-
account's Neo4j subgraph.
|
|
5
|
-
|
|
4
|
+
account's Neo4j subgraph. Node captions are hidden on first load so the
|
|
5
|
+
whole-graph overview stays uncluttered, and appear the moment you interact.
|
|
6
|
+
Once shown, captions follow the zoom level so you see the most useful identity
|
|
7
|
+
at every scale.
|
|
6
8
|
|
|
7
9
|
## Default view
|
|
8
10
|
|
|
@@ -58,11 +60,25 @@ This is an in-page overlay, so the browser tab and address bar stay put. It is
|
|
|
58
60
|
not the operating system's full-screen mode. The maximize button shows only on
|
|
59
61
|
the `/graph` page, not where the graph is embedded in another pane.
|
|
60
62
|
|
|
63
|
+
## Captions hidden on first load
|
|
64
|
+
|
|
65
|
+
When you open `/graph` (or refresh the page) the whole graph auto-fits into the
|
|
66
|
+
viewport, which on a busy graph would render every caption at once and overlap
|
|
67
|
+
them into unreadable clutter. So captions start hidden. They appear on your
|
|
68
|
+
first interaction of any kind — zoom, drag, a filter chip, a search, clicking a
|
|
69
|
+
node — and stay visible for the rest of that visit. To get the clean
|
|
70
|
+
caption-free overview back, reload the page.
|
|
71
|
+
|
|
72
|
+
This is a one-off marker tied to page load, not a zoom threshold: after the
|
|
73
|
+
first interaction, captions follow the zoom tiers below and do not hide again
|
|
74
|
+
until the next reload.
|
|
75
|
+
|
|
61
76
|
## Conversation label tiers
|
|
62
77
|
|
|
63
78
|
Conversation nodes carry the most operator-meaningful identity in the
|
|
64
79
|
subgraph (the conversation name or summary, the date it started, the message
|
|
65
|
-
count).
|
|
80
|
+
count). Once captions are showing they render in one of three tiers, switched
|
|
81
|
+
by canvas scale:
|
|
66
82
|
|
|
67
83
|
| Zoom | Label shape | Example |
|
|
68
84
|
|------|-------------|---------|
|
|
@@ -8,6 +8,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
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
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. The agent finishes by calling `outlook-account-register-poll`.
|
|
10
10
|
4. **Done.** Subsequent tool calls (mail, calendar, contacts) use the persisted refresh token transparently.
|
|
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.
|
|
11
12
|
|
|
12
13
|
## Tools
|
|
13
14
|
|
|
@@ -25,7 +26,8 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
25
26
|
| `outlook-calendar-respond` | Accept, decline, or tentatively accept an invite. |
|
|
26
27
|
| `outlook-calendar-freebusy` | Availability for one or more addresses over a window. |
|
|
27
28
|
| `outlook-contacts-list` | Top contacts. Default top=50. |
|
|
28
|
-
| `outlook-mailbox-info` | Health probe — auth state, refresh-window, folder count. |
|
|
29
|
+
| `outlook-mailbox-info` | Health probe for one mailbox — auth state, refresh-window, folder count. Name the mailbox with `mailbox` when several are attached. |
|
|
30
|
+
| `outlook-mailbox-list` | Every connected mailbox with its email, scopes, and token health. Local only — no network — so it also flags a mailbox whose stored credentials cannot be read. |
|
|
29
31
|
| `outlook-mail-send` | Send a message (`POST /me/sendMail`). to/cc/bcc arrays, subject, body, isHtml. Success asserted on Graph 202. Needs `Mail.Send`. |
|
|
30
32
|
| `outlook-draft` | Create a draft in Drafts (`POST /me/messages`). Same body model. Returns the draft id. Needs `Mail.ReadWrite`. |
|
|
31
33
|
| `outlook-draft-send` | Send an existing draft by id (`POST /me/messages/{id}/send`). Graph consumes the draft, returns 202. Needs `Mail.Send`. |
|
|
@@ -43,7 +45,9 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
43
45
|
| Pending-store census | `pending-store-census account=<id> live=<N>` |
|
|
44
46
|
| Pending cleared | `pending-cleared account=<id> verified=<bool>` |
|
|
45
47
|
| /me failed post-token | `me-failed account=<id> status=<N>` |
|
|
46
|
-
| Auth ok | `auth-ok account=<id> graphUserId=<id> scopes=<csv> tokenExpSec=<N>` |
|
|
48
|
+
| Auth ok | `auth-ok account=<id> graphUserId=<id> mail=<addr> scopes=<csv> tokenExpSec=<N>` |
|
|
49
|
+
| Registration already in flight | `register-in-progress account=<id>` (start refused; one device-code flow at a time) |
|
|
50
|
+
| Credential resolve | `cred-resolve account=<id> mailbox=<id-or-empty> source=arg|sole|none|refused` — names the resolved mailbox and how it was chosen; `source=refused` is the ambiguity/not-found refusal |
|
|
47
51
|
| Token refreshed | `token-refreshed account=<id> oldExpSec=<N> newExpSec=<N>` |
|
|
48
52
|
| Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
|
|
49
53
|
| Mail list | `mail-list op=resolve account=<id> folderIn=<name> folderResolved=<token> asRawId=<bool> count=<N> elapsedMs=<N>` (on error, `error=<msg>` replaces `count`) |
|
|
@@ -58,6 +62,7 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
58
62
|
| Scope insufficient | `scope-insufficient tool=<t> account=<id> missing=Calendars.ReadWrite` (stale read-only consent) |
|
|
59
63
|
| Contacts list | `contacts-list account=<id> count=<N> elapsedMs=<N>` |
|
|
60
64
|
| Mailbox info | `mailbox-info account=<id> tokenWithinRefreshWindow=<bool> folderCount=<N>` |
|
|
65
|
+
| Mailbox list | `mailbox-list account=<id> count=<N> unreadable=<N>` (unreadable>0 flags an orphaned or corrupt mailbox store) |
|
|
61
66
|
| Graph error | `graph-error account=<id> status=<N> code=<graphErrorCode> retryAfterMs=<N-or-null>` — writes flow through the same `callGraph` loop, so a failed write logs `graph-error` too |
|
|
62
67
|
| On-prem rejected | `on-prem-rejected account=<id> mailServer=<host>` (terminal) |
|
|
63
68
|
| Scope insufficient | `scope-insufficient account=<id> tool=<t> missing=Mail.Send` (terminal — re-register to grant send) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "outlook",
|
|
3
|
-
"description": "Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read + draft + send; contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox; outlook-mail-send / outlook-draft / outlook-draft-send: compose 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.",
|
|
3
|
+
"description": "Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read + draft + 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; outlook-mail-send / outlook-draft / outlook-draft-send: compose 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 + draft + send; contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox; outlook-mail-send / outlook-draft / outlook-draft-send: compose 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.
|
|
3
|
+
description: Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read + draft + 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; outlook-mail-send / outlook-draft / outlook-draft-send: compose 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
|
|
@@ -50,6 +50,9 @@ tools:
|
|
|
50
50
|
- name: outlook-mailbox-info
|
|
51
51
|
publicAllowlist: false
|
|
52
52
|
adminAllowlist: false
|
|
53
|
+
- name: outlook-mailbox-list
|
|
54
|
+
publicAllowlist: false
|
|
55
|
+
adminAllowlist: false
|
|
53
56
|
always: false
|
|
54
57
|
embed: ["admin"]
|
|
55
58
|
metadata: {"platform":{"optional":true}}
|
|
@@ -75,12 +78,13 @@ Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes. Mail is read,
|
|
|
75
78
|
|
|
76
79
|
## Capabilities
|
|
77
80
|
|
|
78
|
-
- **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` completes registration — one check per call.
|
|
81
|
+
- **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` completes registration — 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.
|
|
82
|
+
- **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.
|
|
79
83
|
- **Mail (read):** `outlook-mail-list` returns recent messages; `outlook-mail-search` runs a Graph `$search` query.
|
|
80
84
|
- **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-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.
|
|
81
85
|
- **Calendar (read + control):** `outlook-calendar-list` and `outlook-calendar-event` read events; `outlook-calendar-create` books an event; `outlook-calendar-update` reschedules or edits one; `outlook-calendar-cancel` cancels a meeting (notifying attendees when the account organises it) or deletes a personal appointment; `outlook-calendar-respond` accepts, declines, or tentatively accepts an invite; `outlook-calendar-freebusy` reports availability for one or more addresses over a window. Writes require the `Calendars.ReadWrite` consent; a mailbox on stale read-only consent returns a distinct signal naming the scope and `outlook-account-register`.
|
|
82
86
|
- **Contacts (read-only):** `outlook-contacts-list` returns contacts.
|
|
83
|
-
- **Health:** `outlook-mailbox-info` reports auth state, refresh-window status, and top-level folder count. Use
|
|
87
|
+
- **Health:** `outlook-mailbox-info` reports auth state, refresh-window status, and top-level folder count for one mailbox (name it with `mailbox` when several are attached). `outlook-mailbox-list` lists every connected mailbox with its email, scopes, and token health, reading local stores only — no network — so it also flags a mailbox whose stored credentials cannot be read. Use these to answer "did mailbox X auth?" without grepping logs.
|
|
84
88
|
|
|
85
89
|
## Out of scope
|
|
86
90
|
|