关于 calendar-event-manager
Calendar Event Manager 是用于同时创建 macOS Calendar 日程和 Reminders 提醒的技能,规定分层声音警报及验证步骤,以减少日程设置的静默失败。
主要功能
- 01同步创建 Calendar 日程和 Reminders 提醒
- 02设置不同提示音的分层声音警报
- 03提供清晨日程的提醒时间指引
- 04使用 AppleScript 构建日期并查找日历
- 05验证创建后的警报和提醒
适用场景
- 01根据邀请或描述创建日程
- 02为即将到来的活动添加多层提醒
- 03测试 Calendar 和 Reminders 通知
- 04确认警报和提醒已实际保存
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ terrylica/ cc-skills --skill calendar-event-manager安装后在 Claude Code 中输入/calendar-event-manager确认已加载。
附带文件references/evolution-log.mdreferences/sound-reference.md
详细介绍
日历事件管理器
创建带有分级声音提醒和配套提醒事项的 macOS 日历事件,确保 Mac 和 iOS 上都不会错过事件。
自我进化技能:此技能会在使用过程中不断改进。如果说明有误、参数发生偏差,或需要使用变通方法,请立即修正此文件,不要拖延。仅针对真实且可复现的问题进行更新。
关键规则(2026-02-12 总结的重要经验)
这些规则绝不可妥协。违反其中任何一条都会让此技能失去意义。
1. 日历和提醒事项始终同时创建
每个事件都必须同时创建:
- 日历事件,包含多个
sound alarm条目(每个级别使用自定义声音) - 提醒事项,作为单独的通知渠道,至少创建 3 条
绝不能只创建其中一项。
2. 使用 sound alarm,不要使用 display alarm
-- CORRECT: audible alert with custom sound
make new sound alarm at end of sound alarms with properties {trigger interval:-60, sound name:"Glass"}
-- WRONG: silent visual banner only
make new display alarm at end of display alarms with properties {trigger interval:-60}
每个闹钟都支持自己的 sound name 属性。不同级别要使用不同声音,让用户仅凭声音就能辨别提醒级别。
3. 仅使用较长的声音(>= 1.4 秒)
短促的声音容易被错过或忽略。绝不要使用短于 1.4 秒的声音。
仅限使用以下声音:
| 声音 | 时长 | 用途 |
|---|---|---|
| Funk | 2.16s | 事件发生时(音量最大) |
| Glass | 1.65s | 提前 1 小时 |
| Pop | 1.63s | 当天早晨 / 提前 3 小时 |
| Sosumi | 1.54s | 前一天 |
| Ping | 1.50s | 提前 30 分钟 |
| Submarine | 1.49s | 替代选项 |
| Blow | 1.40s | 较早的轻柔提醒 |
禁止使用的声音: Hero、Basso、Bottle、Purr、Frog、Morse、Tink(均 < 1.4s)
4. 必须设置多个提前提醒
日历每个事件最多接受 5 个闹钟(2026-08-17 在 CalDAV 日历上验证:添加第 6 个闹钟时,较早设置的闹钟会被静默移除,且不会报错)。请谨慎分配这 5 个名额,不要将名额用于重复设置已在同一时间触发的提醒事项。
默认闹钟级别:
| 级别 | 触发时间 | 日历声音 | 提醒事项 |
|---|---|---|---|
| 提前 1 天 | -1439 min | Blow | “明天:...” |
| 前一晚 | -720 min | Sosumi | — |
| 提前 1 小时 | -60 min | Glass | (由日历提醒) |
| 提前 30 分钟 | -30 min | Ping | (由日历提醒) |
| 事件发生时 | 0 min | Funk | 到期时间提醒 |
另外创建 3 条提醒事项:“明天:”(前一天上午 9 点)、“今天:”(当天早晨)和到期时间提醒。
使用 -1439,绝不要使用 -1440。 日历会静默丢弃精确的 1440 分钟(24 小时)偏移量——回读时闹钟不会显示。已于 2026-08-17 两次验证。
-1439可以保留。
清晨事件(约上午 9:30 之前)
默认级别不适用于清晨事件。请进行调整:
- 上午 9 点触发的“当天早晨”提醒事项会在 08:30 的事件结束后才触发。将其调整到事件前约 90 分钟。
- 对于 08:30 的事件,-180 min 级别会在05:30响起,让用户提前 3 小时醒来。删除该级别;改用前一晚的 -720 级别。
5. macOS 通知设置前提条件
必须在系统设置中启用日历通知:
- 系统设置 > 通知 > 日历 > 允许通知 = 开
- 提醒样式 = 横幅或提醒
- 播放声音 = 开
使用以下命令打开:open "x-apple.systempreferences:com.apple.Notifications-Settings.extension"
TodoWrite 任务模板
模板 A:根据邀请创建事件
1. Extract event details (title, date, time, location, notes, RSVP)
2. Create Calendar event with 5-tier sound alarms (Blow, Sosumi, Glass, Ping, Funk)
3. Create 3 Reminders (TOMORROW, TODAY morning, due-time)
4. Verify event and reminders created
5. Report full schedule to user
模板 B:根据用户描述创建事件
1. Ask user for: event name, date/time, duration, location
2. Create Calendar event with 5-tier sound alarms (max Calendar allows)
3. Create 3 Reminders
4. Verify event and reminders created
5. Report full schedule to user
模板 C:测试通知设置
1. Create test Calendar event 3 min in future with sound alarms (1 min, 2 min tiers)
2. Create test Reminder 2 min in future
3. Wait for user confirmation of notifications
4. Clean up test event and reminders
AppleScript 日期构造(至关重要)
绝不要在 AppleScript 中使用 date "STRING"。 基于字符串的日期解析取决于区域设置,而且会悄无声息地产生错误结果:
| 反模式 | 结果 | 示例 |
|---|---|---|
date "April 1, 2026 at 6:00:00 PM" | 在 24 小时制系统上,“PM”会被忽略 → 06:00 | 个人会话中出现 4 次故障 |
date "2026-04-01 18:00:00" | ISO 格式被解析为一组独立数字 → 年份变成 12169 | 出现 1 次故障 |
set month 后再执行 set day to 1 | 31 日加上 4 月(30 天)→ 滚动到 5 月 1 日 | 出现 1 次故障 |
务必使用程序化方式构造日期:
-- Build date safely: day-first-then-month prevents rollover
set d to current date
set day of d to 1 -- safe floor FIRST (prevents month rollover)
set month of d to April
set year of d to 2026
set day of d to 1 -- now set actual target day
set hours of d to 18 -- 24h format, no AM/PM ambiguity
set minutes of d to 0
set seconds of d to 0
发现日历(首先运行)
tell application "Calendar"
set output to ""
repeat with c in calendars
set output to output & name of c & " (writable:" & writable of c & ")" & linefeed
end repeat
output
end tell
使用第一个 writable:true 的日历。切勿假设存在“Home”或“Calendar”。
创建完整事件(可直接复制粘贴)
tell application "Calendar"
-- Build start date programmatically
set startDate to current date
set day of startDate to 1
set month of startDate to MONTH_CONSTANT
set year of startDate to YEAR_INT
set day of startDate to DAY_INT
set hours of startDate to HOUR_24
set minutes of startDate to 0
set seconds of startDate to 0
-- Build end date (1 hour later)
set endDate to startDate + 1 * hours
tell calendar "WRITABLE_CALENDAR_NAME"
set newEvent to make new event with properties {summary:"EVENT_NAME", start date:startDate, end date:endDate, location:"LOCATION", description:"NOTES"}
tell newEvent
-- EXACTLY 5 alarms: a 6th silently evicts one of these.
-- -1439 not -1440: an exact 24h offset is silently dropped.
make new sound alarm at end of sound alarms with properties {trigger interval:-1439, sound name:"Blow"}
make new sound alarm at end of sound alarms with properties {trigger interval:-720, sound name:"Sosumi"}
make new sound alarm at end of sound alarms with properties {trigger interval:-60, sound name:"Glass"}
make new sound alarm at end of sound alarms with properties {trigger interval:-30, sound name:"Ping"}
make new sound alarm at end of sound alarms with properties {trigger interval:0, sound name:"Funk"}
end tell
end tell
reload calendars
end tell
验证(创建后务必运行)
验证不是可选项。本文件中记录的每一次故障都是静默发生的—— 所有情况下,创建步骤都返回了成功。
tell application "Calendar"
tell calendar "WRITABLE_CALENDAR_NAME"
set e to first event whose summary contains "EVENT_NAME"
set out to (summary of e) & " | " & (start date of e) & " -> " & (end date of e) & linefeed
-- Read BOTH classes: Calendar reports sound alarms back as display alarms.
set out to out & "alarms=" & ((count of display alarms of e) + (count of sound alarms of e)) & linefeed
repeat with a in display alarms of e
set out to out & " " & (trigger interval of a) & linefeed
end repeat
repeat with a in sound alarms of e
set out to out & " " & (trigger interval of a) & linefeed
end repeat
return out
end tell
end tell
确认提醒的数量以及每个触发间隔——不要只确认事件存在。被丢弃的提醒是最常见的静默故障。
日历 AppleScript 桥接的局限性(已于 2026-08-17 验证)
| 尝试 | 结果 | 应改为这样做 |
|---|---|---|
repeat with e in (every event whose summary …) | 错误 -1728,“Can't get item 1” | 先收集 uid of (every event whose …),再按 uid 逐个删除或操作 |
count of (every alarm of e) | 语法错误 -2741,不存在 alarm 类 | 分别统计 display alarms 和 sound alarms |
delete (first display alarm whose …) | 错误 -10000,AppleEvent 处理程序失败 | 无法单独删除提醒——重新创建整个事件 |
delete display alarm <index> | 错误 -10000 | 同上:重新创建事件 |
读取已创建提醒的 sound name | 报告 0 个声音提醒、N 个显示提醒 | 这是预期行为。sound name 会在 EventKit 层级持久保存;仍然要创建声音提醒 |
创建配对的提醒事项
绝不要使用
default list。 在这台机器上,它会解析到一个空的 占位列表,名称就是DEFAULT_TASK_CALENDAR_NAME(一个未替换的模板字符串)。make new reminder in (default list)会返回成功,但什么也不会写入—— 这是彻底的静默故障。先按名称找到真实列表,再明确指定该列表。
-- Discover lists FIRST; never assume.
tell application "Reminders"
set out to ""
repeat with l in lists
set out to out & name of l & " count=" & (count of reminders in l) & linefeed
end repeat
return out
end tell
tell application "Reminders"
-- Target the real list BY NAME (usually "Reminders"), not `default list`.
set targetList to list "Reminders"
-- Build date programmatically (same pattern as Calendar)
set eventDate to current date
set day of eventDate to 1
set month of eventDate to MONTH_CONSTANT
set year of eventDate to YEAR_INT
set day of eventDate to DAY_INT
set hours of eventDate to HOUR_24
set minutes of eventDate to 0
set seconds of eventDate to 0
-- CRITICAL: `copy`, not `set`. `set x to eventDate` ALIASES the same date
-- object, so mutating x silently rewrites eventDate too (this corrupted a
-- due-time reminder to 07:00 on 2026-08-17). `copy` makes a real clone.
copy eventDate to morningDate
set hours of morningDate to 7
set minutes of morningDate to 0
set seconds of morningDate to 0
copy eventDate to dayBefore
set dayBefore to dayBefore - 1 * days
set hours of dayBefore to 9
set minutes of dayBefore to 0
set seconds of dayBefore to 0
make new reminder in targetList with properties {name:"NOW: EVENT_NAME", due date:eventDate, body:"LOCATION" & linefeed & "NOTES"}
make new reminder in targetList with properties {name:"TOMORROW: EVENT_NAME", due date:dayBefore, body:"Event tomorrow! LOCATION"}
make new reminder in targetList with properties {name:"TODAY: EVENT_NAME", due date:morningDate, body:"Today! LOCATION"}
end tell
请注意,body: 必须使用 & linefeed &——AppleScript
字符串中的字面量 \n 是反斜杠和字母 n 两个字符,不是换行符。
修改后检查清单
修改此技能后:
- 声音参考表与 sound-reference.md 一致
- 文档记录了全部 5 个提醒等级及其正确声音(日历最多支持 5 个)
- BANNED 声音列表完整
- Hook 文件(
hooks/calendar-reminder-sync.ts)与技能规则一致 - AppleScript 示例使用
sound alarm,而非display alarm
参考资料
- 声音参考 - 完整的声音时长数据以及获准/拒绝列表
- Apple 日历脚本指南
执行后反思
此技能执行完毕后,关闭前请检查:
- 命令是否成功? — 如果没有,修复导致失败的指令或错误表。
- 参数或输出是否发生变化? — 如果底层工具的接口发生偏移,请更新 Usage 示例和 Parameters 表以保持一致。
- 是否需要变通方案? — 如果你不得不临时调整(更换标志、增加步骤),请更新此 SKILL.md,以便下次调用时无需采用相同的变通方案。
只有问题确实存在且可复现时才进行更新,不要基于猜测更新。