mcpField
C
calendar-event-manager

calendar-event-manager

github.com/terrylica生产力1213 次安装今天更新
代码仓库

关于 calendar-event-manager

Calendar Event Manager 是用于同时创建 macOS Calendar 日程和 Reminders 提醒的技能,规定分层声音警报及验证步骤,以减少日程设置的静默失败。

主要功能

  1. 01同步创建 Calendar 日程和 Reminders 提醒
  2. 02设置不同提示音的分层声音警报
  3. 03提供清晨日程的提醒时间指引
  4. 04使用 AppleScript 构建日期并查找日历
  5. 05验证创建后的警报和提醒

适用场景

  1. 01根据邀请或描述创建日程
  2. 02为即将到来的活动添加多层提醒
  3. 03测试 Calendar 和 Reminders 通知
  4. 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 秒的声音。

仅限使用以下声音:

声音时长用途
Funk2.16s事件发生时(音量最大)
Glass1.65s提前 1 小时
Pop1.63s当天早晨 / 提前 3 小时
Sosumi1.54s前一天
Ping1.50s提前 30 分钟
Submarine1.49s替代选项
Blow1.40s较早的轻柔提醒

禁止使用的声音: Hero、Basso、Bottle、Purr、Frog、Morse、Tink(均 < 1.4s)

4. 必须设置多个提前提醒

日历每个事件最多接受 5 个闹钟(2026-08-17 在 CalDAV 日历上验证:添加第 6 个闹钟时,较早设置的闹钟会被静默移除,且不会报错)。请谨慎分配这 5 个名额,不要将名额用于重复设置已在同一时间触发的提醒事项。

默认闹钟级别:

级别触发时间日历声音提醒事项
提前 1 天-1439 minBlow“明天:...”
前一晚-720 minSosumi—
提前 1 小时-60 minGlass(由日历提醒)
提前 30 分钟-30 minPing(由日历提醒)
事件发生时0 minFunk到期时间提醒

另外创建 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 131 日加上 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 两个字符,不是换行符。


修改后检查清单

修改此技能后:

  1. 声音参考表与 sound-reference.md 一致
  2. 文档记录了全部 5 个提醒等级及其正确声音(日历最多支持 5 个)
  3. BANNED 声音列表完整
  4. Hook 文件(hooks/calendar-reminder-sync.ts)与技能规则一致
  5. AppleScript 示例使用 sound alarm,而非 display alarm

参考资料

执行后反思

此技能执行完毕后,关闭前请检查:

  1. 命令是否成功? — 如果没有,修复导致失败的指令或错误表。
  2. 参数或输出是否发生变化? — 如果底层工具的接口发生偏移,请更新 Usage 示例和 Parameters 表以保持一致。
  3. 是否需要变通方案? — 如果你不得不临时调整(更换标志、增加步骤),请更新此 SKILL.md,以便下次调用时无需采用相同的变通方案。

只有问题确实存在且可复现时才进行更新,不要基于猜测更新。

评论

支持 Markdown