Installs into .claude/skills of the current project.
Are you the author of Calendar?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/istota-project-calendar)
---
name: calendar
triggers: [calendar, cal , event, meeting, schedule, appointment, caldav]
description: Calendar operations with CalDAV
cli: true
source_types: [briefing]
companion_skills: [untrusted_input]
dependencies: [caldav, icalendar]
env: [{"var":"CALDAV_URL","from":"config","config_path":"caldav_url","when":"caldav_url","gate_has_discovered_calendars":true},{"var":"CALDAV_USERNAME","from":"config","config_path":"caldav_username","when":"caldav_url","gate_has_discovered_calendars":true},{"var":"CALDAV_PASSWORD","from":"config","config_path":"caldav_password","when":"caldav_url","gate_has_discovered_calendars":true,"sensitive":true}]
---
# Calendar Operations
Calendar operations use CalDAV. Credentials are available via environment variables:
- `CALDAV_URL`: CalDAV server URL (e.g., `https://nextcloud.example.com/remote.php/dav`)
- `CALDAV_USERNAME`: Username for CalDAV authentication
- `CALDAV_PASSWORD`: Password/app token for CalDAV authentication
## CLI Commands
The simplest way to interact with calendars is via the CLI:
```bash
# List today's events from all of your calendars (`agenda` is an alias for `list`)
istota-skill calendar list --tz "America/Los_Angeles"
# List tomorrow's events
istota-skill calendar list --date tomorrow --tz "America/Los_Angeles"
# List events for a specific date
istota-skill calendar list --date 2026-02-15 --tz "America/Los_Angeles"
# List from a specific calendar
istota-skill calendar list --calendar "https://..." --date today
# List events for the next 7 days
istota-skill calendar list --week --tz "America/Los_Angeles"
# Create an event (floating time — use when the time is local regardless of timezone)
istota-skill calendar create \
--calendar "https://..." \
--summary "Team Meeting" \
--start "2026-02-15 14:00" \
--end "2026-02-15 15:00" \
--location "Conference Room A"
# Create an event with explicit timezone (use for travel/flights)
istota-skill calendar create \
--calendar "https://..." \
--summary "Flight JFK→LHR" \
--start "2026-04-26 19:10" \
--end "2026-04-27 19:05" \
--tz "America/Los_Angeles" \
--description "SAS SK932"
# Create a one-day all-day event
istota-skill calendar create \
--calendar "https://..." \
--summary "Holiday" \
--start "2026-08-29" \
--all-day
# Update an event
istota-skill calendar update \
--calendar "https://..." \
--uid "event-uid-here" \
--summary "Updated Title" \
--start "2026-02-15 15:00" \
--end "2026-02-15 16:00"
# Move an all-day event. The end date is inclusive.
istota-skill calendar update \
--calendar "https://..." \
--uid "event-uid-here" \
--start "2026-08-29" \
--end "2026-08-30" \
--all-day
# Update: clear optional fields
istota-skill calendar update \
--calendar "https://..." \
--uid "event-uid-here" \
--clear-location --clear-description
# Delete an event
istota-skill calendar delete --calendar "https://..." --uid "event-uid-here"
```
**`--calendar` takes one of the user's own calendars**, as listed in the prompt. A calendar another user shared with the bot is refused as `Calendar not found`, the same answer as one that does not exist.
**Always pass `--tz` with the user's timezone** (from prompt metadata) to ensure correct date boundaries.
**Always pass `--tz` when creating timed events with specific timezone semantics** (flights, meetings across timezones). Omit `--tz` for local timed events where the wall-clock time is what matters. All-day events are DATE values and never carry a timezone. Their end date is inclusive; create may omit it for a one-day event, while update requires both dates to keep the event boundaries the same type.
Output is JSON:
```json
{
"status": "ok",
"date": "today",
"event_count": 2,
"events": [
{
"calendar": "Work",
"uid": "abc123",
"summary": "Team Meeting",
"start": "2026-02-15T14:00:00",
"end": "2026-02-15T15:00:00",
"location": "Conference Room A",
"description": null,
"all_day": false,
"timezone": "America/Los_Angeles"
}
]
}
```
The `timezone` field shows the original TZID from the iCalendar data. `"floating"` means the event has no timezone — the time is interpreted as-is regardless of the viewer's timezone.
## Python API
The `istota.skills.calendar` module also provides functions for programmatic access:
| Function | Description | Returns |
|----------|-------------|---------|
| `get_caldav_client(url, username, password)` | Create CalDAV client | `caldav.DAVClient` |
| `list_calendars(client)` | List every calendar the account can see, other users' shared ones included | `list[(name, url)]` |
| `get_calendars_for_user(client, username)` | Get calendars owned by a user | `list[(name, url, writable)]` |
| `owned_calendars(client, username)` | The same, as `(name, url)` | `list[(name, url)]` |
| `get_events(client, calendar_url, start, end)` | Get events in date range | `list[CalendarEvent]` |
| `get_today_events(client, calendar_url, tz)` | Get today's events | `list[CalendarEvent]` |
| `get_tomorrow_events(client, calendar_url, tz)` | Get tomorrow's events | `list[CalendarEvent]` |
| `get_week_events(client, calendar_url, tz)` | Get next 7 days' events | `list[CalendarEvent]` |
| `get_event_by_uid(client, calendar_url, uid)` | Get single event by UID | `CalendarEvent \| None` |
| `create_event(client, calendar_url, ...)` | Create new event | `str` (event UID) |
| `update_event(client, calendar_url, uid, ...)` | Update existing event | `bool` |
| `delete_event(client, calendar_url, uid)` | Delete event by UID | `bool` |
| `format_event_for_display(event)` | Format event for human display | `str` |
| `format_day_schedule(events, date_label)` | Format day's events | `str` |
### CalendarEvent Dataclass
```python
@dataclass
class CalendarEvent:
uid: str # Unique identifier
summary: str # Event title
start: datetime # Start time; midnight when an all-day event is read
end: datetime # End time; exclusive midnight for an all-day event
location: str | None
description: str | None
all_day: bool
timezone: str | None # Original TZID, None = floating
```
### Permissions
Calendars can be shared with read-only or edit permissions:
- **Read-only**: Can view events but not modify them
- **Edit**: Can create, update, and delete events
When attempting to modify a read-only calendar, `update_event()` and `create_event()` will raise `caldav.error.AuthorizationError`.
### Python Example
```python
from istota.skills.calendar import get_caldav_client, get_today_events
import os
client = get_caldav_client(
url=os.environ["CALDAV_URL"],
username=os.environ["CALDAV_USERNAME"],
password=os.environ["CALDAV_PASSWORD"],
)
# Get today's events (always pass user's timezone)
calendar_url = "https://nextcloud.example.com/remote.php/dav/calendars/alice/personal/"
for event in get_today_events(client, calendar_url, tz="America/Los_Angeles"):
print(f"{event.start.strftime('%H:%M')} - {event.summary}")
```