brain/docs/superpowers/specs/2026-04-12-o365-calendar-in...

245 lines
8.9 KiB
Markdown

# O365 Calendar Integration — Design Spec
## Goal
Integrate Microsoft Office 365 calendar with the Obsidian vault to enable meeting note creation with automatic participant matching, series recognition, and agenda pre-population. Two creation paths: manual (Templater) and Claude-assisted (MCP).
## Scope
- **In scope:** Calendar read access, meeting note creation, participant matching, series recognition, agenda import
- **Out of scope:** Email integration (Phase 2), write-back to O365, automatic background sync, Copilot/Work IQ integration
## Architecture Overview
```
┌─────────────────────────────────────────────────┐
│ Obsidian Vault │
│ │
│ 03 Bereiche/Meetings/ 00 Kontext/Personen/ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Meeting Notes │◄───│ Person Notes │ │
│ │ (per instance) │ │ (email matching) │ │
│ └──────────────────┘ └──────────────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ Templater Claude Person-Matcher │
│ Template (MCP) (shared logic) │
│ │ │ │
└──────────┼──────────┼─────────────────────────────┘
│ │
▼ ▼
┌──────────────────────┐
│ Microsoft Graph API │
│ (Azure App Reg) │
└──────────────────────┘
```
### Components
| Component | Purpose | Location |
|---|---|---|
| MCP Server (Softeria) | Claude reads O365 calendar | `~/.claude/` config |
| Node.js User Script | Templater reads O365 calendar | `scripts/o365-calendar.js` |
| Person Matcher | Email → person note, auto-create | `scripts/person-matcher.js` |
| Meeting Template | Unified format for both paths | Templater template folder |
| Frontmatter extensions | `email` on persons, `domain` on companies | Existing notes |
## Vault Structure Changes
### New folder
- `03 Bereiche/Meetings/` — all meeting notes, flat, filename `YYYY-MM-DD Title.md`
### Person frontmatter extension
```yaml
# New field added to all person notes
email: christopher.klein@krah.de # Primary match key
```
### Company frontmatter extension
```yaml
# New field added to company notes
domain: krah-gruppe.de # For auto-assignment person → company via email domain
```
### Auto-created person notes
When a meeting participant has no match in the vault:
```yaml
---
tags: [person]
vorname: Max
nachname: Müller
email: max.mueller@landata.de
kategorie: Extern
firma: "[[LANdata]]" # Wikilink if company note with matching domain exists
# Plain text "landata.de" otherwise
status: ungeprüft # Flag for manual review
---
# Max Müller
## Zur Person
- **E-Mail:** max.mueller@landata.de
- **Firma:** LANdata (landata.de)
## Notizen
```
### Auto-created company notes
Companies are NOT auto-created. Only the domain is stored as plain text in the person note until a company note is manually created.
## Meeting Note Template
```markdown
---
tags: [meeting]
date: YYYY-MM-DD
start: "HH:MM"
end: "HH:MM"
serie: "Serie Name" # Empty if not recurring
projekt: # Wikilink to project if assignable
teilnehmer:
- "[[Person 1]]"
- "[[Person 2]]"
status: offen # offen / abgeschlossen
o365_id: "AAMkAG..." # For back-reference to O365
---
# Meeting Title
## Teilnehmer
- [[Person 1]] — Role/Context
- [[Person 2]]
## Agenda
- Imported agenda items
## Notizen
## Entscheidungen
## Aufgaben
```
## Matching Logic
### Participant matching (priority order)
1. **Email match:** Search `00 Kontext/Personen/` for matching `email` frontmatter field
2. **Name fallback:** Compare O365 `displayName` against `vorname` + `nachname` in person notes
3. **Auto-create:** No match → create new person note with `status: ungeprüft`, derive company from email domain
### Company matching via email domain
1. Extract domain from email address (e.g., `landata.de` from `max@landata.de`)
2. Search `00 Kontext/Firmen/` for matching `domain` frontmatter field
3. Match found → Wikilink to company note
4. No match → store domain as plain text
### Series recognition
1. Check O365 event for `seriesMasterId` → recurring meeting
2. Match event title against folder names in `03 Bereiche/Jour Fixe/*/Agenda.md`
3. Match found → populate `serie` field, link to Jour Fixe agenda
### Agenda pre-population (priority order)
1. **Jour Fixe agenda:** Series recognized → import open items (`🔴 Offen` section) from `03 Bereiche/Jour Fixe/[Serie]/Agenda.md`
2. **O365 event body:** No series or Jour Fixe empty → use calendar event body text
3. **Empty:** Both empty → empty agenda section
## Creation Path A: Manual via Templater
1. User triggers template (hotkey or command palette)
2. Templater User Script calls Node.js helper
3. Node.js script authenticates via Azure App Registration (Delegated OAuth2 flow)
4. Fetches calendar events (today + next 7 days)
5. Presents selection list: `📅 14:00-15:00 IT Team Weekly | 📅 16:00-16:30 Call mit LANdata`
6. User selects → script returns event data as JSON
7. Templater renders note, runs participant matching, writes file to `03 Bereiche/Meetings/`
### Components
- `scripts/o365-calendar.js` — Node.js script, Graph API client, OAuth2 token management
- `scripts/person-matcher.js` — Reads person notes, matches by email/name, creates new person notes
- Templater template referencing both scripts
## Creation Path B: Via Claude (MCP)
1. User says "Leg Meeting-Notiz an" or similar
2. Claude calls MCP tools: `list-calendar-events` → find event → `get-event-details`
3. Claude applies same matching logic (persons, series, agenda)
4. Claude creates note in same format
### MCP Server
- **Server:** `softeria/graph-mcp-server` (or best available alternative at implementation time)
- **Config:** `~/.claude/` MCP server configuration
- **Auth:** Same Azure App Registration as Templater path
- **Scope:** Read-only calendar access
### Matching rules in CLAUDE.md
The matching rules (email → person, domain → company, title → series) are documented in CLAUDE.md so Claude applies them consistently without the Node.js scripts.
## Boundaries
### Meeting Notes vs. Daily Notes
- Daily Notes remain unchanged: daily log, decisions, short notes
- Meeting Notes are standalone documents in `03 Bereiche/Meetings/`
- Connection via wikilinks: Daily Note can link to meeting note
### Meeting Notes vs. Jour Fixe
- Jour Fixe remains the running agenda management (Offen/Postponed/Dauerläufer)
- Meeting Notes are per-instance protocols
- For Jour Fixe series: typically NO meeting note created, work directly in agenda
- If created: agenda imported from Jour Fixe, meeting note links back
### What does NOT happen
- No automatic background synchronization
- No email copies in vault (calendar only in Phase 1)
- No write-back to O365
- No automatic meeting note creation — always conscious decision
## Documentation
A user-facing documentation note will be created at `03 Bereiche/IT-Management/O365 Kalender Integration.md`. This note explains:
- What the integration does and how it works (non-technical overview)
- How to create a meeting note manually (Templater workflow, step by step)
- How to create a meeting note via Claude
- How participant matching works (email → person, auto-creation)
- How series recognition and agenda import work
- Where meeting notes are stored and how to find them
- Known limitations and planned future extensions (email integration)
This is a living document, updated as the integration evolves.
## Prerequisites
- [ ] Add `email` field to all existing person notes (6 KRAH employees)
- [ ] Add `domain: krah-gruppe.de` to KRAH company note
- [ ] OAuth2 flow: Application (Client Credentials) — app-only access to user's calendar
- [ ] Node.js available on system
- [ ] Evaluate Softeria MCP server compatibility at implementation time
- [ ] Create user documentation at `03 Bereiche/IT-Management/O365 Kalender Integration.md`
## Open Questions
- Exact OAuth2 token storage/refresh strategy for Templater Node.js script
- Softeria MCP server: verify it supports delegated auth and calendar event listing with sufficient detail (body, attendees, recurrence)
- Fallback if Softeria is insufficient: build minimal custom MCP server against Graph API