
Macos Calendar
- 8 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
macos-calendar is a Claude skill that creates, lists, and manages macOS Calendar events via AppleScript.
About
macos-calendar creates, lists, and manages Apple Calendar events on macOS using AppleScript. It runs a calendar.sh script that supports listing calendars and creating events from JSON with fields for title, calendar, notes, date offset or absolute date, time, duration, alarms, all-day, and iCal RRULE recurrence. A developer uses it when they ask an agent to schedule a meeting, add a reminder, or set a deadline. It uses relative date math to avoid locale-specific date parsing issues.
- Create, list, and manage macOS Calendar events via AppleScript (osascript)
- Relative date math avoids locale issues across FR/EN/DE date formats
- Supports summary, calendar, description, offset/absolute date, time, duration, alarms, all-day, and RRULE recurrence
Macos Calendar by the numbers
- 8 all-time installs (skills.sh)
- Ranked #2,242 of 3,280 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
macos-calendar capabilities & compatibility
Free; macOS-only, uses built-in osascript and python3.
- Capabilities
- create event · list calendars · manage events
- Use cases
- planning · project management
- Platforms
- macOS
- Pricing
- Free
What macos-calendar says it does
Create, list, and manage macOS Calendar events via AppleScript.
All date handling uses relative math (`current date + N * days`) to avoid locale issues (FR/EN/DE date formats).
Requires macOS with Calendar.app. Uses osascript (AppleScript) and python3 for JSON parsing.
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill macos-calendarAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Create, list, and manage Apple Calendar events on macOS from an agent via AppleScript.
Who is it for?
macOS users who want an agent to schedule events and reminders in Apple Calendar.
Skip if: Windows or Linux users, or anyone not using Calendar.app.
When should I use this skill?
The user asks to add a reminder, schedule an event, create a calendar entry, or set a deadline on macOS.
What you get
The agent creates the calendar event, including recurrence and alarms, in one call.
- Apple Calendar events
- calendar listings
By the numbers
- 11 documented JSON event fields
Files
macOS Calendar
Manage Apple Calendar events via $SKILL_DIR/scripts/calendar.sh. All date handling uses relative math (current date + N * days) to avoid locale issues (FR/EN/DE date formats).
Quick start
List calendars
Always list calendars first to find the correct calendar name:
"$SKILL_DIR/scripts/calendar.sh" list-calendarsCreate an event
echo '<json>' | "$SKILL_DIR/scripts/calendar.sh" create-eventJSON fields:
| Field | Required | Default | Description |
|---|---|---|---|
summary | yes | - | Event title |
calendar | no | first calendar | Calendar name (from list-calendars) |
description | no | "" | Event notes |
offset_days | no | 0 | Days from today (0=today, 1=tomorrow, 7=next week) |
iso_date | no | - | Absolute date YYYY-MM-DD (overrides offset_days) |
hour | no | 9 | Start hour (0-23) |
minute | no | 0 | Start minute (0-59) |
duration_minutes | no | 30 | Duration |
alarm_minutes | no | 0 | Alert N minutes before (0=no alarm) |
all_day | no | false | All-day event |
recurrence | no | - | iCal RRULE string. See references/recurrence.md |
Interpreting natural language
Map user requests to JSON fields:
| User says | JSON |
|---|---|
| "tomorrow at 2pm" | offset_days: 1, hour: 14 |
| "in 3 days" | offset_days: 3 |
| "next Monday at 10am" | Calculate offset_days from today to next Monday, hour: 10 |
| "February 25 at 3:30pm" | iso_date: "2026-02-25", hour: 15, minute: 30 |
| "every weekday at 9am" | hour: 9, recurrence: "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR" |
| "remind me 1 hour before" | alarm_minutes: 60 |
| "all day event on March 1" | iso_date: "2026-03-01", all_day: true |
For "next Monday", "next Friday" etc: compute the day offset using the current date. Use date command if needed:
# Days until next Monday (1=Monday)
target=1; today=$(date +%u); echo $(( (target - today + 7) % 7 ))Example prompts
These are real user prompts and the commands you should run:
"Remind me to call the dentist in 2 days"
"$SKILL_DIR/scripts/calendar.sh" list-calendarsThen:
echo '{"calendar":"Personnel","summary":"Call dentist","offset_days":2,"hour":9,"duration_minutes":15,"alarm_minutes":30}' | "$SKILL_DIR/scripts/calendar.sh" create-event"Schedule a team sync every Tuesday at 2pm with a 10-min reminder"
echo '{"calendar":"Work","summary":"Team sync","hour":14,"duration_minutes":60,"recurrence":"FREQ=WEEKLY;BYDAY=TU","alarm_minutes":10}' | "$SKILL_DIR/scripts/calendar.sh" create-event"Block July 15 as a vacation day"
echo '{"calendar":"Personnel","summary":"Vacances","iso_date":"2026-07-15","all_day":true}' | "$SKILL_DIR/scripts/calendar.sh" create-event"I have a doctor appointment next Thursday at 3:30pm, remind me 1 hour before"
# First compute offset_days to next Thursday (4=Thursday)
target=4; today=$(date +%u); offset=$(( (target - today + 7) % 7 )); [ "$offset" -eq 0 ] && offset=7Then:
echo "{\"calendar\":\"Personnel\",\"summary\":\"Doctor appointment\",\"offset_days\":$offset,\"hour\":15,\"minute\":30,\"duration_minutes\":60,\"alarm_minutes\":60}" | "$SKILL_DIR/scripts/calendar.sh" create-event"Set up a daily standup at 9am on weekdays for the next 4 weeks"
echo '{"calendar":"Work","summary":"Daily standup","hour":9,"duration_minutes":15,"recurrence":"FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;COUNT=20"}' | "$SKILL_DIR/scripts/calendar.sh" create-event"Add a biweekly 1-on-1 with my manager on Fridays at 11am"
echo '{"calendar":"Work","summary":"1-on-1 Manager","hour":11,"duration_minutes":30,"recurrence":"FREQ=WEEKLY;INTERVAL=2;BYDAY=FR","alarm_minutes":5}' | "$SKILL_DIR/scripts/calendar.sh" create-eventCritical rules
1. Always list calendars first if the user hasn't specified one — calendars marked [read-only] cannot be used for event creation 2. Never use hardcoded date strings in AppleScript — always use offset_days or iso_date 3. Confirm the calendar name with the user if multiple personal calendars exist 4. Never target a `[read-only]` calendar — the script will reject it with an error 5. For recurring events, consult references/recurrence.md for RRULE syntax 6. Pass JSON via stdin — never as a CLI argument (avoids leaking data in process list) 7. All fields are validated by the script (type coercion, range checks, format validation) — invalid input is rejected with an error message 8. All actions are logged to logs/calendar.log with timestamp, command, calendar, and summary
{
"ownerId": "kn76qgcyqfzadfx73566ekfmm181b375",
"slug": "macos-calendar",
"version": "1.2.0",
"publishedAt": 1771354113927
}{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "macos-calendar",
"installedVersion": "1.2.0",
"installedAt": 1776068593868
}
iCal Recurrence Rules (RRULE)
Apple Calendar uses standard iCal RRULE format for recurring events.
Common patterns
| Pattern | RRULE |
|---|---|
| Daily | FREQ=DAILY;INTERVAL=1 |
| Every weekday | FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR |
| Weekly | FREQ=WEEKLY;INTERVAL=1 |
| Biweekly | FREQ=WEEKLY;INTERVAL=2 |
| Monthly (same date) | FREQ=MONTHLY;INTERVAL=1 |
| Monthly (e.g. 2nd Tuesday) | FREQ=MONTHLY;BYDAY=2TU |
| Yearly | FREQ=YEARLY;INTERVAL=1 |
Limiting recurrence
- End after N occurrences: add
COUNT=10 - End by date: add
UNTIL=20261231T000000Z
Examples
- Every Monday and Wednesday:
FREQ=WEEKLY;BYDAY=MO,WE - First Friday of every month:
FREQ=MONTHLY;BYDAY=1FR - Every 3 days for 5 times:
FREQ=DAILY;INTERVAL=3;COUNT=5
#!/bin/bash
# macOS Calendar helper via AppleScript
# Usage: calendar.sh <command>
#
# Commands:
# list-calendars List all available calendars
# create-event Create an event from JSON (reads stdin)
set -euo pipefail
# Verify required dependencies are available
for bin in osascript python3; do
command -v "$bin" >/dev/null 2>&1 || { echo "Error: $bin is required but not found" >&2; exit 1; }
done
# Ensure Calendar.app is running (avoids AppleScript error -600)
if ! pgrep -q "Calendar"; then
open -a Calendar
sleep 2
fi
LOGFILE="${SKILL_DIR:-$(dirname "$0")/..}/logs/calendar.log"
# SR-004: Append-only action log
log_action() {
mkdir -p "$(dirname "$LOGFILE")"
printf '%s\t%s\t%s\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" "$2" "$3" >> "$LOGFILE"
}
cmd="${1:-help}"
case "$cmd" in
list-calendars)
osascript -e 'tell application "Calendar"
set output to ""
repeat with c in calendars
if writable of c then
set output to output & name of c & linefeed
else
set output to output & name of c & " [read-only]" & linefeed
end if
end repeat
return output
end tell'
log_action "list-calendars" "-" "-"
;;
create-event)
# Read JSON from stdin (avoids exposing sensitive data in process list)
json=$(cat)
# Validate, normalize, and extract all fields in a single Python call.
# Outputs tab-separated values on one line.
# Tabs and newlines in string values are replaced with spaces for safe parsing.
# JSON is passed via environment variable (not pipe) because the heredoc
# already occupies stdin — a pipe would be silently discarded by bash.
validated=$(CALENDAR_JSON="$json" python3 << 'PYEOF'
import os, sys, json
try:
data = json.loads(os.environ['CALENDAR_JSON'])
except json.JSONDecodeError as e:
print(f"Error: invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
if 'summary' not in data:
print("Error: 'summary' field is required", file=sys.stderr)
sys.exit(1)
try:
summary = str(data['summary'])
calendar = str(data.get('calendar', ''))
description = str(data.get('description', ''))
recurrence = str(data.get('recurrence', ''))
iso_date = str(data.get('iso_date', ''))
offset_days = int(data.get('offset_days', 0))
hour = int(data.get('hour', 9))
minute = int(data.get('minute', 0))
duration_min = int(data.get('duration_minutes', 30))
alarm_min = int(data.get('alarm_minutes', 0))
all_day = bool(data.get('all_day', False))
except (ValueError, TypeError) as e:
print(f"Error: invalid field value: {e}", file=sys.stderr)
sys.exit(1)
# Range checks
errors = []
if not 0 <= hour <= 23: errors.append("hour must be 0-23")
if not 0 <= minute <= 59: errors.append("minute must be 0-59")
if duration_min < 0: errors.append("duration_minutes must be >= 0")
if alarm_min < 0: errors.append("alarm_minutes must be >= 0")
# Validate and normalize iso_date (ensure zero-padded YYYY-MM-DD)
if iso_date:
parts = iso_date.split('-')
if len(parts) != 3:
errors.append("iso_date must be YYYY-MM-DD")
else:
try:
y, m, d = int(parts[0]), int(parts[1]), int(parts[2])
if not (1 <= m <= 12 and 1 <= d <= 31 and y >= 1):
errors.append("iso_date has invalid date values")
else:
iso_date = f"{y:04d}-{m:02d}-{d:02d}"
except ValueError:
errors.append("iso_date must contain numeric values")
if errors:
for e in errors:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
# Safe output: replace newlines in string values (one field per line)
def safe(s):
return s.replace('\n', ' ').replace('\r', '')
fields = [
safe(summary), safe(calendar), safe(description), safe(recurrence), safe(iso_date),
str(offset_days), str(hour), str(minute), str(duration_min), str(alarm_min),
'true' if all_day else 'false'
]
for f in fields:
print(f)
PYEOF
)
# Read validated values (one field per line, handles empty fields correctly)
{
read -r summary
read -r calendar
read -r description
read -r recurrence
read -r iso_date
read -r offset_days
read -r hour
read -r minute
read -r duration_min
read -r alarm_min
read -r all_day
} <<< "$validated"
# Defense-in-depth: verify numeric fields are pure integers
for var in offset_days hour minute duration_min alarm_min; do
if ! [[ "${!var}" =~ ^-?[0-9]+$ ]]; then
echo "Error: $var must be an integer" >&2
exit 1
fi
done
# Auto-detect calendar if not specified
if [ -z "$calendar" ]; then
calendar=$(osascript -e 'tell application "Calendar" to get name of first calendar')
fi
# Execute via osascript with argv parameter passing.
# All user-provided strings are passed as typed parameters via "on run argv",
# never interpolated into executable AppleScript code. This prevents injection.
result=$(osascript - "$summary" "$description" "$calendar" "$recurrence" \
"$offset_days" "$hour" "$minute" "$duration_min" "$alarm_min" \
"$all_day" "$iso_date" <<'APPLESCRIPT'
on run argv
set evtSummary to item 1 of argv
set evtDescription to item 2 of argv
set calName to item 3 of argv
set evtRecurrence to item 4 of argv
set offsetDays to (item 5 of argv) as integer
set evtHour to (item 6 of argv) as integer
set evtMinute to (item 7 of argv) as integer
set durationMin to (item 8 of argv) as integer
set alarmMin to (item 9 of argv) as integer
set isAllDay to (item 10 of argv) is "true"
set isoDate to item 11 of argv
-- Calculate start date
if isoDate is not "" then
set startDate to current date
set year of startDate to (text 1 thru 4 of isoDate) as integer
set month of startDate to (text 6 thru 7 of isoDate) as integer
set day of startDate to (text 9 thru 10 of isoDate) as integer
set hours of startDate to evtHour
set minutes of startDate to evtMinute
set seconds of startDate to 0
else
set startDate to (current date) + offsetDays * days
set hours of startDate to evtHour
set minutes of startDate to evtMinute
set seconds of startDate to 0
end if
-- Create event
tell application "Calendar"
-- SR-001: Reject read-only calendars
if not (writable of calendar calName) then
error "Calendar '" & calName & "' is read-only. Choose a writable calendar."
end if
tell calendar calName
if isAllDay then
set newEvent to make new event with properties {summary:evtSummary, start date:startDate, end date:startDate, allday event:true, description:evtDescription}
else
set endDate to startDate + durationMin * minutes
set newEvent to make new event with properties {summary:evtSummary, start date:startDate, end date:endDate, description:evtDescription}
end if
-- Set recurrence if provided
if evtRecurrence is not "" then
set recurrence of newEvent to evtRecurrence
end if
-- Set alarm if provided
if alarmMin > 0 then
make new display alarm at end of newEvent with properties {trigger interval:-alarmMin}
end if
end tell
end tell
return "Event created: " & evtSummary
end run
APPLESCRIPT
)
log_action "create-event" "$calendar" "$summary"
echo "$result"
;;
help|*)
echo "macOS Calendar CLI"
echo ""
echo "Commands:"
echo " list-calendars List all calendars"
echo " create-event Create event from JSON (reads stdin)"
echo ""
echo "Usage:"
echo " echo '<json>' | calendar.sh create-event"
echo ""
echo "JSON fields:"
echo " summary (required) Event title"
echo " calendar Calendar name (auto-detects if omitted)"
echo " description Event notes"
echo " offset_days Days from today (default: 0)"
echo " iso_date Absolute date YYYY-MM-DD (overrides offset_days)"
echo " hour Start hour 0-23 (default: 9)"
echo " minute Start minute 0-59 (default: 0)"
echo " duration_minutes Duration in minutes (default: 30)"
echo " alarm_minutes Alert before event in minutes (0=none)"
echo " all_day true/false (default: false)"
echo " recurrence iCal RRULE (e.g. FREQ=WEEKLY;BYDAY=TU)"
;;
esac
Related skills
FAQ
How does it avoid locale date bugs?
It uses relative date math (current date + N days) so FR/EN/DE date formats do not break parsing.
What event fields are supported?
summary, calendar, description, offset_days or iso_date, hour, minute, duration_minutes, alarm_minutes, all_day, and RRULE recurrence.
What are the requirements?
macOS with Calendar.app, plus the osascript and python3 binaries.