# Apple Smart Schedule

> 把一句自然语言(机票/高铁/开庭/会议/截止日期/聚会/看病等)或一张票据截图，自动变成苹果「日历」事件 + 一串按事件类型智能提前的「提醒事项」。在 macOS 上运行、经 iCloud 同步到 iPhone/iPad。当用户说「帮我加个日程/提醒」「机票 MU5137 8:30 起飞提醒我」「下周三下午开庭提前提醒」「G1234 高铁」「上诉期 15 号截止」「提前 2 小时提醒我」「把这个行程加到日历」等任何要把时间安排写进苹果日历或提醒事项的场景，都必须用本 skill。仅 macOS。

- Skill: `cat-xierluo/apple-smart-schedule` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add cat-xierluo/apple-smart-schedule`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cat-xierluo/apple-smart-schedule/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: cat-xierluo (https://skillmd.com/u/cat-xierluo)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/cat-xierluo/apple-smart-schedule

---


# apple-smart-schedule · 苹果智能日程提醒

## ⚠️ 平台限制(先读)

本 skill **仅在 macOS 上运行**：它通过 `osascript`/`remindctl` 调用 Mac 自带的「日历」和「提醒事项」App 来创建日程。

- ✅ 创建结果经 **iCloud 自动同步**到你登录同一 Apple ID 的 iPhone / iPad。
- ❌ 不能直接在 Windows / Android / Linux 上用，也不能在 iPhone 上直接运行这些脚本。
- **授权（重要）**：首次运行脚本时，macOS 会弹出「xxx 想要控制「日历」/「提醒事项」」对话框，**点「好」即可**（只此一次）。**每换一个终端 App 或新环境首次用，都会再弹一次**——因为授权是按"调用的终端 App"分别记录的（Terminal 授权了不代表 iTerm 也授权了）；点过就好，无需去设置里找。只有当弹窗没出现、或曾经点过"不允许"时，才去「系统设置 › 隐私与安全性 › 自动化」把对应终端的 Calendar / Reminders 开关手动打开。

## 它做什么

用户用大白话说一件事（或发张截图），本 skill：

1. 解析出 **事件标题、开始时间、结束时间(可选)、地点、事件类型**；
2. 按事件类型套用 **默认提前量**（飞机/高铁/开庭/会议/截止/社交 各不同）；
3. 在苹果日历建 **1 个事件**（当天已有同类事件则**更新**它，不重复新建），在提醒事项建 **一串提前提醒**；
4. 回执告诉用户建了什么。

通用场景（同一套逻辑，不限于出行）：

| 类型 | 示例输入 | 智能默认提前量 |
|---|---|---|
| ✈️ 航班 | "东航 MU5137 7/20 8:30 浦东起飞" | 前1天9点 + 前3h + 前1h |
| 🚄 高铁 | "G1234 北京南→上海虹桥 8/1 14:00" | 前1h + 前30min |
| ⚖️ 开庭 | "下周三 14:00 开庭" | 前1天9点 + 前3h + 前1h |
| 💼 会议 | "明天上午 10 点开会" | 前1天9点 + 前1h |
| ⏰ 期限 | "15 号上诉期截止" | 前3天 + 前1天 + 当天9点 |
| 🍻 社交 | "周五晚和张总吃饭" | 前1天9点 + 前2h |
| 🎯 其他 | "后天上午去体检" | 前1h |

## 核心流程（AI 执行步骤）

### 第 0 步：读配置（每次都要）

读 `$SKILL_DIR/config/config.json`，拿到：时区、默认日历名 `default_calendar`、默认提醒清单名 `default_reminder_list`、各类事件提前量 `lead_times`。
再读 `$SKILL_DIR/references/lead-times.md`，掌握**事件类型判断规则**和**提前量计算方法**。

### 第 1 步：解析输入

- **文字输入**：直接从用户消息提取 时间(日期+时分)、事件、地点、航班号/车次 等。
- **截图输入**（机票确认页、12306、微信邀约等）：用视觉能力读出上述字段。
- 时间按 `config.timezone`（默认 Asia/Shanghai）理解。含糊时（如"下周三"未指定时区、只说"下午"），按最常见理解并**在回执里点明你的理解**，让用户一眼能纠正。
- 没给结束时间 → 事件默认时长 60 分钟（会议/社交够用；航班/高铁若能推断时长更好）。

### 第 2 步：判断事件类型

按 `references/lead-times.md` 的关键词表归类到 `flight / train / court / meeting / deadline / social / default` 之一。一条消息可能含多个事件（如出差=航班+到达后会议），分别处理。

### 第 3 步：算出每条提醒的绝对时间

从 `config.lead_times[类型]` 取数组，按 `lead-times.md` 的格式规则把每条换算成**绝对时间**（ISO，Asia/Shanghai）。

**用户当次覆盖优先**：若用户明说"提前 X 小时""只提醒一次"，则当次只用用户指定的，忽略默认。

### 第 4 步：查重与写入（更新还是新建）

**默认先查重**，避免用户补充机票/酒店/改签信息时重复新建事件。用户明说「再建一个」「分开记」时才跳过查重直接新建。

```
# 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>"
```

**查重匹配规则**（对 find 输出的候选逐条判断）：

- 同一天，且 标题含相同关键标识（航班号/车次/案号/活动名）**或** 时间相近（±3 小时）且主题一致 → 同一事件；
- 全天事件（00:00 起、无明确时长）按「当天」参与匹配；
- **唯一匹配 → update**；多个候选或拿不准 → 把候选列给用户选，**不要猜**；
- 0 匹配但用户说「我已经建过了」→ 窗口扩到前后各 1 天重查（`find_event.sh "<日历>" "<前一天>" "<后两天>"`）；
- 走了更新路径就**不要再新建提醒**（很可能之前已建过，重复建=骚扰），回执里问一句要不要顺带补提醒；
- 更新的如果是用户手动建的事件，回执里说清楚**改了哪些字段**（时间/地点/备注）。

写入要点：

- **一次出差 = 一个日历事件**：同一行程的去程/住宿/返程（及当地多个安排）默认合并进**一个**事件——起止 = 去程出发 → 返程落地，标题 = 行程名（如 `XX大学 · 行业前沿讲座（西安）`），航班/酒店细节分层写进备注（✈️ 去程… / 🏨 住宿… / ✈️ 返程…）。**不要**按交通/住宿拆成多条事件；提醒仍按行程内关键节点（如每个航班前 3h）建。
- 单段行程（只有一个航班/一场会议）标题示例：`✈️ MU5137 上海浦东→北京首都`
- **航班/出行录关键字段**：参照 `references/lead-times.md` 的「航班详细字段录入模板」，把 航司/舱位/机型/飞行时长 整理进事件**备注**(update/create 的备注参数)，航站楼进**地点**。**不录**乘客/机票号/订单号（没必要、有隐私顾虑）。
- 提醒标题加区分后缀，让用户在清单里一眼看清：`✈️ MU5137 值机打包(前1天)`、`🚄 G1234 该去车站了(前1h)`、`⚖️ 开庭准备(前3h)`。
- 脚本会自动降级：未装 `remindctl` 时用 `osascript`（提醒仍能创建，只是不能查/改/删）。

### 第 5 步：回执

给用户一个简短清单，例如：

```
已为你安排 ✈️ 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，稍等片刻可在手机上看到）
```

更新已有事件时，回执换成「改了什么」，例如：

```
✈️ MU5137 已改签为 08:50 起飞——更新的是你 7/20 已有的那条日历事件（未新建）
📅 改动：开始时间 08:30 → 08:50，备注补充了舱位信息
```

## 首次使用配置（重要）

1. 若 `config/config.json` 还不存在，从模板复制一份：
   ```
   cp config/config.example.json config/config.json
   ```
2. 跑自检，拿到你机器上真实的日历名和提醒清单名：
   ```
   bash $SKILL_DIR/scripts/setup_check.sh
   ```
3. 把输出里的**日历名**填进 `config.json` 的 `default_calendar`，**清单名**填进 `default_reminder_list`。填错不会报错——脚本会兜底落到第一个日历/默认清单，但名字对才会落到你想要的地方。

> `config.json` 是你的本地配置（被 .gitignore 忽略、不提交）；`config.example.json` 是入库的默认模板。

想调整各类事件的提前量，直接改 `config.json` 的 `lead_times`（格式见 `references/lead-times.md`）。

**提前量就是个人配置入口**：用户表达「提醒太多/只要提前 3 小时/前 1 天不用提醒」这类偏好时，把它**写进 `config.json` 的 `lead_times`**（一次配置长期生效），不要每次口头忽略默认——否则下次换会话又会按默认建一串。

## 安装更强后端（可选）

提醒事项默认用 `osascript`（只能创建）。装上 `remindctl` 后还能查/改/删：

```
brew install steipete/tap/remindctl
```

装不装不影响创建功能，本 skill 会自动探测。

## 脚本参考

| 脚本 | 作用 |
|---|---|
| `scripts/create_event.sh` | 建日历事件（osascript） |
| `scripts/find_event.sh` | 按日期窗口+关键词查已有事件（查重/定位，输出 uid 和各字段） |
| `scripts/update_event.sh` | **修改已有**日历事件（按 UID 或标题定位，`-` = 不改该项；create_event.sh 只能新建） |
| `scripts/create_reminder.sh` | 建提醒（remindctl 优先，降级 osascript） |
| `scripts/list_calendars.sh` | 列出所有日历名 |
| `scripts/list_reminder_lists.sh` | 列出所有提醒清单 |
| `scripts/setup_check.sh` | 首次自检：依赖、权限、日历/清单名 |

## 修改已有事件（用 update_event.sh，别用 create_event.sh 重建）

用户说「把行程加到我已有的那个日程上」「改一下时间」「补上酒店信息」时，是**修改**不是新建。
`create_event.sh` 只会新建，重复调用会产生重复事件——这类需求一律走：

```
$SKILL_DIR/scripts/update_event.sh "<UID 或 标题关键词>" ["<新标题>"] ["<开始>"] ["<结束>"] ["<地点>"] ["<备注>"]
```

**参数规则**：第 2–6 个参数传 `-` 表示「不改这一项」；开始/结束可只改其一，另一个填 `-`。

**定位顺序**：先按 UID 精确匹配 → 匹配不到再按标题包含匹配 → 标题匹配到**多个**时拒绝修改并返回候选清单（防误改），此时应改用 UID。

**先查再改**：不知道 UID 时，先用 osascript 按关键词把候选事件（含 UID、起止、日历名）列出来，再按 UID 精确改。

**例**：把已有的「西安出差」占位改准，并补酒店信息
```
update_event.sh "西安出差" "-" "2026-09-08 10:10" "2026-09-09 12:35" "西安 · XX大学" "去程 CA1234 …"
update_event.sh "XX酒店" "-" "-" "-" "-" "预订号 888888 · 客户经理 …"
```

### ⚠️ AppleScript 改日期的两个坑（已踩过）

1. **组件顺序**：必须先 `set day of d to 1`，再依次设 `year / month / day`，最后设 `hours / minutes / seconds`。顺序反了可能因目标月天数不足而溢出（例如把 31 日塞进 9 月）。
2. **禁止用减法跨天**：想表示「9 日 17:00」就**直接**设 `day=9, hours=17`；**不要**写成 `day=9, hours=0` 再 `减 7 小时`——减法会跨天回退到 8 日 17:00，把两天的行程压进同一天。

改完务必用 osascript 复核一遍起止时间，确认跨天事件真的跨了天。

## 注意

- 这是改用户个人日历/提醒的操作。**创建**是安全的、可逆的（用户可在 App 里删）；但如本 skill 未来扩展删除/批量操作，务必先和用户确认目标。
- 提醒数据是个人隐私，不要把日历/提醒内容发到网络或第三方服务。解析只在本地完成。
- 若用户其实想要的是飞书/钉钉/Outlook 等非苹果日历，本 skill 不适用——那些需要各自的工具。

