A Microsoft 365 Calendar + To Do skill that talks directly to Microsoft Graph from a single self-contained Python script (plus a one-time auth-bootstrap helper). Built as an agent skill (the Microsoft counterpart to a google-workspace skill) but usable as a plain CLI on its own.
Originally proposed upstream as NousResearch/hermes-agent#25979.
- Calendar: read (single / all / shared calendars), create, update, delete, keyword-search, full event detail — with recurrence (daily/weekly/monthly/yearly), all-day events, and series-master safety warnings.
- Microsoft To Do: lists, tasks, subtasks ("steps"), create/update/complete/delete, recurrence, list CRUD.
- Idempotent writes via content-based
transactionId(Graph dedupes retries within 6h). - Write-op audit log (append-only JSON-Lines) for "who changed what when" forensics.
- Autonomous token refresh — the script refreshes the access token on expiry; you only authenticate once.
Output is always JSON; errors go to stderr with exit 1. No framework, no build step — just Python + requests.
Requires Python 3.8+.
git clone https://github.com/ByteSide/hermes-skill-outlook.git
cd hermes-skill-outlook
pip install -r requirements.txtIn the Azure Portal → App registrations:
- New registration. For supported account types, pick "Accounts in any organizational directory and personal Microsoft accounts" — this matches the default
"tenant": "common". (If you instead pick "Personal Microsoft accounts only", set"tenant": "consumers"inconfig.json; for a single work/school tenant, set your tenant ID.) - Under Authentication → Advanced settings, set "Allow public client flows" to Yes (required for the device-code flow).
- Under API permissions → Add a permission → Microsoft Graph → Delegated permissions, add:
User.ReadCalendars.ReadWriteCalendars.ReadWrite.SharedTasks.ReadWriteTasks.ReadWrite.Sharedoffline_access
- Copy the Application (client) ID from the Overview page.
cp .credentials/config.example.json .credentials/config.json
# edit .credentials/config.json, paste your client_idpython3 scripts/setup_auth.pyFollow the printed device-code prompt (open the URL, enter the code, sign in). On success it writes .credentials/credentials.json. From then on the token refreshes itself.
python3 scripts/outlook.py status
python3 scripts/outlook.py calendarsOptionally set default_calendar (the ID from calendars) and default_calendar_name in config.json so bare events/create-event calls target your preferred calendar.
See SKILL.md for the full subcommand reference and agent-facing usage notes. Quick taste:
python3 scripts/outlook.py events --all-calendars --days 7
python3 scripts/outlook.py create-event --subject "Dentist" \
--start "2026-05-20T10:00:00" --end "2026-05-20T11:00:00" --dry-run
python3 scripts/outlook.py tasks
python3 scripts/outlook.py create-task --title "Bananas" --list "Shopping"- Timezone: defaults to
UTC. Override with theOUTLOOK_TZenv var (e.g.OUTLOOK_TZ="Europe/Berlin").--start/--endmust be timezone-naive ISO (noZ, no offset) — the TZ is applied via a GraphPreferheader. - Credentials never leave your machine.
.credentials/is gitignored; onlyconfig.example.jsonis tracked. - Writes are confirm-first by design. Every mutating subcommand supports
--dry-run, which returns the request body without side effects — wire your agent to show the dry run and get a yes before the real call.
Offline behavior tests (no network, no credentials) for the recurrence, idempotency, and validation logic:
python3 tests/test_offline.pyMIT — see LICENSE.