# Apple Calendar Scheduler

> Parse natural-language schedule requests into Apple Calendar events, especially Chinese phrases such as "明早 8 点开会" or multiple events in one sentence. Use when Codex needs to extract event details, check Apple Calendar conflicts before writing, warn about overlaps, and create events with a default 30-minute reminder on macOS.

- Skill: `gainubi/apple-calendar-scheduler` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add gainubi/apple-calendar-scheduler`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gainubi/apple-calendar-scheduler/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: gainubi (https://skillmd.com/u/gainubi)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gainubi/apple-calendar-scheduler

---


# Apple Calendar Scheduler

Convert natural-language schedule requests into structured events, check Apple Calendar for overlaps, and create events only after the event data is complete enough to write safely.

## Workflow

Follow this sequence every time:

1. Parse the user's request into one or more event objects using the schema in [references/event-schema.md](references/event-schema.md).
   - Prefer [scripts/parse_schedule.py](scripts/parse_schedule.py) for Chinese natural-language requests.
2. Normalize relative time phrases into absolute ISO datetimes in the user's local timezone.
3. Fill `alert_minutes_before` with `30` when the user did not specify a reminder.
4. Infer reasonable defaults for missing fields (see Operating Rules). Only ask when truly ambiguous.
5. Check for overlaps before creating anything:
   - Use [scripts/detect_conflicts.py](scripts/detect_conflicts.py) for one or more candidate events.
   - Treat any time overlap as a conflict unless the user explicitly says to allow it.
6. If conflicts exist, stop before writing and tell the user which existing events overlap.
7. If there are no conflicts, create the events using the JXA method (see "JXA Event Creation" section). Python scripts are fallback only if JXA fails..
8. Return a compact summary of what was created, including time range, calendar name, and reminder lead time.

## Operating Rules

- Default to a 30-minute reminder.
- **Do NOT ask the user for confirmation or clarification.** Infer reasonable defaults and create events directly.
  - Missing start time for a morning event with a deadline constraint → start 1 hour before the deadline.
  - Missing duration for meals / social gatherings → default 2 hours.
  - Missing duration for meetings → default 1 hour.
  - Missing calendar → use the script default or "个人" calendar.
- Only stop and ask when the user's intent is genuinely ambiguous (e.g., conflicting instructions or multiple equally valid interpretations).
- Do not silently shift an event to avoid a conflict. Surface the conflict first.
- When the user gives multiple events in one sentence, split them into separate event objects and validate each one.
- Prefer the user's explicitly named calendar. If none is given, let the script choose its default target calendar.
- If Calendar access is denied, stop and tell the user to grant automation/calendar permission, then retry.

## Commands

List existing events in a window:

```bash
python3 scripts/list_events.py --start 2026-03-24T00:00:00+08:00 --end 2026-03-25T00:00:00+08:00
```

Check one or more candidate events for conflicts:

```bash
python3 scripts/detect_conflicts.py --input-file /tmp/events.json
```

Create events after conflict checks pass:

```bash
python3 scripts/create_event.py --input-file /tmp/events.json
```

Parse Chinese schedule text into structured events and unresolved questions:

```bash
python3 scripts/parse_schedule.py --text "明早1个小时买菜，9:30之后不行，然后晚上6点朋友到家里聚餐4小时"
```

Suggest concrete slots for unresolved items:

```bash
python3 scripts/suggest_slots.py --input-file /tmp/parsed.json
```

Run the full workflow in one shot:

```bash
python3 scripts/schedule_from_text.py --text "明早1个小时买菜，9:30之后不行，然后晚上6点朋友到家里聚餐4小时"
```

## Event Extraction Guidance

Map user language into concise event titles:

- "明早买菜 1 小时" -> `title: 买菜`
- "晚上 6 点朋友到家里聚餐" -> `title: 朋友聚餐`, `location: 家里`

Infer an end time only when duration is explicit or strongly implied:

- "明天 8:30 开会 1 小时" -> end at `09:30`
- "晚上 6 点聚餐" -> default 2 hours (18:00-20:00) unless user says otherwise

Convert constraints like "9:30 之后不行" into a scheduling constraint: infer the latest possible start time and schedule accordingly. Do not ask for confirmation.

Use the parser output conservatively:

- If `events` is non-empty and `unresolved` is empty, move on to conflict detection.
- If a segment lands in .unresolved., apply reasonable defaults first. Only ask as a last resort, or use [scripts/suggest_slots.py](scripts/suggest_slots.py) to propose concrete windows.
- When `constraints` are present without an exact start, treat them as availability hints, not a scheduled event.

## Resources

- [references/event-schema.md](references/event-schema.md): required event structure and validation rules
- [scripts/calendar_jxa.js](scripts/calendar_jxa.js): direct Calendar app bridge via JXA
- [scripts/parse_schedule.py](scripts/parse_schedule.py): parse common Chinese schedule phrases into structured events and unresolved follow-ups
- [scripts/suggest_slots.py](scripts/suggest_slots.py): turn unresolved time windows plus duration into suggested concrete slots
- [scripts/schedule_from_text.py](scripts/schedule_from_text.py): one-shot entrypoint for parse, suggestion, conflict detection, and optional creation
- [scripts/list_events.py](scripts/list_events.py): read events from Apple Calendar
- [scripts/detect_conflicts.py](scripts/detect_conflicts.py): compare candidate events with existing events
- [scripts/create_event.py](scripts/create_event.py): create events and attach reminders

---

## JXA Event Creation (Primary Method)

Python scripts require Python 3.10+ due to type union syntax. On macOS with Python 3.9, use JXA directly via `osascript -l JavaScript`.

### How to Create Events

**IMPORTANT**: Use `cal.events.push(newEvent)` — do NOT use `new app.Event({..., calendar: cal})` as it silently fails.

```bash
osascript -l JavaScript <<'JXA'
const app = Application("Calendar");
app.includeStandardAdditions = true;
const cal = app.calendars.byName("个人");

const s1 = new Date("2026-03-24T08:30:00+08:00");
const e1 = new Date("2026-03-24T09:30:00+08:00");
const ev1 = new app.Event({summary: "买菜", startDate: s1, endDate: e1});
cal.events.push(ev1);

const s2 = new Date("2026-03-24T18:00:00+08:00");
const e2 = new Date("2026-03-24T20:00:00+08:00");
const ev2 = new app.Event({summary: "朋友聚餐", startDate: s2, endDate: e2});
cal.events.push(ev2);

JSON.stringify({ok: true, created: 2});
JXA
```

### How to Delete Events

```bash
osascript -l JavaScript <<'JXA'
const app = Application("Calendar");
const cal = app.calendars.byName("个人");
const events = cal.events();
events.filter(e => e.summary() === "买菜").forEach(e => e.delete());
JSON.stringify({ok: true});
JXA
```

### Notes

- `new Date("2026-03-24T08:30:00+08:00")` works correctly in JXA; `toISOString()` shows UTC but Calendar displays in local time.
- Always verify with `list_events.py` after creation.
- When the Python scripts fail with `TypeError: unsupported operand type(s) for |`, fall back to JXA directly.

