You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Proposing skills/productivity/outlook/ — a Microsoft 365 Calendar + To Do skill via the Graph v1.0 API, structured as the Microsoft counterpart to the existing skills/productivity/google-workspace/. I have a working implementation in production use and am willing to do the upstream generalization work if maintainers are interested.
Why this fits the bundled productivity tree
Direct precedent: google-workspace is bundled today and covers Gmail/Calendar/Drive/Docs against the Google ecosystem. The Microsoft 365 ecosystem is at least equally widespread (personal Outlook.com accounts plus the Microsoft 365 Family/Business tenants) and currently has no first-class Hermes skill.
Gap in the platforms tree: gateway/platforms/ ships email/discord/whatsapp/telegram/slack/etc., but no Graph-API client for Outlook calendar or Microsoft To Do.
Complementary, not overlapping, with #20133 (hermes-a365): that proposal is the M365 Copilot / Agent 365 governance + Bot Framework bridge path, requires Tier‑3 or E7 licensing, .NET tooling, BF webhook adapter, and targets enterprise-tenant operators. This skill is the personal / family / SMB path: plain Graph v1.0, refresh-token OAuth, no .NET, no Bot Framework, no paid tier required beyond a regular MS account.
What's built
22 subcommands, single-file Python (~1.1k LOC) using stdlib + requests (already in the Hermes venv, no extra deps), structured along the same lines as google-workspace/scripts/google_api.py:
Calendar — calendars, events [--days N --past N --all-calendars --calendar X], search-events --query X (cross-calendar subject search via $filter contains() since Graph rejects $search on /me/events), event-detail, create-event (with --all-day + full recurrence: daily/weekly/monthly/yearly + --recurrence-days/--recurrence-until/--recurrence-count), update-event (PATCH with seriesMaster + attendee warnings), delete-event (loud warning when deleting a seriesMaster would wipe a whole recurring series).
To Do — lists, create-list, rename-list, delete-list (with wellknownListName protection for built-in defaultList/flaggedEmails), tasks [--list X --all], create-task (with the same recurrence flags), update-task, complete-task, delete-task.
Subtasks (Graph checklistItems, shown as "Steps" in the Outlook clients) — steps, add-step, complete-step, delete-step.
Ops — status (graph user, default calendar, token freshness, scope), audit-log [--tail N --op NAME] (append-only JSON-lines log of every write, useful for "what did the agent do last Tuesday").
Architecture notes
Auth: raw refresh-token flow against https://login.microsoftonline.com/common/oauth2/v2.0/token. No MSAL dep needed — keeps the install footprint small versus pulling in msgraph-sdk-python. Tokens persisted in .credentials/credentials.json (chmod 600), atomic-rename on rotation.
Idempotency: every create-event POST includes a content-hash transactionId (SHA‑256 of subject + start + end + calendar-id, truncated to Graph's 40-char limit). A client retry inside the 6-hour dedup window returns the same event id instead of creating a duplicate. Verified live: two identical POSTs produced the identical id; same content after a delete-event produced a fresh id (no tombstone block).
Validation upfront: refuses TZ-aware ISO strings on --start/--end/--due (passing 2026-12-15T18:00:00+02:00 together with a separate timeZone: "Europe/Berlin" header is ambiguous — the caller's +02:00 assumption from summer is wrong in winter). Also refuses end <= start and negative --days/--past.
Time-zone handling: configurable via OUTLOOK_TZ env var (default Europe/Berlin), passed as Prefer: outlook.timezone="…" header on every Graph call. Recurrence-time-zone is left for Graph to derive server-side (it maps IANA → Windows TZ name automatically).
Multi-calendar: events --all-calendars aggregates via /me/calendarView across owned calendars. search-events iterates /me/calendars/{id}/events per calendar because Graph rejects $search on /me/events with 501.
Channel-agnostic by design: the script knows nothing about who's invoking it (Telegram vs. Discord vs. WhatsApp vs. cron). All channel-access rules live one layer up, in the agent's system-prompt configuration. Keeps the skill itself privacy-safe and reusable across deployments.
Open questions for maintainers
Bundled or optional? Personal Outlook accounts are extremely common, but Azure-AD-app registration is a real setup hurdle (similar to Google's OAuth-client-secret download). skills/productivity/outlook/ vs. optional-skills/communication/outlook/ — which fits your "broadly useful vs. needs heavyweight setup" judgement better?
Naming: outlook (consumer-facing), microsoft-365, or m365-calendar (narrower scope claim)? The skill covers Calendar + To Do today, not Mail/Drive/Teams, so a narrower name might be more honest.
Setup script convention: google-workspace/scripts/setup.py walks the user through downloading client_secret.json from Google Cloud Console and runs a local-redirect OAuth flow. I'd mirror that pattern for Microsoft — Azure-AD-app registration → client_id.json download → device-code OR local-redirect flow → credentials.json written. Any preferences on flow choice?
hermes auth add microsoft as core CLI? Currently the skill manages its own OAuth file. Hermes core already has hermes auth add anthropic etc. — would it make sense to lift Microsoft OAuth into the same place so multiple Microsoft-related skills (this one, any future Mail-via-Graph skill, etc.) share one token? Or leave per-skill credentials as google-workspace does?
Scope set: today the skill requests Calendars.ReadWrite + .Shared, Tasks.ReadWrite + .Shared, User.Read. Open to making this configurable at setup time (read-only profile, no .Shared, etc.) — let me know if you have a preferred scope-selection UX.
SKILL.md sizing: I have a ~19 KB SKILL.md locally that I'd trim to ≤10 KB (matching google-workspace) for upstream, moving the long-form examples into references/ per the existing pattern. Any other formatting expectations beyond what CONTRIBUTING.md covers?
Effort estimate I'm willing to put in
~7–10 h to generalise: shrink SKILL.md, split docs into references/, write the OAuth setup.py, strip deployment-specific defaults, translate inline examples to English, full re-test against a fresh tenant.
I've already done four internal audit passes (idempotency, multi-calendar aggregation, audit-log forensics, recurrence cross-DST, etc.) and have ~80 live API calls of verification behind the current implementation.
If the response is "yes, send a PR", I'll prepare a draft against main and link it back here. If the response is "not bundled, but optional fits", I'll target optional-skills/communication/ instead. If neither is a fit, I'll publish as a community skill discoverable via the skills hub + Discord and link back to that.
Happy to share the current script via a gist or draft PR for pre-review if that's useful before doing the generalization work.
Summary
Proposing
skills/productivity/outlook/— a Microsoft 365 Calendar + To Do skill via the Graph v1.0 API, structured as the Microsoft counterpart to the existingskills/productivity/google-workspace/. I have a working implementation in production use and am willing to do the upstream generalization work if maintainers are interested.Why this fits the bundled productivity tree
google-workspaceis bundled today and covers Gmail/Calendar/Drive/Docs against the Google ecosystem. The Microsoft 365 ecosystem is at least equally widespread (personal Outlook.com accounts plus the Microsoft 365 Family/Business tenants) and currently has no first-class Hermes skill.gateway/platforms/ships email/discord/whatsapp/telegram/slack/etc., but no Graph-API client for Outlook calendar or Microsoft To Do.What's built
22 subcommands, single-file Python (~1.1k LOC) using stdlib +
requests(already in the Hermes venv, no extra deps), structured along the same lines asgoogle-workspace/scripts/google_api.py:Calendar —
calendars,events [--days N --past N --all-calendars --calendar X],search-events --query X(cross-calendar subject search via$filter contains()since Graph rejects$searchon /me/events),event-detail,create-event(with--all-day+ full recurrence: daily/weekly/monthly/yearly +--recurrence-days/--recurrence-until/--recurrence-count),update-event(PATCH with seriesMaster + attendee warnings),delete-event(loud warning when deleting aseriesMasterwould wipe a whole recurring series).To Do —
lists,create-list,rename-list,delete-list(withwellknownListNameprotection for built-indefaultList/flaggedEmails),tasks [--list X --all],create-task(with the same recurrence flags),update-task,complete-task,delete-task.Subtasks (Graph
checklistItems, shown as "Steps" in the Outlook clients) —steps,add-step,complete-step,delete-step.Ops —
status(graph user, default calendar, token freshness, scope),audit-log [--tail N --op NAME](append-only JSON-lines log of every write, useful for "what did the agent do last Tuesday").Architecture notes
https://login.microsoftonline.com/common/oauth2/v2.0/token. No MSAL dep needed — keeps the install footprint small versus pulling inmsgraph-sdk-python. Tokens persisted in.credentials/credentials.json(chmod 600), atomic-rename on rotation.create-eventPOST includes a content-hashtransactionId(SHA‑256 of subject + start + end + calendar-id, truncated to Graph's 40-char limit). A client retry inside the 6-hour dedup window returns the same event id instead of creating a duplicate. Verified live: two identical POSTs produced the identical id; same content after adelete-eventproduced a fresh id (no tombstone block).--start/--end/--due(passing2026-12-15T18:00:00+02:00together with a separatetimeZone: "Europe/Berlin"header is ambiguous — the caller's+02:00assumption from summer is wrong in winter). Also refusesend <= startand negative--days/--past.OUTLOOK_TZenv var (defaultEurope/Berlin), passed asPrefer: outlook.timezone="…"header on every Graph call. Recurrence-time-zone is left for Graph to derive server-side (it maps IANA → Windows TZ name automatically).events --all-calendarsaggregates via/me/calendarViewacross owned calendars.search-eventsiterates/me/calendars/{id}/eventsper calendar because Graph rejects$searchon/me/eventswith 501.Open questions for maintainers
skills/productivity/outlook/vs.optional-skills/communication/outlook/— which fits your "broadly useful vs. needs heavyweight setup" judgement better?outlook(consumer-facing),microsoft-365, orm365-calendar(narrower scope claim)? The skill covers Calendar + To Do today, not Mail/Drive/Teams, so a narrower name might be more honest.google-workspace/scripts/setup.pywalks the user through downloadingclient_secret.jsonfrom Google Cloud Console and runs a local-redirect OAuth flow. I'd mirror that pattern for Microsoft — Azure-AD-app registration →client_id.jsondownload → device-code OR local-redirect flow →credentials.jsonwritten. Any preferences on flow choice?hermes auth add microsoftas core CLI? Currently the skill manages its own OAuth file. Hermes core already hashermes auth add anthropicetc. — would it make sense to lift Microsoft OAuth into the same place so multiple Microsoft-related skills (this one, any future Mail-via-Graph skill, etc.) share one token? Or leave per-skill credentials asgoogle-workspacedoes?Calendars.ReadWrite + .Shared,Tasks.ReadWrite + .Shared,User.Read. Open to making this configurable at setup time (read-only profile, no.Shared, etc.) — let me know if you have a preferred scope-selection UX.references/per the existing pattern. Any other formatting expectations beyond whatCONTRIBUTING.mdcovers?Effort estimate I'm willing to put in
references/, write the OAuthsetup.py, strip deployment-specific defaults, translate inline examples to English, full re-test against a fresh tenant.If the response is "yes, send a PR", I'll prepare a draft against
mainand link it back here. If the response is "not bundled, but optional fits", I'll targetoptional-skills/communication/instead. If neither is a fit, I'll publish as a community skill discoverable via the skills hub + Discord and link back to that.Happy to share the current script via a gist or draft PR for pre-review if that's useful before doing the generalization work.