⚠️ Platform limitation (read first)
This skill runs only on macOSit uses osascript/remindctl to call the built-in "Calendar" and "Reminders" apps on the Mac to create schedules.
- ✅ The creation results are automatically synced via iCloudto your iPhone / iPad signed in with the same Apple ID.
- ❌ It cannot be used directly on Windows / Android / Linux, nor can these scripts run directly on iPhone.
- Authorization (important): When the script is run for the first time, macOS will pop up a "xxx wants to control "Calendar"/"Reminders"" dialog.Just click "OK"(only this once).Each time you switch to a different terminal app or use it for the first time in a new environment, it will pop up again—because authorization is recorded separately by "the calling terminal app" (authorizing Terminal does not mean iTerm is also authorized); just click it and there is no need to look for it in Settings. Only when the pop-up does not appear, or when you have previously clicked "Don't Allow", should you go to "System Settings › Privacy & Security › Automation" and manually turn on the Calendar / Reminders switch for the corresponding terminal.
What it does
The user says something in plain language (or sends a screenshot), and this skill:
- Parses event title, start time, end time (optional), location, event type;
- Applies default lead timeaccording to event type (plane/high-speed rail/court hearing/meeting/deadline/social each differ);
- Creates in Apple Calendar 1 event(if a similar event already exists that day,updateit instead of creating a duplicate), and creates in Reminders a set of advance reminders;
- and reports back to the user what was created.
General scenarios (the same logic, not limited to travel):
| Type | Example input | Smart default lead time |
|---|---|---|
| ✈️ Flight | "China Eastern MU5137 7/20 8:30 departing from Pudong" | 1 day before at 9 AM + 3h before + 1h before |
| 🚄 High-speed rail | "G1234 Beijing South→Shanghai Hongqiao 8/1 14:00" | 1h before + 30min before |
| ⚖️ Court hearing | "next Wednesday 14:00 court hearing" | 1 day before at 9 AM + 3h before + 1h before |
| 💼 Meeting | "meeting tomorrow morning at 10" | 1 day before at 9 AM + 1h before |
| ⏰ Deadline | "appeal period deadline on the 15th" | 3 days before + 1 day before + 9 AM on the day |
| 🍻 Social | "dinner with Mr. Zhang on Friday evening" | 1 day before at 9 AM + 2h before |
| 🎯 Other | "health check-up the morning after tomorrow" | 1h before |
Core workflow (AI execution steps)
Step 0: Read the configuration (every time)
Read $SKILL_DIR/config/config.json, and get: time zone, default calendar name default_calendar, default reminder list name default_reminder_list, lead time for each event type lead_times.
Then read $SKILL_DIR/references/lead-times.md, and master theevent type determination rulesandlead time calculation method。
Step 1: Parse the input
- Text input: extract directly from the user message the time (date + hour and minute), event, location, flight number/train number, etc.
- Screenshot input(flight ticket confirmation page, 12306, WeChat invitation, etc.): use visual capability to read out the above fields.
- Interpret the time according to
config.timezone(default Asia/Shanghai). When ambiguous (e.g., "next Wednesday" without a specified time zone, or only "afternoon"), use the most common interpretation andstate your interpretation in the receipt, so the user can correct it at a glance. - No end time given → default event duration is 60 minutes (enough for meetings/social events; for flights/high-speed rail, it is better if the duration can be inferred).
Step 2: Determine the event type
Classify into one of references/lead-times.md according to the keyword list in flight / train / court / meeting / deadline / social / default . One message may contain multiple events (e.g., a business trip = flight + meeting after arrival); handle them separately.
Step 3: Calculate the absolute time of each reminder
Take the array from config.lead_times[类型] , and convert each one into lead-times.md according to the format rules inabsolute time(ISO,Asia/Shanghai)。
User's override for this instance takes precedence: if the user explicitly says "X hours in advance" or "remind only once", then for this instance use only what the user specified and ignore the defaults.
Step 4: Duplicate check and write (update or create new)
By default, check for duplicates first, to avoid creating duplicate events when the user supplements flight/hotel/rebooking information. Only when the user explicitly says "create another one" or "record separately" should you skip the duplicate check and create a new one directly.
# 1) 查事件日期当天的已有事件(默认查 default_calendar;用户提过别的日历就查那个;指称模糊用 ALL)
$SKILL_DIR/scripts/find_event.sh "<日历名|ALL>" "<事件日期YYYY-MM-DD>"
# 2a) 唯一匹配 → 更新已有事件(第 2–6 参数传 "-" = 不改该项;备注是整段替换,补充信息要先合并原备注再写入)
$SKILL_DIR/scripts/update_event.sh "<uid>" ["<新标题>"] ["<新开始ISO>"] ["<新结束ISO>"] ["<新地点>"] ["<新备注>"]
# 定位也可用标题关键词兜底;多匹配会拒绝并列出候选。详见下文「修改已有事件」一节。
# 2b) 无匹配 → 新建
$SKILL_DIR/scripts/create_event.sh "<标题>" "<default_calendar>" "<开始ISO>" ["<结束ISO>"] ["<地点>"] ["<备注>"]
# 3) 提醒:对每个提前量建一条
$SKILL_DIR/scripts/create_reminder.sh "<标题>" "<default_reminder_list>" "<提醒绝对ISO>"
Duplicate match rules(judge each candidate in the find output one by one):
- Same day, and the title contains the same key identifier (flight number/train number/case number/activity name)or the time is close (±3 hours) and the topic is consistent → same event;
- All-day events (starting at 00:00, with no clear duration) participate in matching by "that day";
- Unique match → update; if there are multiple candidates or you are unsure → list the candidates for the user to choose,do not guess;
- 0 matches but the user says "I already created it" → expand the window to 1 day before and after and search again (
find_event.sh "<日历>" "<前一天>" "<后两天>"); - if you have taken the update path, thendo not create a new reminder again(it was most likely already created before, and creating it again = harassment); in the receipt, ask whether to also add a reminder;
- If what is being updated is an event manually created by the user, explain clearly in the receiptwhich fields were changed(time/location/notes).
Writing key points:
- One business trip = one calendar event: for the same itinerary, the outbound trip/accommodation/return trip (and multiple local arrangements) are merged by default intooneevent—start/end = outbound departure → return landing, title = itinerary name (e.g.,
XX大学 · 行业前沿讲座(西安)), and flight/hotel details are written hierarchically into the notes (✈️ Outbound… / 🏨 Accommodation… / ✈️ Return…).Do notSplit into multiple events by transportation/accommodation; reminders are still created based on key milestones within the itinerary (e.g., 3h before each flight). - Details within the itinerary are also merged into the overview: the exact time slots, locations, meeting room numbers, online meeting IDs, and agenda highlights of lectures/meetings — all go into the overview event'snotesorlocationfields,do not create new lecture/meeting sub-events, and do not create separate reminders for them either. Criterion: any "detail that occurs on the day the business trip overview event starts/ends or on a day in between" is handled by merging; only schedules that are truly independent and do not depend on any travel overview get new events. Written into DEC-006.
- Example title for a single-segment itinerary (only one flight/one meeting):
✈️ MU5137 上海浦东→北京首都 - Key fields for flight/travel entries: refer to the "Flight detailed field entry template" in
references/lead-times.md, and organize airline/cabin class/aircraft type/flight duration into the eventnotes(the notes parameter of update/create), terminal goes intolocation。Do not recordpassenger/ticket number/order number (unnecessary and raises privacy concerns). - Add distinguishing suffixes to reminder titles so users can see them clearly in the list:
✈️ MU5137 值机打包(前1天)、🚄 G1234 该去车站了(前1h)、⚖️ 开庭准备(前3h)。 - The script will automatically degrade: when
remindctlis not installed, useosascript(reminders can still be created, just cannot be queried/modified/deleted).
Step 5: Receipt
Give the user a brief list, for example:
已为你安排 ✈️ MU5137(2026-07-20 08:30,上海浦东 T2)
📅 日历事件已建 →「个人」日历
⏰ 提醒事项已建 3 条:
• 07-19 09:00 值机打包(前1天)
• 07-20 05:30 该去机场了(前3h)
• 07-20 07:30 开始登机(前1h)
(经 iCloud 同步到你的 iPhone/iPad,稍等片刻可在手机上看到)
When updating an existing event, change the receipt to "what was changed", for example:
✈️ MU5137 已改签为 08:50 起飞——更新的是你 7/20 已有的那条日历事件(未新建)
📅 改动:开始时间 08:30 → 08:50,备注补充了舱位信息
First-time setup (important)
- If
config/config.jsondoes not exist yet, copy one from the template:cp config/config.example.json config/config.json - Run the self-check to get the real calendar names and reminder list names on your machine:
bash $SKILL_DIR/scripts/setup_check.sh - Put the output'scalendar nameinto
config.json'sdefault_calendar,list nameintodefault_reminder_list. If you fill it in wrong, no error will be reported — the script will fall back to the first calendar/default list, but only the correct name will land where you want it.
config.jsonis your local configuration (ignored by .gitignore and not committed);config.example.jsonis the default template checked into the repository.
To adjust the lead time for various types of events, directly modify config.json 's lead_times(format see references/lead-times.md)。
Lead time is the personal configuration entry point: when the user expresses preferences such as "too many reminders/only 3 hours in advance/no reminder needed 1 day before", put itinto config.json 's lead_times(configured once and effective long-term), do not verbally ignore the default each time — otherwise next time in a new session a string will be created according to the default again.
Install a stronger backend (optional)
Reminders use osascriptby default (create only). After installing remindctl , you can also query/modify/delete:
brew install steipete/tap/remindctl
Whether installed or not does not affect the creation function; this skill will automatically detect it.
Script reference
| Script | Purpose |
|---|---|
scripts/create_event.sh |
Create calendar events (osascript) |
scripts/find_event.sh |
Query existing events by date window + keywords (deduplication/location, output uid and each field) |
scripts/update_event.sh |
Modify existingcalendar events (located by UID or title,- = do not change that item; create_event.sh can only create new ones) |
scripts/create_reminder.sh |
Create reminder (prefer remindctl, fall back to osascript) |
scripts/list_calendars.sh |
List all calendar names |
scripts/list_reminder_lists.sh |
List all reminder lists |
scripts/setup_check.sh |
Initial self-check: dependencies, permissions, calendar/list names |
Modify an existing event (use update_event.sh, do not rebuild with create_event.sh)
When the user says "add the itinerary to that existing schedule of mine", "change the time", or "add the hotel info", it ismodify, not create.
create_event.sh only creates; repeated calls produce duplicate events—for this kind of request always use:
$SKILL_DIR/scripts/update_event.sh "<UID 或 标题关键词>" ["<新标题>"] ["<开始>"] ["<结束>"] ["<地点>"] ["<备注>"]
Parameter rules: pass - for the 2nd–6th parameters to mean "do not change this item"; for start/end you may change only one, and fill -。
Lookup order: first exact match by UID → if no match, then match by title containing → if the title matchesmultipleevents, refuse to modify and return the candidate list (to prevent mistaken edits); in this case switch to using UID.
Look up before modifying: when you do not know the UID, first use osascript to list candidate events by keyword (including UID, start/end, calendar name), then modify precisely by UID.
Example: correct the existing "Xi'an business trip" placeholder and add the hotel info
update_event.sh "西安出差" "-" "2026-09-08 10:10" "2026-09-09 12:35" "西安 · XX大学" "去程 CA1234 …"
update_event.sh "XX酒店" "-" "-" "-" "-" "预订号 888888 · 客户经理 …"
⚠️ Two pitfalls of changing dates in AppleScript (already encountered)
- Component order: must first
set day of d to 1, then setyear / month / dayin order, and finally sethours / minutes / seconds. If the order is reversed, it may overflow because the target month does not have enough days (for example, putting day 31 into September). - Do not use subtraction to cross days: to mean "the 9th at 17:00",directlyset
day=9, hours=17;do notwrite it asday=9, hours=0then减 7 小时—subtraction rolls back across the day to the 8th at 17:00, squeezing two days of itinerary into the same day.
After modifying, be sure to re-check the start/end times with osascript to confirm that a cross-day event really crosses days.
Note
- This is an operation that modifies the user's personal calendars/reminders.Creationis safe and reversible (the user can delete it in the app); but if this skill is extended in the future to deletion/batch operations, be sure to confirm the target with the user first.
- Reminder data is personal privacy; do not send calendar/reminder contents to the network or third-party services. Parsing is done locally only.
- If the user actually wants a non-Apple calendar such as Feishu/DingTalk/Outlook, this skill does not apply—those require their own tools.