# suruf-calendar > Display interactive calendars with FullCalendar, Google Calendar API, and CalDAV support. AI-driven view selection renders optimal calendar view (month, week, day, list) based on user intent. - Author: a.mor - Repository: alimrb/suruf-platform-repo - Version: 20260208142121 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-08 - Source: https://github.com/alimrb/suruf-platform-repo - Web: https://mule.run/skillshub/@@alimrb/suruf-platform-repo~suruf-calendar:20260208142121 --- --- name: suruf-calendar description: Display interactive calendars with FullCalendar, Google Calendar API, and CalDAV support. AI-driven view selection renders optimal calendar view (month, week, day, list) based on user intent. --- # Suruf Calendar Skill AI-driven calendar management with intelligent view selection. Automatically chooses the best calendar view based on user intent. ## CRITICAL: Google Calendar Integration When the user mentions **Google Calendar**, **my calendar**, **my schedule**, or similar personal calendar requests: 1. **FIRST** call `google_calendar_list_events` to fetch events server-side 2. **THEN** render the calendar UI with those events using `suruf_ui_calendar` or `<<>>` block 3. **NEVER** use `caldavUrl` with Google Calendar URLs — Google's CalDAV endpoint blocks browser CORS requests ### Google Calendar Flow (Preferred) ``` Step 1: Fetch events from Google Calendar API (server-side) google_calendar_list_events({ calendarId: "primary", timeMin: "2026-02-01T00:00:00Z", timeMax: "2026-02-28T23:59:59Z" }) Step 2: Render calendar with fetched events suruf_ui_calendar({ initialView: "month", events: [ ...events from step 1... ], editable: true, selectable: true }) ``` ### Available Google Calendar Tools | Tool | Purpose | |------|---------| | `google_calendar_list_calendars` | List all user's calendars | | `google_calendar_list_events` | List events in a time range | | `google_calendar_get_event` | Get a specific event by ID | | `google_calendar_create_event` | Create a new event | | `google_calendar_update_event` | Update an existing event | | `google_calendar_delete_event` | Delete an event | | `google_calendar_search_events` | Search events by text | | `google_calendar_get_freebusy` | Check free/busy status | | `google_calendar_auth_status` | Check if authenticated | If `google_calendar_auth_status` returns `authenticated: false`, tell the user to visit the auth endpoint to authenticate. ## Rendering Mode ### Mode 1: MCP Apps (Preferred) If `suruf_ui_calendar` tool is available, call it directly: ``` suruf_ui_calendar({ initialView: "month", events: [ { title: "Team Meeting", start: "2025-06-15T10:00:00", end: "2025-06-15T11:00:00" }, { title: "Launch Day", start: "2025-06-20", allDay: true, color: "#6366f1" } ], selectable: true, editable: true }) ``` ### Mode 2: COMPONENTS Block (Fallback) If MCP tools are NOT available, use the existing iframe/COMPONENTS format below. ## CRITICAL: Always Render the Calendar **When this skill is invoked (via `/suruf-calendar`, `#calendar`, or any calendar-related query), you MUST ALWAYS render a calendar component. Never respond with only help text or explanations.** If the user types just `/suruf-calendar` with no additional context: - Render a monthly calendar view (`dayGridMonth`) as the default - Do NOT provide help text about how to use the skill - The calendar IS the response ## CRITICAL: Output Format **You MUST output a `<<>>` block in your response!** The openwebui-frontend service will parse this block and inject the components into the post's `props.components`. Do NOT output raw HTML tags - they will be escaped. ### Required Output Format Your response MUST include this block (with your component JSON inside): ``` Here's your calendar: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "dayGridMonth", "editable": true, "selectable": true }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ### Component JSON Schema **IMPORTANT**: Always use `type: "suruf-component-view"` with `componentType: "calendar"` for iframe-based rendering. Do NOT use `type: "suruf-calendar"` directly. ```json { "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "dayGridMonth", // dayGridMonth | timeGridWeek | timeGridDay | listWeek "eventsJson": "[...]", // JSON string of events array "caldavUrl": "http://...", // CalDAV server URL "caldavUser": "username", // CalDAV username "calendarPath": "/user/cal.ics", // Calendar ICS path "editable": true, // Allow event drag/resize "selectable": true, // Allow date selection "weekNumbers": false, // Show week numbers "nowIndicator": true, // Show current time indicator "firstDay": 1, // First day of week (0=Sunday, 1=Monday) "variant": "light", // light | dark | auto "locale": "en" // Locale for date formatting }, "minHeight": "600px", // Container height "toolbar": true // Show toolbar controls } ``` **IMPORTANT**: - Do NOT output raw HTML tags like `` - they will be escaped - Do NOT use markdown code fences expecting rendering - the calendar won't render - Always include the `<<>>` block with valid JSON - Always use `type: "suruf-component-view"` NOT `type: "suruf-calendar"` ## When to Use - User asks about calendar, schedule, or appointments - Displaying events, meetings, or time-based data - Showing availability or bookings - Integrating with CalDAV/Radicale backend - User explicitly uses `#calendar` command - Any query involving "schedule", "events", "appointments", "meetings" ## AI-Driven View Selection ### Intent Recognition Detect user intent from natural language: | Intent | Trigger Phrases | Preferred View | Data Source | |--------|----------------|----------------|-------------| | `google` | "google calendar", "my calendar", "my schedule", "my events" | `timeGridWeek` | `google_calendar_list_events` | | `month` | "calendar", "month view", "monthly", "overview" | `dayGridMonth` | Google Calendar or static | | `week` | "this week", "weekly", "week view", "schedule" | `timeGridWeek` | Google Calendar or static | | `today` | "today", "daily", "day view", "today's agenda" | `timeGridDay` | Google Calendar or static | | `list` | "list", "upcoming", "agenda", "events" | `listWeek` | Google Calendar or static | | `connect` | "connect", "caldav", "sync", "radicale" | CalDAV-enabled view | CalDAV (Radicale only) | ### Component Selection Logic ```javascript function selectView(intent, hasEvents, hasCalDAV) { if (hasCalDAV) { return { initialView: 'timeGridWeek', caldavUrl: '...' }; } switch(intent) { case 'month': return { initialView: 'dayGridMonth' }; case 'week': return { initialView: 'timeGridWeek' }; case 'today': return { initialView: 'timeGridDay' }; case 'list': return { initialView: 'listWeek' }; default: return { initialView: 'dayGridMonth' }; } } ``` ## Calendar Views | View | ID | Description | Best For | |------|------|-------------|----------| | Month | `dayGridMonth` | Month view with day cells | Monthly overview | | Week | `timeGridWeek` | Week view with time slots | Weekly scheduling | | Day | `timeGridDay` | Day view with time slots | Daily agenda | | List | `listWeek` | List view of events | Event listing | ## Event Object Structure ```json { "id": "unique-id", "title": "Event Title", "start": "2026-01-13T09:00:00", "end": "2026-01-13T10:00:00", "allDay": false, "color": "#3b82f6", "description": "Optional description", "location": "Optional location" } ``` ## Response Examples ### Google Calendar (HIGHEST PRIORITY) User: `my google calendar` or `show my schedule` or `my calendar` Your response flow: 1. Call `google_calendar_list_events({ calendarId: "primary", timeMin: "", timeMax: "" })` 2. Map returned events to calendar event format 3. Render with MCP tool or COMPONENTS block: ``` suruf_ui_calendar({ initialView: "dayGridMonth", events: [ { id: "abc123", title: "Team Meeting", start: "2026-02-03T10:00:00+03:00", end: "2026-02-03T11:00:00+03:00" }, { id: "def456", title: "Lunch", start: "2026-02-03T12:00:00+03:00", end: "2026-02-03T13:00:00+03:00", color: "#10b981" } ], editable: true, selectable: true, nowIndicator: true }) ``` If google_calendar tools are not authenticated, respond: ``` Your Google Calendar is not connected yet. Please visit http://localhost:4015/auth/start to authenticate with Google, then try again. ``` ### Bare Skill Invocation (MOST COMMON) User: `/suruf-calendar` Your response (MUST render the calendar, not help text): ``` Here's your calendar for this month: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "dayGridMonth", "editable": true, "selectable": true, "nowIndicator": true }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ### Basic Monthly Calendar User: `#calendar` Your response: ``` Here's your calendar: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "dayGridMonth", "editable": true, "selectable": true }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ### Weekly Schedule with Events User: `#calendar Show my meetings this week` Your response: ``` Here's your weekly schedule with meetings: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "timeGridWeek", "nowIndicator": true, "eventsJson": "[{\"id\":\"1\",\"title\":\"Team Standup\",\"start\":\"2026-01-13T09:00:00\",\"end\":\"2026-01-13T09:30:00\"},{\"id\":\"2\",\"title\":\"Project Review\",\"start\":\"2026-01-13T14:00:00\",\"end\":\"2026-01-13T15:00:00\",\"color\":\"#10b981\"},{\"id\":\"3\",\"title\":\"Client Call\",\"start\":\"2026-01-14T11:00:00\",\"end\":\"2026-01-14T12:00:00\",\"color\":\"#f59e0b\"}]" }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ### Today's Agenda User: `#calendar today` Your response: ``` Here's today's agenda: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "timeGridDay", "nowIndicator": true, "selectable": true }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ### List View of Upcoming Events User: `#calendar list upcoming events` Your response: ``` Here are your upcoming events: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "listWeek" }, "minHeight": "500px", "toolbar": true }] } <<>> ``` ### Dark Theme Calendar User: `#calendar dark mode` Your response: ``` Here's your calendar in dark mode: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "dayGridMonth", "variant": "dark", "editable": true }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ### CalDAV Connected Calendar User: `#calendar Connect to my calendar` Your response: ``` Here's your CalDAV-connected calendar: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "timeGridWeek", "caldavUrl": "http://localhost:5232", "caldavUser": "admin", "calendarPath": "/admin/calendar.ics", "editable": true, "nowIndicator": true }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ### Calendar with Pre-populated Events User: `#calendar Show sprint planning events` Your response: ``` Here's the sprint calendar: <<>> { "components": [{ "type": "suruf-component-view", "componentType": "calendar", "props": { "initialView": "timeGridWeek", "firstDay": 1, "eventsJson": "[{\"id\":\"sprint-1\",\"title\":\"Sprint Planning\",\"start\":\"2026-01-13T10:00:00\",\"end\":\"2026-01-13T11:30:00\",\"color\":\"#3b82f6\"},{\"id\":\"sprint-2\",\"title\":\"Daily Standup\",\"start\":\"2026-01-14T09:00:00\",\"end\":\"2026-01-14T09:15:00\",\"color\":\"#10b981\"},{\"id\":\"sprint-3\",\"title\":\"Sprint Review\",\"start\":\"2026-01-17T14:00:00\",\"end\":\"2026-01-17T15:00:00\",\"color\":\"#f59e0b\"}]" }, "minHeight": "600px", "toolbar": true }] } <<>> ``` ## CalDAV Integration (Radicale Only) **WARNING**: CalDAV ONLY works with the local Radicale server. Do NOT use `caldavUrl` with Google Calendar, iCloud, or other external providers — they block browser CORS requests. For Google Calendar, use the `google_calendar_*` MCP tools instead. For connecting to Radicale CalDAV backend: | Setting | Value | |---------|-------| | Radicale URL | http://localhost:5232 | | Calendar Path | /{username}/calendar.ics | | Default User | admin | | Protocol | iCalendar (ICS) over HTTP | ### CalDAV Component Configuration ```json { "type": "suruf-component-view", "componentType": "calendar", "props": { "caldavUrl": "http://localhost:5232", "caldavUser": "admin", "calendarPath": "/admin/calendar.ics", "initialView": "timeGridWeek", "editable": true }, "minHeight": "600px", "toolbar": true } ``` ## Custom Events The calendar component emits these events for parent interaction: | Event | Detail | Description | |-------|--------|-------------| | `suruf-calendar-event-click` | `{event: CalendarEvent}` | User clicked an event | | `suruf-calendar-date-select` | `{start, end, allDay}` | User selected date range | | `suruf-calendar-event-change` | `{event: CalendarEvent}` | Event was moved/resized | | `suruf-calendar-view-change` | `{view: string}` | View was changed | ## Color Palette Recommended event colors: | Color | Hex | Usage | |-------|-----|-------| | Blue | `#3b82f6` | Default, meetings | | Green | `#10b981` | Completed, success | | Amber | `#f59e0b` | Warning, pending | | Red | `#ef4444` | Urgent, important | | Purple | `#8b5cf6` | Personal, misc | | Cyan | `#06b6d4` | Info, notes | ## Error Handling The component handles calendar and CalDAV errors internally. If CalDAV is unavailable, the component will show a connection error. For critical errors before rendering, output a simple message (no COMPONENTS block needed): ``` Unable to load calendar. Please check if the CalDAV server is accessible. ``` ## Related Skills | Skill | Purpose | Integration Point | |-------|---------|-------------------| | [`suruf-database`](../suruf-database/SKILL.md) | PostgreSQL queries | Query event data from database | | [`suruf-openwebui`](../suruf-openwebui/SKILL.md) | Open WebUI API | Channel-specific calendars | | [`suruf-html-deliverable`](../suruf-html-deliverable/SKILL.md) | HTML output | Complex calendar reports | ## Documentation - [Calendar Views Reference](references/calendar-views.md) - View types and configuration - [Event Format Reference](references/event-format.md) - Event object structure - [CalDAV Integration](references/caldav-integration.md) - CalDAV server setup ## Infrastructure | Component | Location | |-----------|----------| | React Component | `suruf-openwebui-frontend/src/components/calendar/` | | Web Component | `suruf-openwebui-frontend/src/web-components/suruf-calendar-wc.tsx` | | Embedded Page | `suruf-openwebui-frontend/public/web-components/calendar-embed.html` | | CalDAV Backend | Radicale (Docker) at port 5232 | | FullCalendar Version | 6.1.20 | | Related Ticket | SURUF-515 | ## Open WebUI Integration When responding in **Open WebUI** (not Open WebUI), use the iframe embed approach instead of `<<>>` blocks. ### Open WebUI Output Format Return an `embeds` array with inline HTML: ```json { "embeds": ["...calendar HTML..."] } ``` ### Open WebUI Calendar Template ```html
``` ### Platform Detection | Platform | Output Format | |----------|--------------| | Open WebUI | `<<>>` block with JSON | | Open WebUI | `embeds` array with inline HTML | See [`openwebui-react-embed`](../openwebui-react-embed/SKILL.md) for full Open WebUI integration details. ## User Interaction: Collect Details via Forms **MANDATORY:** When the user asks to create calendar events without providing full details, use `suruf_ui_form_generator` to collect them BEFORE creating. **Example:** "Add an event" → form: title, date, start time, duration (select: 15/30/45/60/90/120 min), location, description, recurrence (select: none/daily/weekly/monthly), color (color picker). See `suruf/FORM-INTERACTION.md` for the full pattern.