sophhub 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/package.json +1 -1
  2. package/skills/consensus/skill.json +20 -0
  3. package/skills/consensus/src/SKILL.md +93 -0
  4. package/skills/deepwiki/skill.json +20 -0
  5. package/skills/deepwiki/src/SKILL.md +45 -0
  6. package/skills/deepwiki/src/_meta.json +6 -0
  7. package/skills/deepwiki/src/scripts/deepwiki.js +135 -0
  8. package/skills/feishu-bitable/skill.json +20 -0
  9. package/skills/feishu-bitable/src/CHECKLIST.md +150 -0
  10. package/skills/feishu-bitable/src/README.md +178 -0
  11. package/skills/feishu-bitable/src/SKILL.md +113 -0
  12. package/skills/feishu-bitable/src/_meta.json +6 -0
  13. package/skills/feishu-bitable/src/api.js +381 -0
  14. package/skills/feishu-bitable/src/bin/cli.js +284 -0
  15. package/skills/feishu-bitable/src/description.md +143 -0
  16. package/skills/feishu-bitable/src/examples/create-records.json +52 -0
  17. package/skills/feishu-bitable/src/examples/create-table.json +64 -0
  18. package/skills/feishu-bitable/src/package-lock.json +324 -0
  19. package/skills/feishu-bitable/src/package.json +33 -0
  20. package/skills/feishu-bitable/src/publish-config.json +14 -0
  21. package/skills/feishu-bitable/src/test-simple.js +61 -0
  22. package/skills/feishu-bitable/src/utils.js +261 -0
  23. package/skills/flight-booking/skill.json +9 -2
  24. package/skills/flight-booking/src/scripts/flight_booking.py +2 -1
  25. package/skills/google-maps/skill.json +20 -0
  26. package/skills/google-maps/src/SKILL.md +237 -0
  27. package/skills/google-maps/src/_meta.json +6 -0
  28. package/skills/google-maps/src/lib/map_helper.py +912 -0
  29. package/skills/large-task-router/skill.json +20 -0
  30. package/skills/large-task-router/src/SKILL.md +79 -0
  31. package/skills/large-task-router/src/templates/plan.md +74 -0
  32. package/skills/skillhub/skill.json +11 -4
  33. package/skills/skillhub/src/SKILL.md +11 -1
  34. package/skills/sophnet-dailynews/skill.json +20 -0
  35. package/skills/sophnet-dailynews/src/SKILL.md +179 -0
  36. package/skills/sophnet-dailynews/src/cache.json +151 -0
  37. package/skills/sophnet-dailynews/src/sources.json +230 -0
  38. package/skills/sophnet-schedule/skill.json +20 -0
  39. package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -0
  40. package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -0
  41. package/skills/sophnet-schedule/src/SKILL.md +1050 -0
  42. package/skills/sophnet-schedule/src/_meta.json +6 -0
  43. package/skills/sophnet-schedule/src/api/__init__.py +0 -0
  44. package/skills/sophnet-schedule/src/api/models.py +245 -0
  45. package/skills/sophnet-schedule/src/apps/add_event.py +237 -0
  46. package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -0
  47. package/skills/sophnet-schedule/src/apps/check_roc.py +246 -0
  48. package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -0
  49. package/skills/sophnet-schedule/src/apps/import_events.py +216 -0
  50. package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -0
  51. package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -0
  52. package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -0
  53. package/skills/sophnet-schedule/src/compat.py +66 -0
  54. package/skills/sophnet-schedule/src/config/__init__.py +0 -0
  55. package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -0
  56. package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -0
  57. package/skills/sophnet-schedule/src/config/settings.py +133 -0
  58. package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -0
  59. package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -0
  60. package/skills/sophnet-schedule/src/gcal/__init__.py +0 -0
  61. package/skills/sophnet-schedule/src/gcal/client.py +374 -0
  62. package/skills/sophnet-schedule/src/gcal/models.py +91 -0
  63. package/skills/sophnet-schedule/src/requirements.txt +6 -0
  64. package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -0
  65. package/skills/sophnet-schedule/src/server.py +669 -0
  66. package/skills/sophnet-schedule/src/services/__init__.py +0 -0
  67. package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -0
  68. package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -0
  69. package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -0
  70. package/skills/sophnet-schedule/src/services/event_classifier.py +100 -0
  71. package/skills/sophnet-schedule/src/services/event_diff.py +160 -0
  72. package/skills/sophnet-schedule/src/services/google_integration.py +500 -0
  73. package/skills/sophnet-schedule/src/services/job_store.py +100 -0
  74. package/skills/sophnet-schedule/src/services/local_event_store.py +266 -0
  75. package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -0
  76. package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -0
  77. package/skills/sophnet-schedule/src/services/table_parser.py +286 -0
  78. package/skills/sophnet-schedule/src/services/task_builder.py +167 -0
  79. package/skills/sophnet-schedule/src/services/time_window.py +72 -0
  80. package/skills/sophnet-stock/skill.json +20 -0
  81. package/skills/sophnet-stock/src/App-Plan.md +442 -0
  82. package/skills/sophnet-stock/src/README.md +214 -0
  83. package/skills/sophnet-stock/src/SKILL.md +236 -0
  84. package/skills/sophnet-stock/src/TODO.md +394 -0
  85. package/skills/sophnet-stock/src/_meta.json +6 -0
  86. package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -0
  87. package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -0
  88. package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -0
  89. package/skills/sophnet-stock/src/docs/README.md +95 -0
  90. package/skills/sophnet-stock/src/docs/USAGE.md +465 -0
  91. package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -0
  92. package/skills/sophnet-stock/src/scripts/dividends.py +365 -0
  93. package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -0
  94. package/skills/sophnet-stock/src/scripts/portfolio.py +548 -0
  95. package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -0
  96. package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -0
  97. package/skills/sophnet-stock/src/scripts/watchlist.py +336 -0
  98. package/skills/xiaohongshu/skill.json +20 -0
  99. package/skills/xiaohongshu/src/SKILL.md +91 -0
  100. package/skills/xiaohongshu/src/_meta.json +6 -0
  101. package/skills/xiaohongshu/src/assets/card.html +216 -0
  102. package/skills/xiaohongshu/src/assets/cover.html +82 -0
  103. package/skills/xiaohongshu/src/assets/example.md +84 -0
  104. package/skills/xiaohongshu/src/assets/styles.css +318 -0
  105. package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -0
  106. package/skills/xiaohongshu/src/scripts/sign_server.py +158 -0
  107. package/skills/xiaohongshu/src/scripts/stealth.min.js +7 -0
  108. package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -0
  109. package/skills/xiaohongshu/src/workflow.py +185 -0
@@ -0,0 +1,1050 @@
1
+ ---
2
+ name: sophnet-schedule
3
+ description: >
4
+ Sophnet 日程提醒:Google Calendar 实时同步、本地托底存储、定时提醒、日程变化监控、固定任务管理。
5
+ 全自动运行,无需用户每日操作;同时支持用户主动查询日程、检查冲突、管理任务。
6
+ 支持双模式:已授权 Google Calendar 时云端同步,未授权时本地存储,功能完全可用。
7
+ 触发场景:用户说"今天有什么安排"、"查一下日程冲突"、"帮我加个定时任务"、
8
+ "手动生成今日计划"、"有没有日程变化"等。
9
+ metadata:
10
+ {
11
+ "openclaw":
12
+ {
13
+ "emoji": "⏰",
14
+ "requires":
15
+ {
16
+ "bins": ["python3", "uv"],
17
+ "python_packages": ["requests", "pyyaml", "urllib3"],
18
+ },
19
+ },
20
+ }
21
+ ---
22
+
23
+ ## 加载时说明
24
+
25
+ 当用户启用此 skill 时,向用户发送以下欢迎语(语气友好,使用 Markdown):
26
+
27
+ > ⏰ **自动日程调度已就绪!** 每天自动运行:
28
+ >
29
+ > - **07:50** 生成今日计划 + 注册提醒
30
+ > - **08:00** 推送今日日程早报
31
+ > - **全天** 每小时监控日程变化(有变化才通知)
32
+ > - **会前提醒** 会议提前 2 分钟、运动/饭局提前 15 分钟自动提醒
33
+ > - **21:00** 推送今日工作总结
34
+ >
35
+ > 💡 **支持 Google Calendar + 本地托底**:
36
+ > - 已授权 Google Calendar → 日程自动同步到云端
37
+ > - 未授权 → 日程保存到本地存储,功能完全可用
38
+ >
39
+ > 你也可以随时说:
40
+ > - 📝 **"明天下午3点牙医预约"** → 我帮你添加日程
41
+ > - 📅 **"今天有什么安排"** → 查询今日日程
42
+ > - ⚠️ **"有没有日程冲突"** → 检测时间重叠
43
+ > - ➕ **"帮我加个定时任务"** → 新增每日提醒
44
+
45
+ ---
46
+
47
+ # Sophnet 日程提醒
48
+
49
+ Sophnet 日程提醒系统。基于 Google Calendar API 自动同步日程,配置驱动,无需每日手动操作。
50
+
51
+ ---
52
+
53
+ ## 工作模式
54
+
55
+ 系统支持两种工作模式,自动选择:
56
+
57
+ | 模式 | 条件 | 行为 |
58
+ |------|------|------|
59
+ | **Google 模式** | 已完成 Google OAuth 授权(`token.json` 存在且有效) | 日程写入 Google Calendar,数据云端同步 |
60
+ | **本地模式** | 未完成 Google OAuth 授权 | 日程写入本地存储(`data/local_events.json`),作为托底方案 |
61
+
62
+ **模式切换逻辑**:
63
+ - 首次使用时,如果没有 Google Calendar 授权,系统自动使用本地模式
64
+ - 完成授权后,可以通过"同步本地事件到 Google"将本地已有日程推送到 Google Calendar
65
+ - 后续新日程会自动写入 Google Calendar
66
+
67
+ **Agent 操作要点**:
68
+ - 调用 `add_event.py` 后,检查返回 JSON 中的 `provider` 字段
69
+ - `provider == "google"` → 告知用户"已写入 Google Calendar"
70
+ - `provider == "local"` → 告知用户"已写入本地存储",并提示可授权 Google Calendar
71
+
72
+ ---
73
+
74
+ ## 一、自动化功能概览
75
+
76
+ 以下任务由 OpenClaw Cron 按时自动执行,**用户无需任何操作**:
77
+
78
+ | 时间 | 任务 | 说明 |
79
+ |------|------|------|
80
+ | 07:50 | 每日计划生成 | 拉取 GCal 日程,注册今日提醒,生成 `daily/cron-YYYYMMDD.md` |
81
+ | 08:00 | 日程早报推送 | 推送今日全部日程到 Telegram |
82
+ | 10:00 | 喝水提醒 | 定时健康提醒 |
83
+ | 每小时整点(8:00–20:00) | 日程变化监控 | 对比快照,有变化时通知,无变化不打扰 |
84
+ | 21:00 | 今日工作总结 | 整理当天完成事项 |
85
+
86
+ **会前提醒规则**(从 `config/reminder_rules.yaml` 加载):
87
+
88
+ - 会议:提前 **2 分钟**
89
+ - 运动:提前 **15 分钟**
90
+ - 饭局/宴会:提前 **15 分钟**
91
+ - 其他:提前 **5 分钟**(默认)
92
+
93
+ ---
94
+
95
+ ## 二、用户交互指令
96
+
97
+ ### 2.1 自然语言输入日程 → 写入 Google Calendar
98
+
99
+ **触发词**:用户用自然语言描述一个新日程,如:
100
+ - "明天下午3点牙医预约"
101
+ - "下周六上午10点亲子游泳课,提前30分钟提醒我"
102
+ - "4月5号早上7点跑步,45分钟"
103
+ - "帮我记一下后天下午2点到4点的家庭聚餐,小区附近"
104
+
105
+ **Agent 必须主动进入录入流程,不要等用户说"帮我加到日历"。**
106
+
107
+ #### Step 1:解析自然语言,展示确认卡片
108
+
109
+ 从用户的话中提取以下字段:
110
+
111
+ | 字段 | 解析规则 |
112
+ |------|----------|
113
+ | 事件名称 | 用户描述的事项 |
114
+ | 日期 | 今天/明天/后天 → 计算具体日期;周X/下周X → 计算日期;M月D日 → 当年日期 |
115
+ | 开始时间 | 上午/下午+时间 → 24小时制;X点 = X:00;X点半 = X:30;X点Y分 = X:Y |
116
+ | 结束时间 | 明确说了结束时间则用;说了时长则推算;都没说默认 1 小时 |
117
+ | 地点 | 提到地点则填写;未提则留空 |
118
+ | 提醒时间 | 用户明确要求提前X分钟 → 使用该值;否则按事件类型默认(会议2分钟,运动/饭局15分钟,其他5分钟) |
119
+ | 备注 | 其他补充说明 |
120
+
121
+ 向用户展示确认卡片:
122
+
123
+ ```
124
+ 📅 我帮你记录了这条日程,请确认:
125
+
126
+ | 字段 | 内容 |
127
+ |----------|-------------------------|
128
+ | 事件 | 牙医预约 |
129
+ | 日期 | 2026-04-16(周四) |
130
+ | 时间 | 15:00 – 16:00 |
131
+ | 地点 | 龙湖口腔 |
132
+ | 提前提醒 | 15 分钟(14:45 提醒) |
133
+ | 备注 |(未填) |
134
+
135
+ ✅ 确认写入 Google Calendar ✏️ 修改某项 ❌ 取消
136
+ ```
137
+
138
+ **等待用户确认后再执行 Step 2。**
139
+
140
+ #### Step 2:调用 add_event.py 写入日程
141
+
142
+ 用户确认后(或说"确认"/"好的"/"写入"等),执行:
143
+
144
+ ```bash
145
+ python3 {SKILL_BASE}/apps/add_event.py \
146
+ --summary "牙医预约" \
147
+ --date "2026-04-16" \
148
+ --start "15:00" \
149
+ --end "16:00" \
150
+ --location "龙湖口腔" \
151
+ --remind 15
152
+ ```
153
+
154
+ **参数说明**:
155
+ - `--summary`:事件标题(必填)
156
+ - `--date`:日期 YYYY-MM-DD(必填)
157
+ - `--start`:开始时间 HH:MM(必填)
158
+ - `--end`:结束时间 HH:MM(与 `--duration` 二选一)
159
+ - `--duration`:持续分钟数(默认 60)
160
+ - `--location`:地点(可选)
161
+ - `--description`:备注(可选)
162
+ - `--remind`:提前提醒分钟数(0 = 不注册提醒)
163
+ - `--dry-run`:只打印 payload,不写入(测试用)
164
+
165
+ **重要**:脚本会根据当前是否有有效的 Google Calendar 授权自动选择:
166
+ - 已授权 → 写入 Google Calendar
167
+ - 未授权 → 写入本地存储(本地托底)
168
+
169
+ 返回 JSON 中的关键字段:
170
+ - `provider`:`"google"` 或 `"local"`,指示写入位置
171
+ - `sync_state`:同步状态(本地模式下为 `"local_only"`)
172
+ - `event_id`:事件 ID
173
+ - `reminder_registered`:提醒是否成功注册(`true`/`false`)
174
+ - `reminder_existed`:提醒是否已存在(`true`/`false`)
175
+ - `reminder_error`:提醒注册错误信息(如果有)
176
+
177
+ #### Step 3:读取输出,告知结果
178
+
179
+ 脚本输出 JSON,Agent 读取后根据 `provider` 和提醒注册状态向用户回复:
180
+
181
+ **成功 - 写入 Google Calendar,提醒已注册**(当 `provider == "google"` 且 `reminder_registered == true`):
182
+ ```
183
+ ✅ 已写入 Google Calendar!
184
+
185
+ 📅 **牙医预约**
186
+ 🗓 2026年4月16日 15:00 – 16:00
187
+ 📍 龙湖口腔
188
+ ⏰ 将在 14:45 提醒你(提前 15 分钟)
189
+ ```
190
+
191
+ **成功 - 写入 Google Calendar,提醒注册失败**(当 `provider == "google"` 但 `reminder_registered == false`):
192
+ ```
193
+ ✅ 已写入 Google Calendar!
194
+
195
+ 📅 **牙医预约**
196
+ 🗓 2026年4月16日 15:00 – 16:00
197
+ 📍 龙湖口腔
198
+
199
+ ⚠️ 提醒注册失败:{reminder_error 内容}
200
+ 日程已保存,但不会收到自动提醒。
201
+ ```
202
+
203
+ **成功 - 写入本地存储,提醒已注册**(当 `provider == "local"` 且 `reminder_registered == true`):
204
+ ```
205
+ ✅ 已写入本地存储!
206
+
207
+ 📅 **牙医预约**
208
+ 🗓 2026年4月16日 15:00 – 16:00
209
+ 📍 龙湖口腔
210
+ ⏰ 将在 14:45 提醒你(提前 15 分钟)
211
+
212
+ 💡 提示:当前未连接 Google Calendar,日程已保存到本地。
213
+ 如需同步到 Google Calendar,请完成 Google OAuth 授权。
214
+ ```
215
+
216
+ **成功 - 写入本地存储,提醒注册失败**(当 `provider == "local"` 但 `reminder_registered == false`):
217
+ ```
218
+ ✅ 已写入本地存储!
219
+
220
+ 📅 **牙医预约**
221
+ 🗓 2026年4月16日 15:00 – 16:00
222
+ 📍 龙湖口腔
223
+
224
+ ⚠️ 提醒注册失败:{reminder_error 内容}
225
+ 日程已保存到本地,但不会收到自动提醒。
226
+ ```
227
+
228
+ **失败**(读取 `"ok": false` 和 `"error"` 字段):
229
+ ```
230
+ ❌ 写入失败:{error 字段内容}
231
+
232
+ 可能原因:
233
+ - Google Calendar token 已过期 → 运行 python3 {SKILL_BASE}/scripts/setup_gcal_token.py 重新授权
234
+ - 网络问题(需要代理)→ 检查 .env 中的 HTTP_PROXY 配置
235
+ ```
236
+
237
+ #### 自然语言解析参考表
238
+
239
+ | 用户说 | 解析结果 |
240
+ |--------|----------|
241
+ | `今天` | 当前日期 |
242
+ | `明天` | 当前日期 +1 |
243
+ | `后天` | 当前日期 +2 |
244
+ | `下周五` | 下一个周五的日期 |
245
+ | `4月5号` / `4/5` | 2026-04-05 |
246
+ | `下午3点` | 15:00 |
247
+ | `上午10点半` | 10:30 |
248
+ | `晚上8点` | 20:00 |
249
+ | `两小时` | duration=120 |
250
+ | `45分钟` | duration=45 |
251
+ | `到下午5点` | end=17:00 |
252
+ | `提前半小时` | remind=30 |
253
+ | `提前10分钟` | remind=10 |
254
+
255
+ #### 示例对话
256
+
257
+ **用户**:4月16日下午3点牙医预约,龙湖口腔,提前15分钟提醒我
258
+
259
+ **Bot**:
260
+ ```
261
+ 📅 确认日程:
262
+
263
+ | 字段 | 内容 |
264
+ |----------|-------------------------|
265
+ | 事件 | 牙医预约 |
266
+ | 日期 | 2026-04-16(周四) |
267
+ | 时间 | 15:00 – 16:00 |
268
+ | 地点 | 龙湖口腔 |
269
+ | 提前提醒 | 15 分钟(14:45 提醒) |
270
+
271
+ 确认添加?
272
+ ```
273
+
274
+ **用户**:确认
275
+
276
+ **Bot**(如果已连接 Google Calendar,提醒注册成功):
277
+ ```
278
+ ✅ 已写入 Google Calendar!
279
+
280
+ 📅 **牙医预约**
281
+ 🗓 2026年4月16日 15:00 – 16:00
282
+ 📍 龙湖口腔
283
+ ⏰ 将在 14:45 提醒你(提前 15 分钟)
284
+ ```
285
+
286
+ **Bot**(如果已连接 Google Calendar,但提醒注册失败):
287
+ ```
288
+ ✅ 已写入 Google Calendar!
289
+
290
+ 📅 **牙医预约**
291
+ 🗓 2026年4月16日 15:00 – 16:00
292
+ 📍 龙湖口腔
293
+
294
+ ⚠️ 提醒注册失败:无法写入任务文件(权限不足)
295
+ 日程已保存,但不会收到自动提醒。
296
+ 请手动设置提醒或检查 OpenClaw Cron 配置。
297
+ ```
298
+
299
+ **Bot**(如果未连接 Google Calendar,使用本地托底):
300
+ ```
301
+ ✅ 已写入本地存储!
302
+
303
+ 📅 **牙医预约**
304
+ 🗓 2026年4月16日 15:00 – 16:00
305
+ 📍 龙湖口腔
306
+ ⏰ 将在 14:45 提醒你(提前 15 分钟)
307
+
308
+ 💡 提示:当前未连接 Google Calendar,日程已保存到本地。
309
+ 如需同步到 Google Calendar,请完成 Google OAuth 授权。
310
+ ```
311
+
312
+ ---
313
+
314
+ **用户**:4月5号早上7点跑步,45分钟
315
+
316
+ **Bot**:
317
+ ```
318
+ 📅 确认日程:
319
+
320
+ | 字段 | 内容 |
321
+ |----------|-------------------------|
322
+ | 事件 | 跑步 |
323
+ | 日期 | 2026-04-05(周日) |
324
+ | 时间 | 07:00 – 07:45 |
325
+ | 地点 |(未填) |
326
+ | 提前提醒 | 15 分钟(06:45 提醒,运动默认值)|
327
+
328
+ 确认写入?
329
+ ```
330
+
331
+ ---
332
+
333
+ ### 2.2 发送文件导入日程(Excel / CSV / Word / PDF)
334
+
335
+ **触发词**:用户发送 Excel、CSV、Word、PDF 文件,或说"帮我导入这个日程表"。
336
+
337
+ **收到文件后立即主动搜索,不要等用户提供路径。**
338
+
339
+ #### Step 1:定位文件路径
340
+
341
+ ```bash
342
+ # 渠道上传的文件下载到 media/inbound/
343
+ ls -t <workspace>/media/inbound/ 2>/dev/null \
344
+ | grep -iE '\.(xlsx|xls|csv|docx|pptx|pdf|html)$' | head -3
345
+
346
+ # 兜底:用户常用目录
347
+ ls -t ~/Downloads/ ~/Desktop/ 2>/dev/null \
348
+ | grep -iE '\.(xlsx|xls|csv|docx|pptx|pdf|html)$' | head -3
349
+ ```
350
+
351
+ 找到候选文件后,展示给用户确认:"找到以下文件,是您刚上传的吗?",确认后再转换。
352
+
353
+ #### Step 2:文件转 Markdown
354
+
355
+ ```bash
356
+ uvx "markitdown[all]" <file_path> -o /tmp/schedule_converted.md
357
+ ```
358
+
359
+ 支持格式:Excel(`.xlsx/.xls`)、CSV(`.csv`)、Word(`.docx`)、PowerPoint(`.pptx`)、PDF(`.pdf`)、HTML(`.html`)
360
+
361
+ > 需要服务器已安装 `uv`
362
+
363
+ #### Step 3:解析 Markdown 表格,提取事件
364
+
365
+ ```bash
366
+ python3 - << 'EOF'
367
+ import sys
368
+ sys.path.insert(0, "{SKILL_BASE}")
369
+ from services.table_parser import parse_events_from_markdown
370
+ import json
371
+
372
+ with open("/tmp/schedule_converted.md", encoding="utf-8") as f:
373
+ content = f.read()
374
+
375
+ events = parse_events_from_markdown(content)
376
+ print(json.dumps(events, ensure_ascii=False, indent=2))
377
+ EOF
378
+ ```
379
+
380
+ `table_parser` 支持自动识别非标准列名(如"会期"→日期、"主旨"→事项、"起始"→开始时间)。如列名识别不准,使用 `--column-map` 参数手动映射。
381
+
382
+ #### Step 4:展示识别结果,用户确认
383
+
384
+ ```
385
+ 从文件中识别到以下日程,确认写入 Google Calendar?
386
+
387
+ | 事项 | 日期 | 时间 | 地点 |
388
+ |--------------|------------|---------------|---------|
389
+ | 送小孩 | 2026-04-01 | 09:00–10:00 | 学校 |
390
+ | 买菜 | 2026-04-01 | 14:00–15:30 | 菜市场 |
391
+
392
+ ✅ 全部写入 ✏️ 修改某项 ❌ 取消
393
+ ```
394
+
395
+ #### Step 5:批量写入日程
396
+
397
+ 用户确认后,将 Step 3 输出的 JSON 传给 `import_events.py`:
398
+
399
+ ```bash
400
+ python3 {SKILL_BASE}/apps/import_events.py \
401
+ --json '<Step 3 输出的 JSON 数组>' \
402
+ --remind-default 15
403
+ ```
404
+
405
+ 读取输出,告知用户结果:
406
+
407
+ **全部成功,提醒全部注册成功**:
408
+ ```
409
+ ✅ 成功写入 2 条日程到 Google Calendar!
410
+
411
+ - 送小孩 2026-04-01 09:00–10:00 学校
412
+ - 买菜 2026-04-01 14:00–15:30 菜市场
413
+
414
+ ⏰ 已同时注册 OpenClaw 提醒任务(提前 15 分钟)
415
+ ```
416
+
417
+ **部分提醒注册失败**:
418
+ ```
419
+ ✅ 成功写入 2 条日程到 Google Calendar!
420
+
421
+ - 送小孩 2026-04-01 09:00–10:00 学校
422
+ - 买菜 2026-04-01 14:00–15:30 菜市场
423
+
424
+ ⚠️ 部分提醒注册失败:
425
+ - 买菜:提醒注册失败(错误信息)
426
+ ```
427
+
428
+ **本地模式**:
429
+ ```
430
+ ✅ 成功写入 2 条日程到本地存储!
431
+
432
+ - 送小孩 2026-04-01 09:00–10:00 学校
433
+ - 买菜 2026-04-01 14:00–15:30 菜市场
434
+
435
+ ⏰ 已同时注册 OpenClaw 提醒任务(提前 15 分钟)
436
+
437
+ 💡 提示:当前未连接 Google Calendar,日程已保存到本地。
438
+ ```
439
+
440
+ **返回 JSON 字段说明**:
441
+ - `added`:成功写入的事件数
442
+ - `failed`:失败的事件数
443
+ - `results`:每个事件的结果数组
444
+ - `reminder_registered`:提醒是否成功注册
445
+ - `reminder_existed`:提醒是否已存在
446
+ - `reminder_error`:提醒注册错误信息(如果有)
447
+
448
+ ---
449
+
450
+ ### 2.3 发送截图/图片导入日程(OCR)
451
+
452
+ **触发词**:用户发送日程截图(手机截图、扫描件等),或说"帮我识别这张日程图片"。
453
+
454
+ **收到图片消息后立即主动搜索路径,不要等用户再次说明。**
455
+
456
+ #### Step 1:定位图片路径
457
+
458
+ ```bash
459
+ # 渠道上传的图片下载到 media/inbound/
460
+ ls -t <workspace>/media/inbound/ 2>/dev/null \
461
+ | grep -iE '\.(jpg|jpeg|png|webp|bmp|gif|pdf)$' | head -3
462
+
463
+ # 兜底:用户常用目录
464
+ ls -t ~/Downloads/ ~/Pictures/ ~/Desktop/ 2>/dev/null \
465
+ | grep -iE '\.(jpg|jpeg|png|webp|bmp|gif|pdf)$' | head -3
466
+ ```
467
+
468
+ #### Step 2:OCR 识别
469
+
470
+ ```bash
471
+ uv run /app/skills/sophnet-image-ocr/scripts/ocr.py <image_path>
472
+ ```
473
+
474
+ > 依赖 `sophnet-image-ocr` skill,请确保已安装
475
+
476
+ #### Step 3:Agent 理解 OCR 输出,提取日程事项
477
+
478
+ OCR 输出通常是非标准 Markdown 或纯文本,Agent 直接理解表格结构,提取:日期、开始时间、结束时间、事项名称、地点。字段缺失时使用默认值。
479
+
480
+ 若 OCR 结果是标准 Markdown 表格,可用 `table_parser` 辅助:
481
+
482
+ ```bash
483
+ python3 - << 'EOF'
484
+ import sys, json
485
+ sys.path.insert(0, "{SKILL_BASE}")
486
+ from services.table_parser import parse_events_from_markdown
487
+ ocr_text = """<OCR 输出内容粘贴到这里>"""
488
+ events = parse_events_from_markdown(ocr_text)
489
+ print(json.dumps(events, ensure_ascii=False, indent=2))
490
+ EOF
491
+ ```
492
+
493
+ #### Step 4:展示确认卡片,Step 5:批量写入(同 2.2)
494
+
495
+ ---
496
+
497
+ ### 2.4 查询今日日程
498
+
499
+ **触发词**:用户说"今天有什么安排"、"查一下今天的日程"、"今天有啥"等。
500
+
501
+ **执行步骤**:
502
+
503
+ ```bash
504
+ cd {SKILL_BASE}
505
+ python3 apps/generate_daily_plan.py --dry-run
506
+ ```
507
+
508
+ 同时展示今日日程概览(Markdown 表格,时间升序):
509
+
510
+ ```
511
+ 📅 **今日日程(M月D日 周X)**
512
+
513
+ | 时间 | 事项 | 地点 | 类型 | 提醒时间 |
514
+ |------|------|------|------|----------|
515
+ | 07:30–08:15 | 晨跑 | 小区公园 | sport | 07:15 |
516
+ | 15:00–16:00 | 牙医预约 | 龙湖口腔 | other | 14:45 |
517
+ | 18:30–20:00 | 家庭聚餐 | 小区附近餐厅 | meal | 18:15 |
518
+ ```
519
+
520
+ 若有冲突,紧跟展示:
521
+
522
+ ```
523
+ ⚠️ **发现日程冲突:**
524
+
525
+ - ⚠️ 冲突:【亲子游泳课】(09:00–10:30) 与 【去超市买菜】(09:30–10:30) 重叠 60 分钟
526
+ 建议:调整其中一项,或分开处理。
527
+ ```
528
+
529
+ ### 2.5 查询明日/本周日程
530
+
531
+ **触发词**:用户说"明天有什么安排"、"本周日程"等。
532
+
533
+ **执行步骤**:
534
+
535
+ ```bash
536
+ # 今天 + 明天
537
+ python3 {SKILL_BASE}/apps/generate_daily_plan.py --days 2 --dry-run
538
+ ```
539
+
540
+ 如需查询本周,将 `--days` 设为距本周末的天数(如今天周一,设 `--days 5`),再从结果中筛选并展示。
541
+
542
+ ### 2.6 检查日程冲突
543
+
544
+ **触发词**:用户说"有没有日程冲突"、"检查冲突"等。
545
+
546
+ **执行步骤**:
547
+
548
+ ```bash
549
+ python3 {SKILL_BASE}/apps/generate_daily_plan.py --dry-run
550
+ ```
551
+
552
+ 从输出中提取冲突信息,用以下格式展示:
553
+
554
+ ```
555
+ ⚠️ **日程冲突检测结果**
556
+
557
+ (若有冲突)
558
+ - ⚠️ 冲突:【A】(09:00–10:00) 与 【B】(09:30–10:30) 重叠 30 分钟
559
+ - ⚡ 紧接:【B】(10:00–11:00) 结束后仅 3 分钟 【C】(11:03–12:00) 就开始
560
+
561
+ (若无冲突)
562
+ ✅ 今日日程无冲突。
563
+ ```
564
+
565
+ ### 2.7 检查日程变化
566
+
567
+ **触发词**:用户说"有没有日程变化"、"日历有没有更新"等。
568
+
569
+ **执行步骤**:
570
+
571
+ ```bash
572
+ python3 {SKILL_BASE}/apps/monitor_calendar_changes.py --dry-run
573
+ ```
574
+
575
+ - 有变化时:展示新增 / 取消 / 修改的事项
576
+ - 无变化时:回复"✅ 今日日程无变化。"
577
+
578
+ ### 2.8 手动触发今日计划生成
579
+
580
+ **触发词**:用户说"手动生成今日计划"、"重新注册今天的提醒"等。
581
+
582
+ **执行步骤**:
583
+
584
+ ```bash
585
+ python3 {SKILL_BASE}/apps/generate_daily_plan.py
586
+ ```
587
+
588
+ 成功后告知用户:
589
+ - 已注册的新任务数
590
+ - 生成的 `daily/cron-YYYYMMDD.md` 文件路径
591
+ - 如有冲突,一并展示告警
592
+
593
+ ### 2.9 即时检查 N 分钟内的提醒
594
+
595
+ **触发词**:用户说"接下来半小时有提醒吗"等。
596
+
597
+ **执行步骤**:
598
+
599
+ ```bash
600
+ python3 {SKILL_BASE}/apps/check_reminders.py --window 30
601
+ ```
602
+
603
+ 输出 `NO_REPLY` 表示无提醒;否则展示即将触发的提醒。
604
+
605
+ ### 2.10 查看已注册的 Cron 任务
606
+
607
+ **触发词**:用户说"查看定时任务"、"有哪些 cron"等。
608
+
609
+ ```bash
610
+ openclaw cron list
611
+ ```
612
+
613
+ 以表格形式展示所有任务(名称、下次执行时间、类型)。
614
+
615
+ > **关键路径说明(调试用)**:
616
+ > - OpenClaw Cron 任务存储文件:`~/.openclaw/cron/jobs.json`
617
+ > - 单次事件提醒由 `add_event.py` 直接写入此文件,**创建日程时立即生效**,无需等到次日
618
+ > - 查看文件确认提醒是否注册:`cat ~/.openclaw/cron/jobs.json | python3 -m json.tool | grep -A3 "提醒"`
619
+ > - ⚠️ 不要在 skill 目录下找 `jobs.json`,它不在那里
620
+
621
+ ### 2.11 取消某个提醒
622
+
623
+ **触发词**:用户说"取消今天的 XX 提醒"等。
624
+
625
+ 列出匹配的任务,确认后执行:
626
+
627
+ ```bash
628
+ openclaw cron remove "提醒-{事项名称}"
629
+ ```
630
+
631
+ ### 2.12 新增固定任务
632
+
633
+ **触发词**:用户说"帮我加一个定时任务"、"每天 X 点提醒我 XX"等。
634
+
635
+ **步骤 1:收集信息**,展示确认卡片:
636
+
637
+ ```
638
+ 📋 确认添加以下定时任务:
639
+
640
+ | 字段 | 内容 |
641
+ |------|------|
642
+ | 名称 | 午间休息提醒 |
643
+ | 时间 | 每天 12:30 |
644
+ | 类型 | report(推送到 Telegram)|
645
+ | 内容 | 该午休了,闭眼 20 分钟 |
646
+
647
+ ✅ 确认添加 ✏️ 修改 ❌ 取消
648
+ ```
649
+
650
+ **步骤 2:用户确认后,追加到 `config/task_registry.yaml`**,格式:
651
+
652
+ ```yaml
653
+ - name: 午间休息提醒
654
+ description: "每天午间休息提醒"
655
+ schedule:
656
+ kind: at
657
+ time: "12:30"
658
+ type: report
659
+ message: "该午休了,闭眼 20 分钟"
660
+ delete_after_run: true
661
+ ```
662
+
663
+ **步骤 3:立即同步到 Cron 系统**:
664
+
665
+ ```bash
666
+ POST /tasks/sync # 通过 API(推荐)
667
+ # 或
668
+ python3 {SKILL_BASE}/apps/generate_daily_plan.py
669
+ ```
670
+
671
+ **步骤 4:⚠️ 是否同步写入 Google Calendar?**
672
+
673
+ 固定提醒任务(cron)**不会自动写入 Google Calendar**。如果用户希望在日历里也能看到,需额外询问:
674
+
675
+ > "要不要也加到 Google Calendar 日历里方便查看?"
676
+
677
+ - 用户说"要" → 调用 `add_event.py` 创建对应日历事件(全天或指定时间)
678
+ - 用户说"不用" → 只保留 cron 提醒,不写 GCal
679
+
680
+ **类型说明**:
681
+ | type | 说明 |
682
+ |------|------|
683
+ | `silent` | 静默执行,无通知(适合监控类任务) |
684
+ | `report` | 执行后 announce 结果到 Telegram |
685
+
686
+ **周期任务格式**(使用 cron 表达式):
687
+
688
+ ```yaml
689
+ schedule:
690
+ kind: cron
691
+ expr: "0 9,12,15,18 * * *" # 每天 4 次
692
+ tz: "Asia/Shanghai"
693
+ ```
694
+
695
+ ### 2.13 修改提醒规则
696
+
697
+ **触发词**:用户说"把会议提醒改成提前 5 分钟"等。
698
+
699
+ 修改 `config/reminder_rules.yaml` 中对应的 `lead_minutes` 值,无需改代码,下次 `generate_daily_plan.py` 自动读取新规则:
700
+
701
+ ```yaml
702
+ lead_minutes:
703
+ meeting: 5 # 改为 5 分钟
704
+ sport: 15
705
+ meal: 15
706
+ ```
707
+
708
+ 如需新增分类关键词(如让"健走"归入 sport 类),追加到 `keywords.sport` 列表即可。
709
+
710
+ ---
711
+
712
+ ### 2.14 查询 ROC 周期事件
713
+
714
+ **触发词**:用户说"ROC 有什么事件"、"今年还有哪些周期性提醒"、"ROC 清单"等。
715
+
716
+ ```bash
717
+ python3 {SKILL_BASE}/apps/check_roc.py --list
718
+ ```
719
+
720
+ 以表格展示今年全部 ROC 事件的发生日期和提醒日期:
721
+
722
+ ```
723
+ 📋 **今年 ROC 定期事件**
724
+
725
+ | 事件 | 发生日期 | 提醒日期 | 说明 |
726
+ |------|----------|----------|------|
727
+ | 春节家庭聚会准备 | 02-13 | 02-08 | 提前安排聚餐地点和礼品 |
728
+ | 妈妈生日 | 09-08 | 08-25 | 提前订蛋糕和安排家庭聚餐 |
729
+ | 暑期家庭出行计划 | 07-01 | 06-01 | 提前预订酒店和机票 |
730
+ | 年度家庭体检 | 04-11 | 03-28 | 提前预约体检套餐 |
731
+ ```
732
+
733
+ ### 2.14.1 ROC 事件同步到 Google Calendar
734
+
735
+ ROC 事件按**年份独立批量写入** Google Calendar(全天事件),便于在日历视图中提前查看全年重要节点。
736
+
737
+ **触发机制(自动):**
738
+ - `check_roc.py` 每天 08:05 运行,若当年尚未同步过,自动执行一次全量同步
739
+ - 每年 1 月 1 日 08:00,`ROC年度GCal同步` 任务自动将新一年的事件写入 GCal
740
+
741
+ **手动同步(Agent 操作):**
742
+ ```bash
743
+ # 同步当年(首次安装后可手动执行一次)
744
+ python3 {SKILL_BASE}/apps/sync_roc_to_gcal.py
745
+
746
+ # 同步指定年份
747
+ python3 {SKILL_BASE}/apps/sync_roc_to_gcal.py --year 2027
748
+
749
+ # 预览(不写入 GCal)
750
+ python3 {SKILL_BASE}/apps/sync_roc_to_gcal.py --dry-run
751
+
752
+ # 强制重建(GCal 事件误删时使用)
753
+ python3 {SKILL_BASE}/apps/sync_roc_to_gcal.py --force
754
+ ```
755
+
756
+ **去重机制**:同步状态记录在 `data/roc_gcal_sync.json`(`年份 → { roc_id: gcal_event_id }`),已同步的事件不会重复创建。
757
+
758
+ **用户触发词**:
759
+ - "把 ROC 事件同步到日历" → 执行 `sync_roc_to_gcal.py`
760
+ - "明年的 ROC 事件写到 Google Calendar" → `--year` 指定下一年
761
+ - "ROC 事件在日历里消失了" → `--force` 重新同步
762
+
763
+ ### 2.15 查询近期 ROC 提醒
764
+
765
+ **触发词**:用户说"最近有没有 ROC 提醒"、"未来一个月有什么周期任务"等。
766
+
767
+ ```bash
768
+ python3 {SKILL_BASE}/apps/check_roc.py --upcoming 30
769
+ ```
770
+
771
+ ### 2.16 新增 ROC 事件
772
+
773
+ **触发词**:用户说"帮我加一个 ROC 事件"、"新增一个每年提醒"等。
774
+
775
+ **步骤 1**:收集信息,展示确认卡片:
776
+
777
+ ```
778
+ 📋 确认添加 ROC 事件:
779
+
780
+ | 字段 | 内容 |
781
+ |------|------|
782
+ | ID | dad_birthday |
783
+ | 名称 | 爸爸生日 |
784
+ | 规则 | 每年5月20日 |
785
+ | 提前提醒 | 14 天 |
786
+ | 推送时间 | 09:00 |
787
+ | 说明 | 提前安排家庭聚餐 |
788
+
789
+ ✅ 确认添加 ✏️ 修改 ❌ 取消
790
+ ```
791
+
792
+ **步骤 2**:用户确认后,追加到 `config/roc_events.yaml` 的 `events` 列表:
793
+
794
+ ```yaml
795
+ - id: dad_birthday
796
+ name: 爸爸生日
797
+ rule: 每年5月20日
798
+ reminder_time: "09:00"
799
+ lead_days: 14
800
+ description: "提前安排家庭聚餐"
801
+ ```
802
+
803
+ ### 2.17 修改春节日期(每年维护一次)
804
+
805
+ **触发词**:用户说"更新明年春节日期"等。
806
+
807
+ 直接修改 `config/roc_events.yaml` 中的 `spring_festival` 字段:
808
+
809
+ ```yaml
810
+ spring_festival:
811
+ 2027:
812
+ last_workday: "2027-02-09" # 春节前最后一个工作日
813
+ first_workday: "2027-02-19" # 春节后第一个工作日
814
+ ```
815
+
816
+ ---
817
+
818
+ ## 三、提醒消息格式
819
+
820
+ 自动提醒触发时,消息格式如下:
821
+
822
+ ```
823
+ ⏰ **牙医预约** 将在 15 分钟后开始
824
+ 🕐 15:00–16:00(共 60 分钟)
825
+ 📍 龙湖口腔
826
+ 📝 记得带上医保卡
827
+ ```
828
+
829
+ ---
830
+
831
+ ## 四、日程冲突说明
832
+
833
+ 系统每次生成每日计划时自动检测冲突,规则:
834
+
835
+ - **重叠**:两个事件时间区间有交集(超过 0 分钟)→ `⚠️ 冲突`
836
+ - **紧接**:两个事件间隔 < 5 分钟(无缓冲时间)→ `⚡ 紧接`
837
+ - 全天事件不参与冲突检测
838
+
839
+ ---
840
+
841
+ ## 五、配置说明
842
+
843
+ 所有配置集中在 `config/` 目录,**修改 yaml 不需要改代码**。
844
+
845
+ | 文件 | 用途 |
846
+ |------|------|
847
+ | `config/settings.py` | 路径、代理、Telegram、时区 |
848
+ | `config/reminder_rules.yaml` | 提醒规则和分类关键词 |
849
+ | `config/task_registry.yaml` | 所有固定重复任务注册表 |
850
+ | `.env` | 敏感配置(token 路径、代理地址等) |
851
+
852
+ **环境变量覆盖**:
853
+
854
+ | 变量 | 默认值 | 说明 |
855
+ |------|--------|------|
856
+ | `SOPHNET_SCHEDULE_BASE` | 自动推导 | skill 根路径(兼容旧名 `CRON_SKILL_BASE`) |
857
+ | `OPENCLAW_BASE` | `~/.openclaw` | OpenClaw 工作空间 |
858
+ | `GCAL_TOKEN_PATH` | `~/.config/gcalcli/token.json` | Google OAuth token |
859
+ | `HTTP_PROXY` | 空 | 访问 Google API 的代理地址 |
860
+ | `TELEGRAM_TO` | 空 | Telegram 接收方 ID |
861
+ | `GCAL_CALENDAR_ID` | `primary` | 要监控的日历 ID |
862
+ | `TIMEZONE` | `Asia/Shanghai` | 默认时区 |
863
+
864
+ ---
865
+
866
+ ## 六、任务投递规则
867
+
868
+ | 场景 | sessionTarget | payload.kind | delivery |
869
+ |------|--------------|--------------|----------|
870
+ | 会前/事前提醒 | `main` | `systemEvent` | 不设置 |
871
+ | 周期静默监控 | `isolated` | `agentTurn` | `none` |
872
+ | 报告/日报推送 | `isolated` | `agentTurn` | `announce` |
873
+
874
+ **为什么提醒用 `systemEvent + main`**:
875
+ `isolated + announce` 不支持 webchat(Web 界面)渠道,会报 `Unsupported channel` 错误。
876
+ `systemEvent + main` 直接注入主会话,所有渠道均可用,可靠性最高。
877
+
878
+ ---
879
+
880
+ ## 七、运行测试
881
+
882
+ ```bash
883
+ cd {SKILL_BASE}
884
+
885
+ # 全部单元测试(不需要 Google Calendar 连接)
886
+ python3 tests/test_time_window.py
887
+ python3 tests/test_event_classifier.py
888
+ python3 tests/test_reminder_planner.py
889
+ python3 tests/test_event_diff.py
890
+ python3 tests/test_task_builder.py
891
+ python3 tests/test_conflict_detector.py
892
+ python3 tests/test_add_event.py
893
+
894
+ # 或用 pytest
895
+ python3 -m pytest tests/ -v
896
+
897
+ # 干运行(只生成 md,不注册 cron,需要 GCal token)
898
+ python3 apps/generate_daily_plan.py --dry-run
899
+ python3 apps/generate_daily_plan.py --days 2 --dry-run
900
+ ```
901
+
902
+ ---
903
+
904
+ ## 八、配置维护
905
+
906
+ `config/` 目录下有三个可维护的配置文件,Agent 负责解读用户意图并直接操作对应文件。
907
+
908
+ ---
909
+
910
+ ### 8.1 `task_registry.yaml` — 固定定时任务
911
+
912
+ **说明**:定义所有每日重复运行的 cron 任务(几点生成日报、几点提醒喝水等)。
913
+
914
+ **生效方式**:修改后调用 `POST /tasks/sync`(或自然语言"同步任务"),立即写入 `jobs.json`,**不需要等到明天**。
915
+
916
+ **常见场景**:
917
+
918
+ > **用户**:帮我加一个每天下午 3 点的午睡提醒
919
+ >
920
+ > **Agent**:
921
+ > 1. 在 `task_registry.yaml` 的 `tasks:` 列表追加:
922
+ > ```yaml
923
+ > - name: 午睡提醒
924
+ > description: "下午休息,恢复精力"
925
+ > schedule:
926
+ > kind: at
927
+ > time: "15:00"
928
+ > type: report
929
+ > message: "该午睡了 😴"
930
+ > delete_after_run: true
931
+ > ```
932
+ > 2. 调用 `POST /tasks/sync` 立即注册
933
+ > 3. 回复:「已添加"午睡提醒",每天 15:00 生效」
934
+
935
+ > **用户**:把喝水提醒改到下午 2 点
936
+ >
937
+ > **Agent**:找到 `task_registry.yaml` 中 `name: 喝水提醒` 的条目,将 `time: "10:00"` 改为 `time: "14:00"`,再调用 `POST /tasks/sync`。
938
+
939
+ **验证**:
940
+ ```bash
941
+ # dry-run 预览差异,不写入
942
+ curl -s -X POST -H "X-API-Key: $KEY" "$BASE/tasks/sync?dry_run=true" | python3 -m json.tool
943
+
944
+ # 正式同步
945
+ curl -s -X POST -H "X-API-Key: $KEY" "$BASE/tasks/sync" | python3 -m json.tool
946
+ ```
947
+
948
+ ---
949
+
950
+ ### 8.2 `reminder_rules.yaml` — 提醒提前量与分类关键词
951
+
952
+ **说明**:控制不同类型事件的提醒提前量(分钟)和匹配关键词。修改后**立即生效**(运行时读取,无需重启)。
953
+
954
+ **常见场景**:
955
+
956
+ > **用户**:把体检提醒改成提前 1 小时
957
+ >
958
+ > **Agent**:将 `reminder_rules.yaml` 中 `medical: 30` 改为 `medical: 60`,回复「已修改,下次提醒计算时生效」。
959
+
960
+ > **用户**:我想让"攀岩"也算作运动类提醒
961
+ >
962
+ > **Agent**:在 `keywords.sport` 列表追加 `- 攀岩`,回复「已添加"攀岩"到运动分类」。
963
+
964
+ > **用户**:加一个"约会"类型,提前 20 分钟提醒
965
+ >
966
+ > **Agent**:
967
+ > 1. 在 `lead_minutes:` 追加 `date: 20`
968
+ > 2. 在 `keywords:` 追加:
969
+ > ```yaml
970
+ > date:
971
+ > - 约会
972
+ > - 约好了
973
+ > - 见面
974
+ > ```
975
+ > 3. 在 `event_classifier.py` 的 `classify` 方法匹配顺序中加入 `"date"`
976
+
977
+ **注意**:新增分类类型时,同步更新 `services/event_classifier.py` 中的匹配顺序列表。
978
+
979
+ ---
980
+
981
+ ### 8.3 `roc_events.yaml` — ROC 周期性事件
982
+
983
+ **说明**:管理每年重复的重要事件(生日、体检、家庭聚会等)。已有完整 REST API 和自然语言交互支持(见 2.14–2.17 节)。
984
+
985
+ **API 快速参考**:
986
+ ```bash
987
+ GET /roc/events # 列出今年所有事件及日期
988
+ POST /roc/events # 新增事件
989
+ PUT /roc/events/{id} # 修改事件
990
+ DELETE /roc/events/{id} # 删除事件
991
+ GET /roc/upcoming?days=90 # 未来 N 天内即将到来的事件
992
+ ```
993
+
994
+ **特殊场景 — 更新春节日期**(每年 1 月更新一次):
995
+
996
+ > **用户**:更新一下明年春节日期
997
+ >
998
+ > **Agent**:修改 `roc_events.yaml` 顶部 `spring_festival:` 中对应年份的 `cny_date`,保存即可。
999
+
1000
+ ---
1001
+
1002
+ ### 8.4 维护操作速查
1003
+
1004
+ | 用户说 | Agent 动作 |
1005
+ |--------|------------|
1006
+ | "加个每天 X 点的提醒" | 追加到 `task_registry.yaml` → `POST /tasks/sync` |
1007
+ | "删掉/改一下某个定时任务" | 修改 `task_registry.yaml` → `POST /tasks/sync` |
1008
+ | "同步任务/立即生效" | `POST /tasks/sync` |
1009
+ | "把 XX 提醒改成 N 分钟" | 修改 `reminder_rules.yaml` `lead_minutes` 对应值 |
1010
+ | "加个关键词到 XX 类型" | 追加到 `reminder_rules.yaml` `keywords.XX` 列表 |
1011
+ | "加个新的提醒类型" | 同时更新 `reminder_rules.yaml` + `event_classifier.py` |
1012
+ | "加个 ROC 事件" | `POST /roc/events`(见 2.15 节) |
1013
+ | "删/改 ROC 事件" | `DELETE`/`PUT /roc/events/{id}` |
1014
+ | "更新春节日期" | 修改 `roc_events.yaml` `spring_festival.YYYY.cny_date` |
1015
+
1016
+ ---
1017
+
1018
+ ## 九、Google Calendar 初始化
1019
+
1020
+ 首次使用需要完成 OAuth 授权:
1021
+
1022
+ ```bash
1023
+ python3 {SKILL_BASE}/scripts/setup_gcal_token.py
1024
+ ```
1025
+
1026
+ 详细步骤见 `docs/GOOGLE_CALENDAR_SETUP.md`。
1027
+
1028
+ ---
1029
+
1030
+ ## 十、常用操作速查
1031
+
1032
+ | 用户说 | Agent 动作 |
1033
+ |--------|------------|
1034
+ | "明天下午3点牙医预约" | 解析 → 确认卡片 → `add_event.py` → 写入 GCal 或本地存储(根据 provider 字段回复) |
1035
+ | 发送 Excel/CSV/Word/PDF | 定位文件 → `markitdown` 转换 → `table_parser` 提取 → 确认 → `import_events.py` 写入 |
1036
+ | 发送截图/图片 | 定位图片 → OCR → Agent 提取事件 → 确认 → `import_events.py` 写入 |
1037
+ | "今天有什么安排" | `generate_daily_plan.py --dry-run`,展示今日日程 |
1038
+ | "明天的日程" | `--days 2 --dry-run`,展示明日部分 |
1039
+ | "有没有日程冲突" | 展示冲突检测结果 |
1040
+ | "有没有日程变化" | `monitor_calendar_changes.py --dry-run` |
1041
+ | "手动生成今日计划" | `generate_daily_plan.py`(注册 cron 任务) |
1042
+ | "接下来有提醒吗" | `check_reminders.py --window 30` |
1043
+ | "查看定时任务" | `openclaw cron list` |
1044
+ | "取消今天 XX 提醒" | `openclaw cron remove "提醒-XX"` |
1045
+ | "帮我加个定时任务" | 收集信息→确认→追加到 task_registry.yaml→`POST /tasks/sync` |
1046
+ | "把体检提醒改成1小时" | 修改 `reminder_rules.yaml` 的 `medical: 60` |
1047
+ | "加个关键词到运动分类" | 追加到 `reminder_rules.yaml` `keywords.sport` |
1048
+ | "ROC 今年有哪些事件" | `check_roc.py --list`,展示全年事件表 |
1049
+ | "最近有 ROC 提醒吗" | `check_roc.py --upcoming 30` |
1050
+ | "加个 ROC 事件" | 收集信息→确认→追加到 `roc_events.yaml` |