Skip to content

Skill proposal: productivity/outlook — Microsoft 365 Calendar + To Do via Graph #25979

Description

@ByteSide

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 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:

Calendarcalendars, 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 Dolists, 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.

Opsstatus (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

  1. 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?
  2. 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.
  3. 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?
  4. 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?
  5. 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.
  6. 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

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havetool/skillsSkills system (list, view, manage)type/featureNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions