Initial backup 2026-02-17
This commit is contained in:
7
skills/calendar/.clawhub/origin.json
Normal file
7
skills/calendar/.clawhub/origin.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"registry": "https://clawhub.ai",
|
||||
"slug": "calendar",
|
||||
"installedVersion": "1.0.0",
|
||||
"installedAt": 1770184125851
|
||||
}
|
||||
98
skills/calendar/README.md
Normal file
98
skills/calendar/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Calendar 📅
|
||||
|
||||
Calendar management and scheduling. Create events, manage meetings, and sync across calendar providers.
|
||||
|
||||
## Features
|
||||
|
||||
- Create events
|
||||
- Schedule meetings
|
||||
- Set reminders
|
||||
- View availability
|
||||
- Recurring events
|
||||
- Calendar sync
|
||||
|
||||
## Supported Providers
|
||||
|
||||
- Google Calendar
|
||||
- Apple Calendar (iCloud)
|
||||
- Work/Corporate Calendars
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Setup Google Calendar
|
||||
```bash
|
||||
export CALENDAR_TYPE=google
|
||||
./cal.sh list
|
||||
```
|
||||
|
||||
### Setup iCloud Calendar
|
||||
```bash
|
||||
export CALENDAR_TYPE=icloud
|
||||
export CALENDAR_ICLOUD_ID='Anthony@martinwa.org'
|
||||
export CALENDAR_ICLOUD_PASS='mvas-vwsk-ktiv-anex'
|
||||
./cal.sh list
|
||||
```
|
||||
|
||||
### Setup Work/Corporate Calendar
|
||||
```bash
|
||||
export CALENDAR_TYPE=work
|
||||
export CALENDAR_WORK_EMAIL='your@email.com'
|
||||
export CALENDAR_WORK_URL='https://your-calendar-server.com/calendars'
|
||||
./cal.sh list
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
**View today's events:**
|
||||
```bash
|
||||
./cal.sh today
|
||||
```
|
||||
|
||||
**View this week's agenda:**
|
||||
```bash
|
||||
./cal.sh agenda --days 7
|
||||
```
|
||||
|
||||
**Schedule a meeting:**
|
||||
```bash
|
||||
./cal.sh create "Team Sync" "2026-02-05 10:00" "2026-02-05 11:00"
|
||||
```
|
||||
|
||||
## Multiple Calendar Support
|
||||
|
||||
Now supports **multiple calendar sources**! Once configured, you can view events from all calendars or filter by type.
|
||||
|
||||
### Using iCloud
|
||||
```bash
|
||||
# Your credentials are already set:
|
||||
export CALENDAR_TYPE=icloud
|
||||
export CALENDAR_ICLOUD_ID='Anthony@martinwa.org'
|
||||
export CALENDAR_ICLOUD_PASS='mvas-vwsk-ktiv-anex'
|
||||
|
||||
# View your iCloud calendar
|
||||
./cal.sh today
|
||||
|
||||
# Or view combined with Google (if you add it later)
|
||||
# Switch back to Google:
|
||||
# unset CALENDAR_TYPE
|
||||
# ./cal.sh today
|
||||
```
|
||||
|
||||
### Using Work Calendar
|
||||
```bash
|
||||
# Set up your work calendar:
|
||||
export CALENDAR_TYPE=work
|
||||
export CALENDAR_WORK_EMAIL='anthony@pacificenergy.com.au'
|
||||
export CALENDAR_WORK_URL='https://outlook.office365.com/EWS/Exchange.asmx'
|
||||
./cal.sh today
|
||||
```
|
||||
|
||||
### Viewing All Calendars
|
||||
Want to see events from Google + iCloud + Work all at once? Ask me to combine them!
|
||||
|
||||
## Calendar Commands
|
||||
|
||||
- `./cal.sh today` - Show today's events
|
||||
- `./cal.sh agenda [days]` - Show upcoming events
|
||||
- `./cal.sh list` - List all configured calendars
|
||||
- `./cal.sh create <title> <start> <end> [options]` - Create new event
|
||||
32
skills/calendar/SKILL.md
Normal file
32
skills/calendar/SKILL.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: calendar
|
||||
description: Calendar management and scheduling. Create events, manage meetings, and sync across calendar providers.
|
||||
metadata: {"clawdbot":{"emoji":"📅","requires":{"bins":["curl","jq"]}}}
|
||||
---
|
||||
|
||||
# Calendar 📅
|
||||
|
||||
Calendar and scheduling management.
|
||||
|
||||
## Features
|
||||
|
||||
- Create events
|
||||
- Schedule meetings
|
||||
- Set reminders
|
||||
- View availability
|
||||
- Recurring events
|
||||
- Calendar sync
|
||||
|
||||
## Supported Providers
|
||||
|
||||
- Google Calendar
|
||||
- Apple Calendar
|
||||
- Outlook Calendar
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```
|
||||
"Schedule meeting tomorrow at 2pm"
|
||||
"Show my calendar for this week"
|
||||
"Find free time for a 1-hour meeting"
|
||||
```
|
||||
315
skills/calendar/cal.py
Normal file
315
skills/calendar/cal.py
Normal file
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple CalDAV Calendar Tool for Google Calendar
|
||||
Works with Gmail app passwords - no OAuth needed!
|
||||
"""
|
||||
import sys
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
# This will be run with: uv run --with caldav cal.py
|
||||
|
||||
def get_credentials():
|
||||
"""Get credentials from environment or .env file"""
|
||||
import os
|
||||
|
||||
# Try to load from skills/imap-smtp-email/.env since we already have Gmail creds there
|
||||
env_file = Path(__file__).parent.parent / 'imap-smtp-email' / '.env'
|
||||
if env_file.exists():
|
||||
for line in env_file.read_text().splitlines():
|
||||
if line.strip() and not line.startswith('#') and '=' in line:
|
||||
key, _, value = line.partition('=')
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if key not in os.environ:
|
||||
os.environ[key] = value
|
||||
|
||||
email = os.environ.get('IMAP_USER') or os.environ.get('SMTP_USER')
|
||||
password = os.environ.get('IMAP_PASS') or os.environ.get('SMTP_PASS')
|
||||
|
||||
if not email or not password:
|
||||
print("Error: Email credentials not found. Set IMAP_USER and IMAP_PASS.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return email, password
|
||||
|
||||
def connect_caldav():
|
||||
"""Connect to Calendar via CalDAV (Google, iCloud, or Work)"""
|
||||
import caldav
|
||||
import os
|
||||
|
||||
calendar_type = os.environ.get('CALENDAR_TYPE', 'google')
|
||||
|
||||
if calendar_type == 'icloud':
|
||||
# iCloud CalDAV
|
||||
email = os.environ.get('CALENDAR_ICLOUD_ID', 'anthonym_au@icloud.com')
|
||||
password = os.environ.get('CALENDAR_ICLOUD_PASS', 'mvas-vwsk-ktiv-anex')
|
||||
url = "https://caldav.icloud.com/"
|
||||
|
||||
print(f"Connecting to iCloud calendar for {email}...", file=sys.stderr)
|
||||
client = caldav.DAVClient(url=url, username=email, password=password)
|
||||
principal = client.principal()
|
||||
return principal
|
||||
|
||||
elif calendar_type == 'work':
|
||||
# Work calendar (Pacific Energy M365)
|
||||
email = os.environ.get('CALENDAR_WORK_EMAIL', 'Anthony.martin@pacificenergy.com.au')
|
||||
password = os.environ.get('CALENDAR_WORK_PASS', 'RecOvery2026!')
|
||||
url = os.environ.get('CALENDAR_WORK_URL', 'https://outlook.office365.com/EWS/Exchange.asmx')
|
||||
|
||||
if not all([email, password, url]):
|
||||
print("Error: Work calendar credentials not configured", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Connecting to work calendar ({email})...", file=sys.stderr)
|
||||
client = caldav.DAVClient(url=url, username=email, password=password)
|
||||
principal = client.principal()
|
||||
return principal
|
||||
|
||||
else:
|
||||
# Google Calendar (default)
|
||||
email, password = get_credentials()
|
||||
url = f"https://calendar.google.com/calendar/dav/{email}/events/"
|
||||
|
||||
print(f"Connecting to Google calendar ({email})...", file=sys.stderr)
|
||||
client = caldav.DAVClient(url=url, username=email, password=password)
|
||||
principal = client.principal()
|
||||
return principal
|
||||
|
||||
def cmd_list(args):
|
||||
"""List all calendars"""
|
||||
principal = connect_caldav()
|
||||
calendars = principal.calendars()
|
||||
|
||||
if not calendars:
|
||||
print("No calendars found")
|
||||
return
|
||||
|
||||
print("Available calendars:")
|
||||
for cal in calendars:
|
||||
print(f" {cal.name}")
|
||||
if args.verbose:
|
||||
print(f" URL: {cal.url}")
|
||||
print()
|
||||
|
||||
def cmd_agenda(args):
|
||||
"""Show upcoming events"""
|
||||
principal = connect_caldav()
|
||||
calendars = principal.calendars()
|
||||
|
||||
# Time range
|
||||
start = datetime.now()
|
||||
if args.days:
|
||||
end = start + timedelta(days=int(args.days))
|
||||
else:
|
||||
end = start + timedelta(days=7)
|
||||
|
||||
print(f"Events from {start.strftime('%Y-%m-%d')} to {end.strftime('%Y-%m-%d')}:\n")
|
||||
|
||||
for calendar in calendars:
|
||||
if args.calendar and args.calendar.lower() not in calendar.name.lower():
|
||||
continue
|
||||
|
||||
events = calendar.search(start=start, end=end, event=True, expand=True)
|
||||
|
||||
if not events:
|
||||
continue
|
||||
|
||||
print(f"📅 {calendar.name}")
|
||||
print("-" * 80)
|
||||
|
||||
for event in events:
|
||||
try:
|
||||
vevent = event.icalendar_component
|
||||
summary = str(vevent.get('SUMMARY', 'No title'))
|
||||
dtstart = vevent.get('DTSTART')
|
||||
dtend = vevent.get('DTEND')
|
||||
location = vevent.get('LOCATION', '')
|
||||
description = vevent.get('DESCRIPTION', '')
|
||||
|
||||
# Format datetime
|
||||
if hasattr(dtstart.dt, 'strftime'):
|
||||
start_str = dtstart.dt.strftime('%Y-%m-%d %H:%M')
|
||||
else:
|
||||
start_str = str(dtstart.dt)
|
||||
|
||||
if hasattr(dtend.dt, 'strftime'):
|
||||
end_str = dtend.dt.strftime('%H:%M')
|
||||
else:
|
||||
end_str = str(dtend.dt)
|
||||
|
||||
print(f"\n {summary}")
|
||||
print(f" When: {start_str} - {end_str}")
|
||||
|
||||
if location:
|
||||
print(f" Where: {location}")
|
||||
|
||||
if args.details and description:
|
||||
print(f" Details: {description[:200]}{'...' if len(str(description)) > 200 else ''}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" [Error parsing event: {e}]")
|
||||
|
||||
print()
|
||||
|
||||
def cmd_today(args):
|
||||
"""Show today's events"""
|
||||
principal = connect_caldav()
|
||||
calendars = principal.calendars()
|
||||
|
||||
start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end = start + timedelta(days=1)
|
||||
|
||||
print(f"Today's events ({start.strftime('%Y-%m-%d')}):\n")
|
||||
|
||||
all_events = []
|
||||
|
||||
for calendar in calendars:
|
||||
if args.calendar and args.calendar.lower() not in calendar.name.lower():
|
||||
continue
|
||||
|
||||
events = calendar.search(start=start, end=end, event=True, expand=True)
|
||||
|
||||
for event in events:
|
||||
try:
|
||||
vevent = event.icalendar_component
|
||||
summary = str(vevent.get('SUMMARY', 'No title'))
|
||||
dtstart = vevent.get('DTSTART')
|
||||
dtend = vevent.get('DTEND')
|
||||
location = vevent.get('LOCATION', '')
|
||||
|
||||
all_events.append({
|
||||
'summary': summary,
|
||||
'start': dtstart.dt,
|
||||
'end': dtend.dt,
|
||||
'location': location,
|
||||
'calendar': calendar.name
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
# Sort by start time
|
||||
all_events.sort(key=lambda x: x['start'])
|
||||
|
||||
if not all_events:
|
||||
print("No events today")
|
||||
return
|
||||
|
||||
for evt in all_events:
|
||||
if hasattr(evt['start'], 'strftime'):
|
||||
start_str = evt['start'].strftime('%H:%M')
|
||||
end_str = evt['end'].strftime('%H:%M')
|
||||
print(f" {start_str}-{end_str} {evt['summary']}")
|
||||
else:
|
||||
print(f" All day {evt['summary']}")
|
||||
|
||||
if evt['location']:
|
||||
print(f" 📍 {evt['location']}")
|
||||
print(f" 📅 {evt['calendar']}")
|
||||
print()
|
||||
|
||||
def cmd_create(args):
|
||||
"""Create a new event"""
|
||||
from icalendar import Calendar, Event as ICalEvent
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
principal = connect_caldav()
|
||||
calendars = principal.calendars()
|
||||
|
||||
# Find calendar
|
||||
target_cal = None
|
||||
if args.calendar:
|
||||
for cal in calendars:
|
||||
if args.calendar.lower() in cal.name.lower():
|
||||
target_cal = cal
|
||||
break
|
||||
else:
|
||||
# Use first calendar
|
||||
target_cal = calendars[0] if calendars else None
|
||||
|
||||
if not target_cal:
|
||||
print(f"Error: Calendar '{args.calendar}' not found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Parse datetime
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(args.start)
|
||||
end_dt = datetime.fromisoformat(args.end)
|
||||
except:
|
||||
print("Error: Invalid datetime format. Use YYYY-MM-DD HH:MM", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Create event
|
||||
cal = Calendar()
|
||||
event = ICalEvent()
|
||||
event.add('summary', args.summary)
|
||||
event.add('dtstart', start_dt)
|
||||
event.add('dtend', end_dt)
|
||||
|
||||
if args.location:
|
||||
event.add('location', args.location)
|
||||
|
||||
if args.description:
|
||||
event.add('description', args.description)
|
||||
|
||||
cal.add_component(event)
|
||||
|
||||
# Save to calendar
|
||||
target_cal.save_event(cal.to_ical())
|
||||
print(f"✅ Event created: {args.summary}")
|
||||
print(f" Calendar: {target_cal.name}")
|
||||
print(f" When: {start_dt} - {end_dt}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Simple CalDAV Calendar Tool')
|
||||
subparsers = parser.add_subparsers(dest='command', help='Command')
|
||||
|
||||
# list
|
||||
list_parser = subparsers.add_parser('list', help='List all calendars')
|
||||
list_parser.add_argument('-v', '--verbose', action='store_true', help='Show URLs')
|
||||
|
||||
# agenda
|
||||
agenda_parser = subparsers.add_parser('agenda', help='Show upcoming events')
|
||||
agenda_parser.add_argument('--days', default='7', help='Days ahead (default: 7)')
|
||||
agenda_parser.add_argument('--calendar', help='Filter by calendar name')
|
||||
agenda_parser.add_argument('--details', action='store_true', help='Show descriptions')
|
||||
|
||||
# today
|
||||
today_parser = subparsers.add_parser('today', help='Show today\'s events')
|
||||
today_parser.add_argument('--calendar', help='Filter by calendar name')
|
||||
|
||||
# create
|
||||
create_parser = subparsers.add_parser('create', help='Create new event')
|
||||
create_parser.add_argument('summary', help='Event title')
|
||||
create_parser.add_argument('start', help='Start time (YYYY-MM-DD HH:MM)')
|
||||
create_parser.add_argument('end', help='End time (YYYY-MM-DD HH:MM)')
|
||||
create_parser.add_argument('--calendar', help='Calendar name')
|
||||
create_parser.add_argument('--location', help='Location')
|
||||
create_parser.add_argument('--description', help='Description')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
if args.command == 'list':
|
||||
cmd_list(args)
|
||||
elif args.command == 'agenda':
|
||||
cmd_agenda(args)
|
||||
elif args.command == 'today':
|
||||
cmd_today(args)
|
||||
elif args.command == 'create':
|
||||
cmd_create(args)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
if '--verbose' in sys.argv or '-v' in sys.argv:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
44
skills/calendar/cal.sh
Normal file
44
skills/calendar/cal.sh
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
#!/bin/bash
|
||||
# CalDAV Calendar Tool - Supports Google, iCloud, and Work Calendars
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Get Apple ID and iCloud password from environment
|
||||
# Note: Use original iCloud email (anthonym_au@icloud.com), not the alias
|
||||
APPLE_ID="${CALENDAR_ICLOUD_ID:-anthonym_au@icloud.com}"
|
||||
APPLE_PASS="${CALENDAR_ICLOUD_PASS:-mvas-vwsk-ktiv-anex}"
|
||||
|
||||
# Get work calendar credentials from environment
|
||||
WORK_EMAIL="${CALENDAR_WORK_EMAIL:-Anthony.martin@pacificenergy.com.au}"
|
||||
WORK_PASS="${CALENDAR_WORK_PASS:-RecOvery2026!}"
|
||||
WORK_URL="${CALENDAR_WORK_URL:-https://outlook.office365.com/EWS/Exchange.asmx}"
|
||||
|
||||
# Choose which calendar to use
|
||||
CALENDAR_TYPE="${1:-google}" # Default to Google
|
||||
CALENDAR_URL=""
|
||||
|
||||
if [ "$CALENDAR_TYPE" = "icloud" ]; then
|
||||
if [ -z "$APPLE_ID" ] || [ -z "$APPLE_PASS" ]; then
|
||||
echo "Error: CALENDAR_ICLOUD_ID and CALENDAR_ICLOUD_PASS must be set for iCloud" >&2
|
||||
echo "Run: export CALENDAR_ICLOUD_ID='your@email.com' CALENDAR_ICLOUD_PASS='password'" >&2
|
||||
exit 1
|
||||
fi
|
||||
CALENDAR_URL="https://caldav.icloud.com/${APPLE_ID}/calendars/"
|
||||
elif [ "$CALENDAR_TYPE" = "work" ]; then
|
||||
if [ -z "$WORK_EMAIL" ] || [ -z "$WORK_URL" ]; then
|
||||
echo "Error: CALENDAR_WORK_EMAIL and CALENDAR_WORK_URL must be set for work calendar" >&2
|
||||
exit 1
|
||||
fi
|
||||
CALENDAR_URL="$WORK_URL"
|
||||
else
|
||||
# Google Calendar (default)
|
||||
CALENDAR_URL="https://calendar.google.com/calendar/dav/"
|
||||
fi
|
||||
|
||||
echo "📅 Using $CALENDAR_TYPE calendar" >&2
|
||||
|
||||
# Add calendar type to env for Python script
|
||||
export CALENDAR_TYPE
|
||||
export CALENDAR_URL
|
||||
|
||||
/home/openclaw/.local/bin/uv run --with caldav --with icalendar --with pytz cal.py "$@"
|
||||
18
skills/calendar/setup-env.sh
Normal file
18
skills/calendar/setup-env.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Environment setup for Calendar skill with iCloud
|
||||
# Add this to your ~/.bashrc or ~/.zshrc to persist
|
||||
|
||||
# iCloud Calendar (Anthony Martin)
|
||||
export CALENDAR_TYPE=icloud
|
||||
export CALENDAR_ICLOUD_ID='Anthony@martinwa.org'
|
||||
export CALENDAR_ICLOUD_PASS='mvas-vwsk-ktiv-anex'
|
||||
|
||||
# Work Calendar (Pacific Energy) - will set up when Anthony provides details
|
||||
# export CALENDAR_TYPE=work
|
||||
# export CALENDAR_WORK_EMAIL='anthony@pacificenergy.com.au'
|
||||
# export CALENDAR_WORK_URL='https://pacificenergy.com/calendars'
|
||||
|
||||
echo "✅ Calendar credentials loaded for Anthony Martin"
|
||||
echo " • Google Calendar: configured"
|
||||
echo " • iCloud Calendar: configured (Anthony@martinwa.org)"
|
||||
echo " • Work Calendar: ready (set up when Pacific Energy email provided)"
|
||||
Reference in New Issue
Block a user