sophhub 0.4.19 → 0.4.21

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 (633) hide show
  1. package/README.md +199 -187
  2. package/agents/ai-cs-admin/.config.json +51 -51
  3. package/agents/ai-cs-admin/AGENTS.md +293 -293
  4. package/agents/ai-cs-admin/HEARTBEAT.md +18 -18
  5. package/agents/ai-cs-qa/.config.json +47 -47
  6. package/agents/ai-cs-qa/BOOTSTRAP.md +22 -22
  7. package/agents/ai-cs-qa/scripts/setup_links.sh +39 -39
  8. package/agents/beauty/.config.json +17 -17
  9. package/agents/beauty/AGENTS.md +234 -234
  10. package/agents/beauty/BOOTSTRAP.md +55 -55
  11. package/agents/beauty/HEARTBEAT.md +5 -5
  12. package/agents/beauty/IDENTITY.md +5 -5
  13. package/agents/beauty/MEMORY.md +44 -44
  14. package/agents/beauty/SOUL.md +64 -64
  15. package/agents/beauty/TOOLS.md +160 -160
  16. package/agents/beauty/USER.md +114 -114
  17. package/agents/intern-admin/.config.json +60 -60
  18. package/agents/intern-admin/AGENTS.md +267 -267
  19. package/agents/intern-admin/BOOTSTRAP.md +21 -21
  20. package/agents/intern-admin/HEARTBEAT.md +3 -3
  21. package/agents/intern-admin/IDENTITY.md +6 -6
  22. package/agents/intern-admin/MEMORY.md +21 -21
  23. package/agents/intern-admin/SOUL.md +23 -23
  24. package/agents/intern-admin/TOOLS.md +93 -93
  25. package/agents/intern-admin/USER.md +16 -16
  26. package/agents/intern-admin/scripts/init_workspace.sh +27 -27
  27. package/agents/intern-qa/.config.json +46 -46
  28. package/agents/intern-qa/AGENTS.md +303 -303
  29. package/agents/intern-qa/BOOTSTRAP.md +16 -16
  30. package/agents/intern-qa/HEARTBEAT.md +3 -3
  31. package/agents/intern-qa/IDENTITY.md +6 -6
  32. package/agents/intern-qa/MEMORY.md +22 -22
  33. package/agents/intern-qa/SOUL.md +24 -24
  34. package/agents/intern-qa/TOOLS.md +24 -24
  35. package/agents/intern-qa/USER.md +27 -27
  36. package/agents/intern-qa/scripts/setup_links.sh +54 -54
  37. package/agents/parent-toddler/.config.json +37 -37
  38. package/agents/parent-toddler/AGENTS.md +51 -51
  39. package/agents/parent-toddler/BOOTSTRAP.md +55 -55
  40. package/agents/parent-toddler/HEARTBEAT.md +5 -5
  41. package/agents/parent-toddler/IDENTITY.md +5 -5
  42. package/agents/parent-toddler/MEMORY.md +22 -22
  43. package/agents/parent-toddler/SOUL.md +35 -35
  44. package/agents/parent-toddler/TOOLS.md +31 -31
  45. package/agents/parent-toddler/USER.md +44 -44
  46. package/agents/vip-admin/.config.json +51 -51
  47. package/agents/vip-admin/AGENTS.md +314 -314
  48. package/agents/vip-admin/BOOTSTRAP.md +21 -21
  49. package/agents/vip-admin/HEARTBEAT.md +19 -19
  50. package/agents/vip-admin/IDENTITY.md +6 -6
  51. package/agents/vip-admin/MEMORY.md +30 -30
  52. package/agents/vip-admin/SOUL.md +25 -25
  53. package/agents/vip-admin/TOOLS.md +108 -108
  54. package/agents/vip-admin/USER.md +31 -31
  55. package/agents/vip-qa/.config.json +58 -58
  56. package/agents/vip-qa/AGENTS.md +319 -319
  57. package/agents/vip-qa/BOOTSTRAP.md +73 -73
  58. package/agents/vip-qa/HEARTBEAT.md +23 -23
  59. package/agents/vip-qa/IDENTITY.md +7 -7
  60. package/agents/vip-qa/MEMORY.md +23 -23
  61. package/agents/vip-qa/SOUL.md +34 -34
  62. package/agents/vip-qa/TOOLS.md +41 -41
  63. package/agents/vip-qa/USER.md +16 -16
  64. package/agents/vip-qa/scripts/setup_links.sh +39 -39
  65. package/bin/sophhub.js +25 -25
  66. package/package.json +35 -33
  67. package/skills/agent-install/skill.json +34 -34
  68. package/skills/agent-install/src/SKILL.md +240 -240
  69. package/skills/agent-install/src/pyproject.toml +6 -6
  70. package/skills/agent-install/src/scripts/backup_agent.py +120 -120
  71. package/skills/agent-install/src/scripts/check_installed.py +479 -479
  72. package/skills/agent-install/src/scripts/common.py +568 -568
  73. package/skills/agent-install/src/scripts/copy_agent_files.py +59 -59
  74. package/skills/agent-install/src/scripts/list_agents.py +285 -285
  75. package/skills/agent-install/src/scripts/resolve_install_params.py +90 -90
  76. package/skills/agent-install/src/scripts/update_agent_md.py +76 -76
  77. package/skills/agent-install/src/scripts/update_openclaw.py +193 -193
  78. package/skills/agent-install/src/scripts/verify_download.py +148 -148
  79. package/skills/aippt/skill.json +20 -20
  80. package/skills/aippt/src/SKILL.md +235 -235
  81. package/skills/aippt/src/pyproject.toml +8 -8
  82. package/skills/aippt/src/scripts/auth.py +122 -122
  83. package/skills/aippt/src/scripts/ppt.py +361 -361
  84. package/skills/aippt/src/scripts/provider_docmee.py +299 -299
  85. package/skills/beauty-salon-inventory/skill.json +16 -16
  86. package/skills/beauty-salon-inventory/src/SKILL.md +69 -69
  87. package/skills/beauty-salon-inventory/src/scripts/init_salon_inventory.py +39 -39
  88. package/skills/beauty-salon-inventory/src/scripts/init_salon_inventory.sh +4 -4
  89. package/skills/beauty-salon-inventory/src/scripts/salon_inventory_cli.py +244 -244
  90. package/skills/beauty-salon-marketing/skill.json +10 -10
  91. package/skills/beauty-salon-marketing/src/SKILL.md +36 -36
  92. package/skills/beauty-salon-marketing/src/playbooks/beauty-salon-festival.md +19 -19
  93. package/skills/beauty-salon-marketing/src/playbooks/beauty-salon-segment.md +18 -18
  94. package/skills/beauty-salon-marketing/src/scripts/beauty_marketing_cli.py +99 -99
  95. package/skills/beauty-salon-marketing/src/scripts/member_segment.py +114 -114
  96. package/skills/beauty-salon-member-appointment/skill.json +10 -10
  97. package/skills/beauty-salon-member-appointment/src/SKILL.md +36 -36
  98. package/skills/beauty-salon-member-appointment/src/pyproject.toml +9 -9
  99. package/skills/beauty-salon-member-appointment/src/scripts/run_e2e_smoke.py +160 -160
  100. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/__init__.py +1 -1
  101. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/__main__.py +4 -4
  102. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/cli.py +921 -921
  103. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/db.py +30 -30
  104. package/skills/beauty-salon-membership/skill.json +20 -20
  105. package/skills/beauty-salon-membership/src/SKILL.md +67 -67
  106. package/skills/beauty-salon-product-service/skill.json +12 -12
  107. package/skills/beauty-salon-product-service/src/SKILL.md +42 -42
  108. package/skills/beauty-salon-product-service/src/pyproject.toml +9 -9
  109. package/skills/beauty-salon-product-service/src/src/product_service_cli/__init__.py +1 -1
  110. package/skills/beauty-salon-product-service/src/src/product_service_cli/__main__.py +4 -4
  111. package/skills/beauty-salon-product-service/src/src/product_service_cli/cli.py +329 -329
  112. package/skills/beauty-salon-product-service/src/src/product_service_cli/db.py +29 -29
  113. package/skills/beauty-salon-staff/skill.json +10 -10
  114. package/skills/beauty-salon-staff/src/SKILL.md +37 -37
  115. package/skills/beauty-salon-staff/src/pyproject.toml +9 -9
  116. package/skills/beauty-salon-staff/src/src/staff_cli/__init__.py +1 -1
  117. package/skills/beauty-salon-staff/src/src/staff_cli/__main__.py +4 -4
  118. package/skills/beauty-salon-staff/src/src/staff_cli/cli.py +479 -479
  119. package/skills/beauty-salon-staff/src/src/staff_cli/db.py +28 -28
  120. package/skills/beauty-salon-suite/skill.json +13 -13
  121. package/skills/beauty-salon-suite/src/SKILL.md +18 -18
  122. package/skills/beauty-salon-suite/src/beauty_db/__init__.py +2 -2
  123. package/skills/beauty-salon-suite/src/beauty_db/db.py +249 -249
  124. package/skills/beauty-salon-traffic/skill.json +20 -20
  125. package/skills/beauty-salon-traffic/src/SKILL.md +84 -84
  126. package/skills/bing-image-search/skill.json +20 -20
  127. package/skills/bing-image-search/src/SKILL.md +105 -105
  128. package/skills/bot-api-status/skill.json +44 -44
  129. package/skills/bot-api-status/src/SKILL.md +99 -99
  130. package/skills/bot-api-status/src/pyproject.toml +5 -5
  131. package/skills/bot-api-status/src/scripts/secret.py +496 -496
  132. package/skills/bot-secret/skill.json +35 -35
  133. package/skills/bot-secret/src/SKILL.md +51 -51
  134. package/skills/bot-secret/src/pyproject.toml +5 -5
  135. package/skills/bot-secret/src/scripts/secret.py +120 -120
  136. package/skills/cake-flower-holiday-campaign/skill.json +20 -20
  137. package/skills/cake-flower-holiday-campaign/src/SKILL.md +68 -68
  138. package/skills/cake-flower-order-sop/skill.json +20 -20
  139. package/skills/cake-flower-order-sop/src/SKILL.md +65 -65
  140. package/skills/claw-agent-get-send/skill.json +32 -32
  141. package/skills/claw-agent-get-send/src/SKILL.md +43 -43
  142. package/skills/claw-agent-get-send/src/pyproject.toml +5 -5
  143. package/skills/claw-agent-get-send/src/scripts/appia_claw.py +379 -379
  144. package/skills/compact-context/skill.json +20 -20
  145. package/skills/compact-context/src/SKILL.md +133 -133
  146. package/skills/compact-context/src/scripts/check.sh +381 -381
  147. package/skills/compact-context/src/scripts/set-keep-recent.mjs +1337 -1337
  148. package/skills/compact-context/src/scripts/setup.sh +96 -96
  149. package/skills/consensus/skill.json +20 -20
  150. package/skills/consensus/src/SKILL.md +93 -93
  151. package/skills/deepwiki/skill.json +20 -20
  152. package/skills/deepwiki/src/SKILL.md +45 -45
  153. package/skills/deepwiki/src/_meta.json +5 -5
  154. package/skills/deepwiki/src/scripts/deepwiki.js +135 -135
  155. package/skills/didi-ride/skill.json +20 -20
  156. package/skills/didi-ride/src/SKILL.md +309 -309
  157. package/skills/didi-ride/src/_meta.json +5 -5
  158. package/skills/didi-ride/src/assets/PREFERENCE.md +58 -58
  159. package/skills/didi-ride/src/package.json +15 -15
  160. package/skills/didi-ride/src/references/api_references.md +171 -171
  161. package/skills/didi-ride/src/references/error_handling.md +68 -68
  162. package/skills/didi-ride/src/references/setup.md +73 -73
  163. package/skills/didi-ride/src/references/workflow.md +150 -150
  164. package/skills/feishu-bitable/skill.json +20 -20
  165. package/skills/feishu-bitable/src/CHECKLIST.md +149 -149
  166. package/skills/feishu-bitable/src/README.md +177 -177
  167. package/skills/feishu-bitable/src/SKILL.md +113 -113
  168. package/skills/feishu-bitable/src/_meta.json +5 -5
  169. package/skills/feishu-bitable/src/api.js +380 -380
  170. package/skills/feishu-bitable/src/bin/cli.js +283 -283
  171. package/skills/feishu-bitable/src/description.md +142 -142
  172. package/skills/feishu-bitable/src/examples/create-records.json +51 -51
  173. package/skills/feishu-bitable/src/examples/create-table.json +63 -63
  174. package/skills/feishu-bitable/src/package-lock.json +324 -324
  175. package/skills/feishu-bitable/src/package.json +32 -32
  176. package/skills/feishu-bitable/src/publish-config.json +13 -13
  177. package/skills/feishu-bitable/src/test-simple.js +60 -60
  178. package/skills/feishu-bitable/src/utils.js +260 -260
  179. package/skills/feishu-notes-assistant-universal/skill.json +20 -20
  180. package/skills/feishu-notes-assistant-universal/src/README.md +55 -55
  181. package/skills/feishu-notes-assistant-universal/src/SKILL.md +159 -159
  182. package/skills/feishu-notes-assistant-universal/src/scripts/_resolve_lark_cli.py +58 -58
  183. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_meeting_minutes.py +462 -462
  184. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud.py +547 -547
  185. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud_test.py +181 -181
  186. package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.py +80 -80
  187. package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.sh +5 -5
  188. package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.py +32 -32
  189. package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.sh +5 -5
  190. package/skills/flight-booking/skill.json +36 -36
  191. package/skills/flight-booking/src/SKILL.md +288 -288
  192. package/skills/flight-booking/src/scripts/flight_booking.py +1237 -1237
  193. package/skills/flyai/skill.json +20 -20
  194. package/skills/flyai/src/SKILL.md +119 -119
  195. package/skills/flyai/src/references/fliggy-fast-search.md +53 -53
  196. package/skills/flyai/src/references/search-flight.md +89 -89
  197. package/skills/flyai/src/references/search-hotels.md +57 -57
  198. package/skills/flyai/src/references/search-poi.md +48 -48
  199. package/skills/google-maps/skill.json +20 -20
  200. package/skills/google-maps/src/SKILL.md +237 -237
  201. package/skills/google-maps/src/_meta.json +5 -5
  202. package/skills/google-maps/src/lib/map_helper.py +912 -912
  203. package/skills/image-classify/skill.json +42 -42
  204. package/skills/image-classify/src/SKILL.md +368 -368
  205. package/skills/image-classify/src/references/config.json +4 -4
  206. package/skills/image-classify/src/scripts/face_search.py +1276 -1276
  207. package/skills/image-description/skill.json +34 -34
  208. package/skills/image-description/src/SKILL.md +33 -33
  209. package/skills/image-description/src/pyproject.toml +8 -8
  210. package/skills/image-description/src/scripts/ana_image.py +112 -112
  211. package/skills/image-identify-world/skill.json +20 -20
  212. package/skills/image-identify-world/src/SKILL.md +40 -40
  213. package/skills/image-identify-world/src/pyproject.toml +8 -8
  214. package/skills/image-identify-world/src/scripts/identify_world.py +115 -115
  215. package/skills/insurance-policy-review/skill.json +27 -27
  216. package/skills/insurance-policy-review/src/SKILL.md +75 -75
  217. package/skills/insurance-sales-playbook/skill.json +20 -20
  218. package/skills/insurance-sales-playbook/src/SKILL.md +58 -58
  219. package/skills/inventory-management/skill.json +20 -20
  220. package/skills/inventory-management/src/SKILL.md +241 -241
  221. package/skills/inventory-management/src/scripts/inventory.py +1844 -1844
  222. package/skills/large-task-router/skill.json +20 -20
  223. package/skills/large-task-router/src/SKILL.md +79 -79
  224. package/skills/large-task-router/src/templates/plan.md +74 -74
  225. package/skills/lawding-contract-review/skill.json +20 -20
  226. package/skills/lawding-contract-review/src/SKILL.md +284 -284
  227. package/skills/lawding-contract-review/src/references/legal-language-library.md +1385 -1385
  228. package/skills/lawding-contract-review/src/scripts/build_reminders.py +471 -471
  229. package/skills/lawding-contract-review/src/scripts/register_contract_cron.py +457 -457
  230. package/skills/md2pdf-converter/skill.json +20 -20
  231. package/skills/md2pdf-converter/src/SKILL.md +244 -244
  232. package/skills/md2pdf-converter/src/_meta.json +5 -5
  233. package/skills/md2pdf-converter/src/scripts/generate_emoji_mapping.py +74 -74
  234. package/skills/md2pdf-converter/src/scripts/md2pdf-local.sh +291 -291
  235. package/skills/notes-hub-assistant/skill.json +20 -20
  236. package/skills/notes-hub-assistant/src/SKILL.md +233 -233
  237. package/skills/notes-hub-assistant/src/scripts/_resolve_lark_cli.py +48 -48
  238. package/skills/notes-hub-assistant/src/scripts/openclaw_meeting_minutes.py +473 -473
  239. package/skills/notes-hub-assistant/src/scripts/openclaw_notes_crud.py +596 -596
  240. package/skills/notes-hub-assistant/src/scripts/openclaw_wolai_notes_crud.py +364 -364
  241. package/skills/notes-hub-assistant/src/scripts/run_meeting_minutes.py +79 -79
  242. package/skills/notes-hub-assistant/src/scripts/run_note_crud.py +37 -37
  243. package/skills/notes-hub-assistant/src/scripts/run_notionbot.py +36 -36
  244. package/skills/notes-hub-assistant/src/scripts/run_wolai_note_crud.py +27 -27
  245. package/skills/schedule-reminder/skill.json +20 -20
  246. package/skills/schedule-reminder/src/SKILL.md +619 -619
  247. package/skills/schedule-reminder/src/schedule_template.md +68 -68
  248. package/skills/schedule-reminder/src/scripts/append_event.py +204 -204
  249. package/skills/schedule-reminder/src/scripts/create_reminders.sh +163 -163
  250. package/skills/schedule-reminder/src/scripts/daily_activate.sh +175 -175
  251. package/skills/schedule-reminder/src/scripts/parse_schedule.py +704 -704
  252. package/skills/schedule-reminder/src/scripts/setup.sh +242 -242
  253. package/skills/schedule-reminder/src//347/224/250/346/210/267/346/214/207/345/215/227.md +311 -311
  254. package/skills/sessions-analysis/skill.json +34 -34
  255. package/skills/sessions-analysis/src/SKILL.md +81 -81
  256. package/skills/sessions-analysis/src/pyproject.toml +5 -5
  257. package/skills/sessions-analysis/src/scripts/ana_logs.py +205 -205
  258. package/skills/share-skill/skill.json +20 -20
  259. package/skills/share-skill/src/SKILL.md +261 -261
  260. package/skills/share-skill/src/scripts/share_skill_to_friend.py +1031 -1031
  261. package/skills/skill-creator/skill.json +20 -20
  262. package/skills/skill-creator/src/SKILL.md +370 -370
  263. package/skills/skill-creator/src/license.txt +202 -202
  264. package/skills/skill-creator/src/scripts/init_skill.py +378 -378
  265. package/skills/skill-creator/src/scripts/package_skill.py +111 -111
  266. package/skills/skill-creator/src/scripts/quick_validate.py +101 -101
  267. package/skills/skillhub/skill.json +27 -27
  268. package/skills/skillhub/src/SKILL.md +121 -121
  269. package/skills/sophnet-age-appearance/skill.json +20 -20
  270. package/skills/sophnet-age-appearance/src/SKILL.md +83 -83
  271. package/skills/sophnet-age-appearance/src/pyproject.toml +10 -10
  272. package/skills/sophnet-age-appearance/src/scripts/age_appearance.py +395 -395
  273. package/skills/sophnet-age-appearance/src/scripts/age_face_crop.py +313 -313
  274. package/skills/sophnet-bot-client/skill.json +20 -20
  275. package/skills/sophnet-bot-client/src/SKILL.md +255 -255
  276. package/skills/sophnet-bot-client/src/pyproject.toml +13 -13
  277. package/skills/sophnet-bot-client/src/scripts/bot_client_proxy.py +165 -165
  278. package/skills/sophnet-bot-client/src/scripts/bot_client_safe.sh +29 -29
  279. package/skills/sophnet-bot-client/src/scripts/bot_client_setup.py +502 -502
  280. package/skills/sophnet-bot-client/src/tests/test_bot_client_proxy.py +255 -255
  281. package/skills/sophnet-bot-client/src/tests/test_bot_client_setup.py +679 -679
  282. package/skills/sophnet-bot-client/src/uv.lock +8 -8
  283. package/skills/sophnet-customer-management/skill.json +20 -20
  284. package/skills/sophnet-customer-management/src/SKILL.md +270 -270
  285. package/skills/sophnet-customer-management/src/pyproject.toml +15 -15
  286. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/__init__.py +2 -2
  287. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/__main__.py +5 -5
  288. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/cli.py +67 -67
  289. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/__init__.py +2 -2
  290. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/customer.py +60 -60
  291. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/export_file.py +18 -18
  292. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/import_file.py +15 -15
  293. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/reminder.py +26 -26
  294. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/schema.py +28 -28
  295. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/config.py +54 -54
  296. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/__init__.py +2 -2
  297. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/exporter.py +85 -85
  298. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/models.py +84 -84
  299. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/normalizer.py +144 -144
  300. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/parser.py +241 -241
  301. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/query.py +109 -109
  302. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/reminder.py +121 -121
  303. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/repository.py +397 -397
  304. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/schema.py +106 -106
  305. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/service.py +565 -565
  306. package/skills/sophnet-customer-management/src/uv.lock +48 -48
  307. package/skills/sophnet-customized-marketing/skill.json +28 -28
  308. package/skills/sophnet-customized-marketing/src/SKILL.md +144 -144
  309. package/skills/sophnet-customized-marketing/src/playbooks/campaign-planning.md +187 -187
  310. package/skills/sophnet-customized-marketing/src/playbooks/content-generation.md +124 -124
  311. package/skills/sophnet-customized-marketing/src/playbooks/marketing-calendar.md +59 -59
  312. package/skills/sophnet-customized-marketing/src/playbooks/multi-channel-bundle.md +94 -94
  313. package/skills/sophnet-customized-marketing/src/playbooks/poster-generation.md +182 -182
  314. package/skills/sophnet-customized-marketing/src/playbooks/style-profile-workflow.md +103 -103
  315. package/skills/sophnet-customized-marketing/src/pyproject.toml +8 -8
  316. package/skills/sophnet-customized-marketing/src/references/campaign-mechanics.md +168 -168
  317. package/skills/sophnet-customized-marketing/src/references/content-safety.md +26 -26
  318. package/skills/sophnet-customized-marketing/src/references/marketing-date-checklist.md +99 -99
  319. package/skills/sophnet-customized-marketing/src/references/platform-writing-guidelines.md +88 -88
  320. package/skills/sophnet-customized-marketing/src/references/quality-checklist.md +44 -44
  321. package/skills/sophnet-customized-marketing/src/scripts/generate_poster.py +572 -572
  322. package/skills/sophnet-customized-marketing/src/scripts/style_profile.py +215 -215
  323. package/skills/sophnet-dailynews/skill.json +20 -20
  324. package/skills/sophnet-dailynews/src/SKILL.md +179 -179
  325. package/skills/sophnet-dailynews/src/cache.json +150 -150
  326. package/skills/sophnet-dailynews/src/sources.json +230 -230
  327. package/skills/sophnet-docx/skill.json +20 -20
  328. package/skills/sophnet-docx/src/SKILL.md +463 -463
  329. package/skills/sophnet-docx/src/package-lock.json +208 -208
  330. package/skills/sophnet-docx/src/package.json +16 -16
  331. package/skills/sophnet-docx/src/pyproject.toml +11 -11
  332. package/skills/sophnet-docx/src/scripts/__init__.py +1 -1
  333. package/skills/sophnet-docx/src/scripts/accept_changes.py +135 -135
  334. package/skills/sophnet-docx/src/scripts/comment.py +318 -318
  335. package/skills/sophnet-docx/src/scripts/ensure_uv_env.sh +68 -68
  336. package/skills/sophnet-docx/src/scripts/office/helpers/merge_runs.py +199 -199
  337. package/skills/sophnet-docx/src/scripts/office/helpers/simplify_redlines.py +197 -197
  338. package/skills/sophnet-docx/src/scripts/office/pack.py +159 -159
  339. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
  340. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
  341. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
  342. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
  343. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
  344. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
  345. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
  346. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
  347. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
  348. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
  349. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
  350. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
  351. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
  352. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
  353. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
  354. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
  355. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
  356. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
  357. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
  358. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
  359. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
  360. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
  361. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
  362. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
  363. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
  364. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
  365. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
  366. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
  367. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
  368. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
  369. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
  370. package/skills/sophnet-docx/src/scripts/office/schemas/mce/mc.xsd +75 -75
  371. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -560
  372. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -67
  373. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -14
  374. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -20
  375. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -13
  376. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
  377. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -8
  378. package/skills/sophnet-docx/src/scripts/office/soffice.py +183 -183
  379. package/skills/sophnet-docx/src/scripts/office/unpack.py +132 -132
  380. package/skills/sophnet-docx/src/scripts/office/validate.py +111 -111
  381. package/skills/sophnet-docx/src/scripts/office/validators/__init__.py +15 -15
  382. package/skills/sophnet-docx/src/scripts/office/validators/base.py +847 -847
  383. package/skills/sophnet-docx/src/scripts/office/validators/docx.py +446 -446
  384. package/skills/sophnet-docx/src/scripts/office/validators/pptx.py +275 -275
  385. package/skills/sophnet-docx/src/scripts/office/validators/redlining.py +247 -247
  386. package/skills/sophnet-docx/src/scripts/templates/comments.xml +3 -3
  387. package/skills/sophnet-docx/src/scripts/templates/commentsExtended.xml +3 -3
  388. package/skills/sophnet-docx/src/scripts/templates/commentsExtensible.xml +3 -3
  389. package/skills/sophnet-docx/src/scripts/templates/commentsIds.xml +3 -3
  390. package/skills/sophnet-docx/src/scripts/templates/people.xml +3 -3
  391. package/skills/sophnet-docx/src/scripts/upload_file.sh +96 -96
  392. package/skills/sophnet-docx/src/uv.lock +320 -320
  393. package/skills/sophnet-face-search/skill.json +20 -20
  394. package/skills/sophnet-face-search/src/SKILL.md +115 -115
  395. package/skills/sophnet-face-search/src/pyproject.toml +11 -11
  396. package/skills/sophnet-face-search/src/scripts/face_search.py +335 -335
  397. package/skills/sophnet-face-search/src/uv.lock +508 -508
  398. package/skills/sophnet-id-photo/skill.json +20 -20
  399. package/skills/sophnet-id-photo/src/SKILL.md +107 -107
  400. package/skills/sophnet-id-photo/src/pyproject.toml +10 -10
  401. package/skills/sophnet-id-photo/src/scripts/id_photo.py +540 -540
  402. package/skills/sophnet-id-photo/src/scripts/id_photo_compliance.py +215 -215
  403. package/skills/sophnet-id-photo/src/scripts/id_photo_face_crop.py +313 -313
  404. package/skills/sophnet-image-edit/skill.json +20 -20
  405. package/skills/sophnet-image-edit/src/SKILL.md +140 -140
  406. package/skills/sophnet-image-edit/src/pyproject.toml +9 -9
  407. package/skills/sophnet-image-edit/src/scripts/edit_and_preview.sh +68 -68
  408. package/skills/sophnet-image-edit/src/scripts/edit_image.py +279 -279
  409. package/skills/sophnet-image-edit/src/uv.lock +234 -234
  410. package/skills/sophnet-image-generate/skill.json +20 -20
  411. package/skills/sophnet-image-generate/src/SKILL.md +62 -62
  412. package/skills/sophnet-image-generate/src/pyproject.toml +9 -9
  413. package/skills/sophnet-image-generate/src/scripts/generate_image.py +156 -156
  414. package/skills/sophnet-image-generate/src/uv.lock +234 -234
  415. package/skills/sophnet-image-ocr/skill.json +20 -20
  416. package/skills/sophnet-image-ocr/src/SKILL.md +167 -167
  417. package/skills/sophnet-image-ocr/src/pyproject.toml +13 -13
  418. package/skills/sophnet-image-ocr/src/scripts/ocr.py +225 -225
  419. package/skills/sophnet-image-ocr/src/uv.lock +234 -234
  420. package/skills/sophnet-infinite-talk/skill.json +20 -20
  421. package/skills/sophnet-infinite-talk/src/SKILL.md +140 -140
  422. package/skills/sophnet-infinite-talk/src/pyproject.toml +9 -9
  423. package/skills/sophnet-infinite-talk/src/scripts/gen.py +172 -172
  424. package/skills/sophnet-oss/skill.json +27 -27
  425. package/skills/sophnet-oss/src/SKILL.md +118 -118
  426. package/skills/sophnet-oss/src/pyproject.toml +8 -8
  427. package/skills/sophnet-oss/src/scripts/upload_file.py +43 -43
  428. package/skills/sophnet-pdf/skill.json +20 -20
  429. package/skills/sophnet-pdf/src/SKILL.md +413 -413
  430. package/skills/sophnet-pdf/src/forms.md +297 -297
  431. package/skills/sophnet-pdf/src/pyproject.toml +14 -14
  432. package/skills/sophnet-pdf/src/reference.md +611 -611
  433. package/skills/sophnet-pdf/src/scripts/check_bounding_boxes.py +65 -65
  434. package/skills/sophnet-pdf/src/scripts/check_fillable_fields.py +11 -11
  435. package/skills/sophnet-pdf/src/scripts/convert_pdf_to_images.py +33 -33
  436. package/skills/sophnet-pdf/src/scripts/create_validation_image.py +37 -37
  437. package/skills/sophnet-pdf/src/scripts/enhance_tutorial.py +557 -557
  438. package/skills/sophnet-pdf/src/scripts/ensure_uv_env.sh +68 -68
  439. package/skills/sophnet-pdf/src/scripts/extract_form_field_info.py +122 -122
  440. package/skills/sophnet-pdf/src/scripts/extract_form_structure.py +115 -115
  441. package/skills/sophnet-pdf/src/scripts/extract_pdf_content.py +34 -34
  442. package/skills/sophnet-pdf/src/scripts/fill_fillable_fields.py +98 -98
  443. package/skills/sophnet-pdf/src/scripts/fill_pdf_form_with_annotations.py +107 -107
  444. package/skills/sophnet-pdf/src/scripts/upload_file.sh +88 -88
  445. package/skills/sophnet-pdf/src/uv.lock +537 -537
  446. package/skills/sophnet-qa-install/skill.json +27 -27
  447. package/skills/sophnet-qa-install/src/SKILL.md +210 -210
  448. package/skills/sophnet-qa-install/src/pyproject.toml +6 -6
  449. package/skills/sophnet-qa-install/src/scripts/backup_md.py +35 -35
  450. package/skills/sophnet-qa-install/src/scripts/check_installed.py +143 -143
  451. package/skills/sophnet-qa-install/src/scripts/update_config.py +142 -142
  452. package/skills/sophnet-qa-install/src/scripts/update_md.py +73 -73
  453. package/skills/sophnet-schedule/skill.json +20 -20
  454. package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -321
  455. package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -145
  456. package/skills/sophnet-schedule/src/SKILL.md +1050 -1050
  457. package/skills/sophnet-schedule/src/_meta.json +6 -6
  458. package/skills/sophnet-schedule/src/api/models.py +245 -245
  459. package/skills/sophnet-schedule/src/apps/add_event.py +237 -237
  460. package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -112
  461. package/skills/sophnet-schedule/src/apps/check_roc.py +246 -246
  462. package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -342
  463. package/skills/sophnet-schedule/src/apps/import_events.py +216 -216
  464. package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -140
  465. package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -169
  466. package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -174
  467. package/skills/sophnet-schedule/src/compat.py +66 -66
  468. package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -96
  469. package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -44
  470. package/skills/sophnet-schedule/src/config/settings.py +133 -133
  471. package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -92
  472. package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -437
  473. package/skills/sophnet-schedule/src/gcal/client.py +374 -374
  474. package/skills/sophnet-schedule/src/gcal/models.py +91 -91
  475. package/skills/sophnet-schedule/src/requirements.txt +6 -6
  476. package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -85
  477. package/skills/sophnet-schedule/src/server.py +669 -669
  478. package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -139
  479. package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -96
  480. package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -117
  481. package/skills/sophnet-schedule/src/services/event_classifier.py +100 -100
  482. package/skills/sophnet-schedule/src/services/event_diff.py +160 -160
  483. package/skills/sophnet-schedule/src/services/google_integration.py +500 -500
  484. package/skills/sophnet-schedule/src/services/job_store.py +100 -100
  485. package/skills/sophnet-schedule/src/services/local_event_store.py +266 -266
  486. package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -116
  487. package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -31
  488. package/skills/sophnet-schedule/src/services/table_parser.py +286 -286
  489. package/skills/sophnet-schedule/src/services/task_builder.py +167 -167
  490. package/skills/sophnet-schedule/src/services/time_window.py +72 -72
  491. package/skills/sophnet-sticker-edit/skill.json +27 -27
  492. package/skills/sophnet-sticker-edit/src/SKILL.md +80 -80
  493. package/skills/sophnet-sticker-edit/src/pyproject.toml +9 -9
  494. package/skills/sophnet-sticker-edit/src/scripts/edit_sticker_image.py +403 -403
  495. package/skills/sophnet-stock/skill.json +20 -20
  496. package/skills/sophnet-stock/src/App-Plan.md +442 -442
  497. package/skills/sophnet-stock/src/README.md +214 -214
  498. package/skills/sophnet-stock/src/SKILL.md +236 -236
  499. package/skills/sophnet-stock/src/TODO.md +394 -394
  500. package/skills/sophnet-stock/src/_meta.json +5 -5
  501. package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -408
  502. package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -233
  503. package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -288
  504. package/skills/sophnet-stock/src/docs/README.md +95 -95
  505. package/skills/sophnet-stock/src/docs/USAGE.md +465 -465
  506. package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -2565
  507. package/skills/sophnet-stock/src/scripts/dividends.py +365 -365
  508. package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -582
  509. package/skills/sophnet-stock/src/scripts/portfolio.py +548 -548
  510. package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -342
  511. package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -409
  512. package/skills/sophnet-stock/src/scripts/watchlist.py +336 -336
  513. package/skills/sophnet-training-install/skill.json +27 -27
  514. package/skills/sophnet-training-install/src/SKILL.md +211 -211
  515. package/skills/sophnet-training-install/src/pyproject.toml +6 -6
  516. package/skills/sophnet-training-install/src/scripts/backup_md.py +35 -35
  517. package/skills/sophnet-training-install/src/scripts/check_installed.py +144 -144
  518. package/skills/sophnet-training-install/src/scripts/update_config.py +142 -142
  519. package/skills/sophnet-training-install/src/scripts/update_md.py +73 -73
  520. package/skills/sophnet-tts/skill.json +20 -20
  521. package/skills/sophnet-tts/src/SKILL.md +79 -79
  522. package/skills/sophnet-tts/src/pyproject.toml +9 -9
  523. package/skills/sophnet-tts/src/scripts/gen_tts.py +130 -130
  524. package/skills/sophnet-video-generate/skill.json +37 -37
  525. package/skills/sophnet-video-generate/src/SKILL.md +117 -117
  526. package/skills/sophnet-video-generate/src/scripts/gen_video.py +321 -321
  527. package/skills/sophnet-xlsx/skill.json +20 -20
  528. package/skills/sophnet-xlsx/src/SKILL.md +399 -399
  529. package/skills/sophnet-xlsx/src/pyproject.toml +11 -11
  530. package/skills/sophnet-xlsx/src/scripts/ensure_uv_env.sh +68 -68
  531. package/skills/sophnet-xlsx/src/scripts/office/helpers/merge_runs.py +199 -199
  532. package/skills/sophnet-xlsx/src/scripts/office/helpers/simplify_redlines.py +197 -197
  533. package/skills/sophnet-xlsx/src/scripts/office/pack.py +159 -159
  534. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
  535. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
  536. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
  537. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
  538. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
  539. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
  540. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
  541. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
  542. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
  543. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
  544. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
  545. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
  546. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
  547. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
  548. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
  549. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
  550. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
  551. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
  552. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
  553. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
  554. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
  555. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
  556. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
  557. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
  558. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
  559. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
  560. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
  561. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
  562. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
  563. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
  564. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
  565. package/skills/sophnet-xlsx/src/scripts/office/schemas/mce/mc.xsd +75 -75
  566. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -560
  567. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -67
  568. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -14
  569. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -20
  570. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -13
  571. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
  572. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -8
  573. package/skills/sophnet-xlsx/src/scripts/office/soffice.py +183 -183
  574. package/skills/sophnet-xlsx/src/scripts/office/unpack.py +132 -132
  575. package/skills/sophnet-xlsx/src/scripts/office/validate.py +111 -111
  576. package/skills/sophnet-xlsx/src/scripts/office/validators/__init__.py +15 -15
  577. package/skills/sophnet-xlsx/src/scripts/office/validators/base.py +847 -847
  578. package/skills/sophnet-xlsx/src/scripts/office/validators/docx.py +446 -446
  579. package/skills/sophnet-xlsx/src/scripts/office/validators/pptx.py +275 -275
  580. package/skills/sophnet-xlsx/src/scripts/office/validators/redlining.py +247 -247
  581. package/skills/sophnet-xlsx/src/scripts/recalc.py +184 -184
  582. package/skills/sophnet-xlsx/src/scripts/upload_file.sh +96 -96
  583. package/skills/sophnet-xlsx/src/uv.lock +319 -319
  584. package/skills/ui-ux-pro-max/skill.json +20 -20
  585. package/skills/ui-ux-pro-max/src/SKILL.md +377 -377
  586. package/skills/ui-ux-pro-max/src/data/icons.csv +101 -101
  587. package/skills/ui-ux-pro-max/src/data/react-performance.csv +45 -45
  588. package/skills/ui-ux-pro-max/src/data/stacks/astro.csv +54 -54
  589. package/skills/ui-ux-pro-max/src/data/stacks/jetpack-compose.csv +53 -53
  590. package/skills/ui-ux-pro-max/src/data/stacks/nuxt-ui.csv +51 -51
  591. package/skills/ui-ux-pro-max/src/data/stacks/nuxtjs.csv +59 -59
  592. package/skills/ui-ux-pro-max/src/data/stacks/shadcn.csv +61 -61
  593. package/skills/ui-ux-pro-max/src/data/typography.csv +57 -57
  594. package/skills/ui-ux-pro-max/src/data/ui-reasoning.csv +101 -101
  595. package/skills/ui-ux-pro-max/src/data/web-interface.csv +31 -31
  596. package/skills/ui-ux-pro-max/src/scripts/core.py +253 -253
  597. package/skills/ui-ux-pro-max/src/scripts/design_system.py +1067 -1067
  598. package/skills/video-understand/skill.json +20 -20
  599. package/skills/video-understand/src/SKILL.md +79 -79
  600. package/skills/video-understand/src/scripts/video_understand.py +204 -204
  601. package/skills/weather/skill.json +19 -19
  602. package/skills/weather/src/SKILL.md +112 -112
  603. package/skills/web-scraper/skill.json +20 -20
  604. package/skills/web-scraper/src/SKILL.md +101 -101
  605. package/skills/web-scraper/src/scripts/scrape.py +270 -270
  606. package/skills/website-builder/skill.json +20 -20
  607. package/skills/website-builder/src/SKILL.md +266 -266
  608. package/skills/website-builder/src/scripts/deploy_site.sh +46 -46
  609. package/skills/wechat-article-publisher/skill.json +20 -20
  610. package/skills/wechat-article-publisher/src/SKILL.md +60 -60
  611. package/skills/wechat-article-publisher/src/config.json +6 -6
  612. package/skills/wechat-article-publisher/src/pyproject.toml +12 -12
  613. package/skills/wechat-article-publisher/src/scripts/publish_wechat.py +825 -825
  614. package/skills/xiaohongshu/skill.json +20 -20
  615. package/skills/xiaohongshu/src/SKILL.md +91 -91
  616. package/skills/xiaohongshu/src/_meta.json +5 -5
  617. package/skills/xiaohongshu/src/assets/card.html +216 -216
  618. package/skills/xiaohongshu/src/assets/cover.html +82 -82
  619. package/skills/xiaohongshu/src/assets/example.md +84 -84
  620. package/skills/xiaohongshu/src/assets/styles.css +318 -318
  621. package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -737
  622. package/skills/xiaohongshu/src/scripts/sign_server.py +158 -158
  623. package/skills/xiaohongshu/src/scripts/stealth.min.js +6 -6
  624. package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -186
  625. package/skills/xiaohongshu/src/workflow.py +185 -185
  626. package/src/commands/agent.js +112 -112
  627. package/src/commands/download.js +101 -101
  628. package/src/commands/info.js +58 -58
  629. package/src/commands/list.js +71 -71
  630. package/src/utils/agents.js +36 -36
  631. package/src/utils/config.js +22 -22
  632. package/src/utils/paths.js +31 -31
  633. package/src/utils/versions.js +57 -57
@@ -1,704 +1,704 @@
1
- #!/usr/bin/env python3
2
- """
3
- parse_schedule.py - 解析 Markdown 日程表,输出今日/近期事项、冲突检测结果
4
-
5
- 用法:
6
- python3 parse_schedule.py --file /home/node/.openclaw/workspace/日程.md [--mode today|tomorrow|week|all]
7
- python3 parse_schedule.py --stdin [--mode today] # 从 stdin 读取 Markdown 内容
8
-
9
- 输出:JSON,包含 events / upcoming / conflicts / recurring / tasks
10
- """
11
- from __future__ import annotations
12
-
13
- import sys
14
- import re
15
- import json
16
- import argparse
17
- from datetime import datetime, date, timedelta
18
- from typing import Optional, List, Dict
19
-
20
-
21
- PRIORITY_ORDER = {"高": 0, "中": 1, "低": 2, "": 1}
22
-
23
- TASK_KEYWORDS = {"提交", "截止", "deadline", "交付", "汇报", "发布", "上线"}
24
-
25
-
26
- def parse_args():
27
- p = argparse.ArgumentParser(description="解析 Markdown 日程表")
28
- p.add_argument("--file", help="Markdown 日程文件路径")
29
- p.add_argument("--stdin", action="store_true", help="从 stdin 读取内容")
30
- p.add_argument(
31
- "--mode",
32
- choices=["today", "tomorrow", "week", "all"],
33
- default="today",
34
- help="筛选范围(默认 today)",
35
- )
36
- p.add_argument("--now", help="覆盖当前时间(格式 YYYY-MM-DD HH:MM,测试用)")
37
- p.add_argument(
38
- "--column-map",
39
- help=(
40
- "自定义列名映射,格式:'自定义列名=字段名,...'\n"
41
- "字段名可选:date/start/end/title/location/participants/priority/advance/note\n"
42
- "示例:--column-map '会期=date,主旨=title,时间段开始=start'"
43
- ),
44
- )
45
- return p.parse_args()
46
-
47
-
48
- def read_content(args) -> str:
49
- if args.stdin:
50
- return sys.stdin.read()
51
- if args.file:
52
- path = args.file.replace("~", __import__("os").path.expanduser("~"))
53
- with open(path, encoding="utf-8") as f:
54
- return f.read()
55
- print("错误:请指定 --file 或 --stdin", file=sys.stderr)
56
- sys.exit(1)
57
-
58
-
59
- def parse_global_config(content: str) -> dict:
60
- """从文件头部注释和引用块提取全局配置"""
61
- cfg = {
62
- "channel": "dingtalk",
63
- "default_advance_minutes": 15,
64
- "timezone": "Asia/Shanghai",
65
- }
66
- for line in content.splitlines():
67
- line = line.strip()
68
- m = re.match(r">\s*\*?\*?提醒渠道\*?\*?[::]\s*(\S+)", line)
69
- if m:
70
- cfg["channel"] = m.group(1).strip()
71
- m = re.match(r">\s*\*?\*?默认提前提醒\*?\*?[::]\s*(\d+)", line)
72
- if m:
73
- cfg["default_advance_minutes"] = int(m.group(1))
74
- m = re.match(r">\s*\*?\*?时区\*?\*?[::]\s*(\S+)", line)
75
- if m:
76
- cfg["timezone"] = m.group(1)
77
- return cfg
78
-
79
-
80
- def normalize_date(raw: str) -> Optional[date]:
81
- raw = raw.strip()
82
- for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%m-%d", "%m/%d"):
83
- try:
84
- d = datetime.strptime(raw, fmt)
85
- if d.year == 1900:
86
- d = d.replace(year=date.today().year)
87
- return d.date()
88
- except ValueError:
89
- pass
90
- return None
91
-
92
-
93
- def normalize_time(raw: str) -> Optional[int]:
94
- """返回分钟数(从当天0点起)"""
95
- raw = raw.strip().replace(":", ":")
96
- m = re.match(r"(\d{1,2}):(\d{2})", raw)
97
- if m:
98
- return int(m.group(1)) * 60 + int(m.group(2))
99
- return None
100
-
101
-
102
- # ─────────────────────────────────────────────────────────
103
- # 列名自动映射(宽松模式,支持不标准的 Excel/CSV 转换结果)
104
- # ─────────────────────────────────────────────────────────
105
-
106
- # 各字段的候选列名(支持中英文、简写、常见变体)
107
- COLUMN_ALIASES = {
108
- "date": {"日期", "date", "Date", "DATE", "时间", "日", "活动日期", "会议日期", "事件日期",
109
- "日程日期", "计划日期"},
110
- "start": {"开始时间", "开始", "start", "Start", "START", "开始时刻", "起始时间", "from", "From",
111
- "开始时间段", "时间开始", "起始"},
112
- "end": {"结束时间", "结束", "end", "End", "END", "结束时刻", "to", "To",
113
- "时间结束", "截止", "终止"},
114
- "title": {"事项", "标题", "title", "Title", "TITLE", "内容", "会议", "活动", "事件",
115
- "subject", "Subject", "name", "Name", "任务", "描述", "会议名称", "活动名称",
116
- "事项名称", "议题", "主题", "项目", "meeting", "Meeting", "event", "Event",
117
- "topic", "Topic", "item", "Item"},
118
- "location": {"地点", "location", "Location", "LOCATION", "地址", "会议室", "venue", "Venue",
119
- "place", "Place", "room", "Room", "场所", "场地"},
120
- "participants": {"参与者", "人员", "participants", "Participants", "与会者", "成员", "attendees",
121
- "Attendees", "负责人", "owner", "Owner", "参会人", "参会人员", "出席人员",
122
- "相关人员", "联系人"},
123
- "priority": {"优先级", "priority", "Priority", "PRIORITY", "重要程度", "紧急度", "级别",
124
- "importance", "Importance", "urgency", "Urgency"},
125
- "advance": {"提前(分钟)", "提前提醒", "提前", "advance", "Advance", "reminder", "Reminder",
126
- "提前时间", "提醒时间", "提前分钟"},
127
- "note": {"备注", "note", "Note", "NOTE", "notes", "Notes", "说明", "描述", "remark",
128
- "Remark", "comments", "Comments", "附注", "补充", "其他"},
129
- }
130
-
131
-
132
- def parse_user_column_map(raw: str) -> dict:
133
- """
134
- 解析用户自定义列名映射字符串。
135
- 输入格式:'会期=date,主旨=title,时间段开始=start'
136
- 返回:{'会期': 'date', '主旨': 'title', '时间段开始': 'start'}
137
- """
138
- VALID_FIELDS = {"date", "start", "end", "title", "location",
139
- "participants", "priority", "advance", "note"}
140
- result = {}
141
- for part in raw.split(","):
142
- part = part.strip()
143
- if "=" not in part:
144
- continue
145
- custom_name, _, field = part.partition("=")
146
- custom_name = custom_name.strip()
147
- field = field.strip()
148
- if field in VALID_FIELDS and custom_name:
149
- result[custom_name] = field
150
- return result
151
-
152
-
153
- def detect_column_mapping(header_cells: List[str],
154
- user_map: Optional[dict] = None) -> dict:
155
- """
156
- 根据表头自动推断列索引映射。
157
- 优先级:用户自定义映射 > 精确别名匹配 > 子串匹配。
158
- 返回 {字段名: 列索引}
159
- """
160
- mapping: dict = {}
161
- used_indices: set = set()
162
-
163
- # 第零轮:用户自定义映射(最高优先级)
164
- if user_map:
165
- for i, cell in enumerate(header_cells):
166
- cell_clean = cell.strip()
167
- if cell_clean in user_map:
168
- field = user_map[cell_clean]
169
- if field not in mapping: # 同一字段只取第一个
170
- mapping[field] = i
171
- used_indices.add(i)
172
-
173
- # 第一轮:精确匹配
174
- for field, aliases in COLUMN_ALIASES.items():
175
- for i, cell in enumerate(header_cells):
176
- if i in used_indices:
177
- continue
178
- if cell.strip() in aliases:
179
- mapping[field] = i
180
- used_indices.add(i)
181
- break
182
-
183
- # 第二轮:子串匹配(仅对未匹配的字段和未使用的列)
184
- for field, aliases in COLUMN_ALIASES.items():
185
- if field in mapping:
186
- continue
187
- for i, cell in enumerate(header_cells):
188
- if i in used_indices:
189
- continue
190
- cell_clean = cell.strip()
191
- matched = False
192
- for alias in aliases:
193
- if not alias:
194
- continue
195
- # 列名包含别名(如"会议名称"包含"名称"→title)
196
- # 但要避免短词误匹配(别名长度至少2)
197
- if len(alias) >= 2 and (alias in cell_clean or cell_clean in alias):
198
- mapping[field] = i
199
- used_indices.add(i)
200
- matched = True
201
- break
202
- if matched:
203
- break
204
-
205
- return mapping
206
-
207
-
208
- def report_unresolved_columns(header_cells: List[str], mapping: dict) -> List[str]:
209
- """返回未能映射到任何已知字段的列名列表"""
210
- mapped_indices = set(mapping.values())
211
- return [
212
- header_cells[i].strip()
213
- for i in range(len(header_cells))
214
- if i not in mapped_indices and header_cells[i].strip()
215
- ]
216
-
217
-
218
- def check_required_fields(mapping: dict) -> List[str]:
219
- """检查必要字段是否都已映射,返回缺失字段名"""
220
- required = ["date", "title"]
221
- return [f for f in required if f not in mapping]
222
-
223
-
224
- def is_standard_header(cells: List[str]) -> bool:
225
- """判断是否是标准格式表头(首列精确为日期/Date/date)"""
226
- SCHEDULE_HEADERS = {"日期", "Date", "date"}
227
- return bool(cells) and cells[0].strip() in SCHEDULE_HEADERS
228
-
229
-
230
- def is_any_schedule_header(cells: List[str]) -> bool:
231
- """宽松判断:表头中是否含有日期相关列"""
232
- date_aliases = COLUMN_ALIASES["date"]
233
- title_aliases = COLUMN_ALIASES["title"]
234
- has_date = any(c.strip() in date_aliases for c in cells)
235
- has_title = any(c.strip() in title_aliases for c in cells)
236
- return has_date and has_title
237
-
238
-
239
- def extract_event_by_mapping(cells: List[str], mapping: dict, default_advance: int,
240
- section_name: str) -> Optional[dict]:
241
- """根据列映射从数据行提取事项"""
242
- def get(field):
243
- idx = mapping.get(field)
244
- if idx is not None and idx < len(cells):
245
- return cells[idx].strip()
246
- return ""
247
-
248
- date_val = normalize_date(get("date"))
249
- if not date_val:
250
- return None
251
-
252
- title = get("title")
253
- if not title:
254
- return None
255
-
256
- start_str = get("start")
257
- end_str = get("end")
258
- start_min = normalize_time(start_str) if start_str else None
259
- end_min = normalize_time(end_str) if end_str else None
260
-
261
- priority_raw = get("priority")
262
- priority = priority_raw if priority_raw in ("高", "中", "低") else "中"
263
-
264
- advance_raw = get("advance")
265
- try:
266
- advance_min = int(advance_raw) if advance_raw else default_advance
267
- except ValueError:
268
- advance_min = default_advance
269
-
270
- return {
271
- "date": date_val.isoformat(),
272
- "start_min": start_min,
273
- "end_min": end_min,
274
- "start_str": start_str,
275
- "end_str": end_str,
276
- "title": title,
277
- "location": get("location"),
278
- "participants": get("participants"),
279
- "priority": priority,
280
- "advance_min": advance_min,
281
- "note": get("note"),
282
- "section": section_name,
283
- "is_task": any(k in title + get("note") for k in TASK_KEYWORDS),
284
- }
285
-
286
-
287
- def parse_table_section(lines: List[str], section_name: str,
288
- default_advance: int,
289
- user_map: Optional[dict] = None) -> dict:
290
- """
291
- 解析日程表格。返回 dict:
292
- events: 解析出的事项列表
293
- unresolved_columns: 无法映射的列名列表(宽松模式下)
294
- missing_required: 缺失的必要字段(date/title)
295
- mode: 'standard' | 'loose' | 'failed'
296
- """
297
- in_table = False
298
- header_found = False
299
- column_mapping: Optional[dict] = None
300
- loose_mode = False
301
- events = []
302
- unresolved_columns: List[str] = []
303
- missing_required: List[str] = []
304
- parse_mode = "failed"
305
-
306
- TASK_HEADERS = {"截止日期", "DeadLine", "deadline", "Deadline"}
307
-
308
- for line in lines:
309
- stripped = line.strip()
310
- if not stripped.startswith("|"):
311
- if in_table:
312
- in_table = False
313
- header_found = False
314
- column_mapping = None
315
- loose_mode = False
316
- continue
317
-
318
- cells = [c.strip() for c in stripped.strip("|").split("|")]
319
- if len(cells) < 2:
320
- continue
321
-
322
- # 跳过任务表头,不被主日程解析器误抓
323
- if cells[0].strip() in TASK_HEADERS:
324
- continue
325
-
326
- # 检测表头
327
- if not in_table:
328
- if is_standard_header(cells):
329
- START_HEADERS = {"开始时间", "开始", "start", "Start", "START", "起始时间"}
330
- second_col = cells[1].strip() if len(cells) > 1 else ""
331
- if second_col in START_HEADERS:
332
- # 标准模式
333
- in_table = True
334
- header_found = True
335
- loose_mode = False
336
- column_mapping = None
337
- parse_mode = "standard"
338
- continue
339
- else:
340
- # 首列是"日期"但列序不标准,降级为宽松模式
341
- mapping = detect_column_mapping(cells, user_map)
342
- missing = check_required_fields(mapping)
343
- if not missing:
344
- in_table = True
345
- header_found = True
346
- loose_mode = True
347
- column_mapping = mapping
348
- parse_mode = "loose"
349
- unresolved_columns = report_unresolved_columns(cells, mapping)
350
- missing_required = []
351
- else:
352
- missing_required = missing
353
- parse_mode = "failed"
354
- continue
355
- elif is_any_schedule_header(cells) or user_map:
356
- # 宽松模式:自动映射 + 用户自定义映射
357
- mapping = detect_column_mapping(cells, user_map)
358
- missing = check_required_fields(mapping)
359
- if not missing:
360
- in_table = True
361
- header_found = True
362
- loose_mode = True
363
- column_mapping = mapping
364
- parse_mode = "loose"
365
- unresolved_columns = report_unresolved_columns(cells, mapping)
366
- missing_required = []
367
- else:
368
- missing_required = missing
369
- parse_mode = "failed"
370
- continue
371
- continue
372
- continue
373
-
374
- # 跳过分隔行
375
- if header_found and all(re.match(r"^[-:]+$", c) for c in cells if c):
376
- continue
377
-
378
- if not in_table:
379
- continue
380
-
381
- if loose_mode and column_mapping:
382
- # 宽松模式:用列映射提取
383
- ev = extract_event_by_mapping(cells, column_mapping, default_advance, section_name)
384
- if ev:
385
- events.append(ev)
386
- else:
387
- # 标准模式:按固定列序提取
388
- if len(cells) < 4:
389
- continue
390
- date_val = normalize_date(cells[0]) if cells[0] else None
391
- if not date_val:
392
- continue
393
- start_min = normalize_time(cells[1]) if len(cells) > 1 else None
394
- end_min = normalize_time(cells[2]) if len(cells) > 2 else None
395
- title = cells[3].strip() if len(cells) > 3 else ""
396
- location = cells[4].strip() if len(cells) > 4 else ""
397
- participants = cells[5].strip() if len(cells) > 5 else ""
398
- priority = cells[6].strip() if len(cells) > 6 else "中"
399
- advance_raw = cells[7].strip() if len(cells) > 7 else ""
400
- note = cells[8].strip() if len(cells) > 8 else ""
401
- if not title:
402
- continue
403
- try:
404
- advance_min = int(advance_raw) if advance_raw else default_advance
405
- except ValueError:
406
- advance_min = default_advance
407
- events.append({
408
- "date": date_val.isoformat(),
409
- "start_min": start_min,
410
- "end_min": end_min,
411
- "start_str": cells[1].strip() if len(cells) > 1 else "",
412
- "end_str": cells[2].strip() if len(cells) > 2 else "",
413
- "title": title,
414
- "location": location,
415
- "participants": participants,
416
- "priority": priority if priority in ("高", "中", "低") else "中",
417
- "advance_min": advance_min,
418
- "note": note,
419
- "section": section_name,
420
- "is_task": any(k in title + note for k in TASK_KEYWORDS),
421
- })
422
-
423
- return {
424
- "events": events,
425
- "mode": parse_mode,
426
- "unresolved_columns": unresolved_columns,
427
- "missing_required": missing_required,
428
- }
429
-
430
-
431
- def parse_task_section(lines: List[str], default_advance: int) -> list:
432
- """解析待提醒任务表(截止日期形式)"""
433
- in_table = False
434
- header_found = False
435
- tasks = []
436
-
437
- TASK_HEADERS = {"截止日期", "DeadLine", "deadline", "Deadline"}
438
-
439
- for line in lines:
440
- stripped = line.strip()
441
- if not stripped.startswith("|"):
442
- if in_table:
443
- in_table = False
444
- header_found = False
445
- continue
446
-
447
- cells = [c.strip() for c in stripped.strip("|").split("|")]
448
- if not cells:
449
- continue
450
-
451
- if cells[0] in TASK_HEADERS:
452
- in_table = True
453
- header_found = True
454
- continue
455
-
456
- if header_found and all(re.match(r"^[-:]+$", c) for c in cells if c):
457
- continue
458
-
459
- if not in_table:
460
- continue
461
-
462
- if len(cells) < 2:
463
- continue
464
-
465
- date_val = normalize_date(cells[0]) if cells[0] else None
466
- if not date_val:
467
- continue
468
-
469
- title = cells[1].strip() if len(cells) > 1 else ""
470
- priority = cells[3].strip() if len(cells) > 3 else "中"
471
- advance_raw = cells[4].strip() if len(cells) > 4 else ""
472
- note = cells[5].strip() if len(cells) > 5 else ""
473
-
474
- # 解析"提前1天"、"提前2天"形式
475
- advance_min = default_advance
476
- m = re.search(r"提前\s*(\d+)\s*天", advance_raw)
477
- if m:
478
- advance_min = int(m.group(1)) * 24 * 60
479
- else:
480
- m = re.search(r"(\d+)", advance_raw)
481
- if m:
482
- advance_min = int(m.group(1))
483
-
484
- tasks.append({
485
- "date": date_val.isoformat(),
486
- "start_min": 9 * 60, # 默认早上9点触发
487
- "end_min": 9 * 60 + 30,
488
- "start_str": "09:00",
489
- "end_str": "09:30",
490
- "title": f"📋 {title}",
491
- "location": "",
492
- "participants": cells[2].strip() if len(cells) > 2 else "",
493
- "priority": priority if priority in ("高", "中", "低") else "中",
494
- "advance_min": advance_min,
495
- "note": note,
496
- "section": "tasks",
497
- "is_task": True,
498
- })
499
-
500
- return tasks
501
-
502
-
503
- def detect_conflicts(events: list[dict]) -> list[dict]:
504
- """检测同日时间冲突"""
505
- by_date: dict[str, list[dict]] = {}
506
- for e in events:
507
- if e["start_min"] is None or e["end_min"] is None:
508
- continue
509
- by_date.setdefault(e["date"], []).append(e)
510
-
511
- conflicts = []
512
- for day, day_events in by_date.items():
513
- sorted_events = sorted(day_events, key=lambda x: x["start_min"])
514
- for i in range(len(sorted_events)):
515
- for j in range(i + 1, len(sorted_events)):
516
- a, b = sorted_events[i], sorted_events[j]
517
- if a["end_min"] <= b["start_min"]:
518
- # 不重叠,但检查是否过于紧凑(< 5分钟间隔)
519
- gap = b["start_min"] - a["end_min"]
520
- if 0 < gap < 5:
521
- conflicts.append({
522
- "type": "tight",
523
- "date": day,
524
- "event_a": a["title"],
525
- "event_b": b["title"],
526
- "a_time": f"{a['start_str']}–{a['end_str']}",
527
- "b_time": f"{b['start_str']}–{b['end_str']}",
528
- "gap_min": gap,
529
- "message": (
530
- f"⚠️ 紧凑安排:【{a['title']}】{a['start_str']}–{a['end_str']} "
531
- f"与【{b['title']}】{b['start_str']} 之间仅 {gap} 分钟,几乎没有缓冲时间。"
532
- ),
533
- })
534
- else:
535
- # 重叠
536
- overlap = a["end_min"] - b["start_min"]
537
- conflicts.append({
538
- "type": "overlap",
539
- "date": day,
540
- "event_a": a["title"],
541
- "event_b": b["title"],
542
- "a_time": f"{a['start_str']}–{a['end_str']}",
543
- "b_time": f"{b['start_str']}–{b['end_str']}",
544
- "overlap_min": overlap,
545
- "message": (
546
- f"⚠️ 时间冲突:【{a['title']}】{a['start_str']}–{a['end_str']} "
547
- f"与【{b['title']}】{b['start_str']}–{b['end_str']} "
548
- f"重叠 {overlap} 分钟。"
549
- ),
550
- })
551
- return conflicts
552
-
553
-
554
- def filter_upcoming(events: list, mode: str, now: datetime) -> list:
555
- """按 mode 筛选需要提醒的事项"""
556
- today = now.date()
557
- tomorrow = today + timedelta(days=1)
558
- week_end = today + timedelta(days=7)
559
- now_min = now.hour * 60 + now.minute
560
-
561
- result = []
562
- for e in events:
563
- try:
564
- ev_date = date.fromisoformat(e["date"])
565
- except ValueError:
566
- continue
567
-
568
- # 已过期跳过
569
- if ev_date < today:
570
- continue
571
- if ev_date == today and e["end_min"] is not None and e["end_min"] < now_min:
572
- continue
573
-
574
- # 低优先级且非任务,默认跳过
575
- if e["priority"] == "低" and not e["is_task"]:
576
- continue
577
-
578
- include = False
579
- if mode == "today" and ev_date == today:
580
- include = True
581
- elif mode == "tomorrow" and ev_date == tomorrow:
582
- include = True
583
- elif mode == "week" and today <= ev_date <= week_end:
584
- include = True
585
- elif mode == "all":
586
- include = True
587
-
588
- # today 模式额外:明日高优先级也纳入
589
- if mode == "today" and ev_date == tomorrow and e["priority"] == "高":
590
- include = True
591
-
592
- if include:
593
- result.append(e)
594
-
595
- result.sort(key=lambda x: (x["date"], x["start_min"] or 0))
596
- return result
597
-
598
-
599
- def build_reminder_time(ev: dict, now: datetime) -> Optional[str]:
600
- """计算提醒时刻的 cron 表达式(单次)"""
601
- if ev["start_min"] is None:
602
- return None
603
- try:
604
- ev_date = date.fromisoformat(ev["date"])
605
- except ValueError:
606
- return None
607
-
608
- remind_total_min = ev["start_min"] - ev["advance_min"]
609
- if remind_total_min < 0:
610
- remind_total_min = 0
611
-
612
- r_hour = remind_total_min // 60
613
- r_min = remind_total_min % 60
614
-
615
- # 单次 cron:分 时 日 月 *
616
- return f"{r_min} {r_hour} {ev_date.day} {ev_date.month} *"
617
-
618
-
619
- def build_reminder_time_str(ev: dict) -> str:
620
- """人类可读的提醒时刻"""
621
- if ev["start_min"] is None:
622
- return ""
623
- remind_total_min = max(0, ev["start_min"] - ev["advance_min"])
624
- r_hour = remind_total_min // 60
625
- r_min = remind_total_min % 60
626
- return f"{r_hour:02d}:{r_min:02d}"
627
-
628
-
629
- def main():
630
- args = parse_args()
631
- content = read_content(args)
632
- cfg = parse_global_config(content)
633
- now = datetime.now()
634
- if args.now:
635
- try:
636
- now = datetime.strptime(args.now, "%Y-%m-%d %H:%M")
637
- except ValueError:
638
- pass
639
-
640
- lines = content.splitlines()
641
-
642
- # 解析用户自定义列名映射
643
- user_map = parse_user_column_map(args.column_map) if args.column_map else None
644
-
645
- # 解析主日程表
646
- schedule_result = parse_table_section(
647
- lines, "schedule", cfg["default_advance_minutes"], user_map
648
- )
649
- events = schedule_result["events"]
650
- # 解析任务表
651
- tasks = parse_task_section(lines, cfg["default_advance_minutes"])
652
- all_events = events + tasks
653
-
654
- # 冲突检测(仅对 schedule 类事项)
655
- conflicts = detect_conflicts(events)
656
-
657
- # 筛选近期/今日事项
658
- upcoming = filter_upcoming(all_events, args.mode, now)
659
-
660
- # 为每个事项计算提醒时刻
661
- for ev in upcoming:
662
- ev["remind_cron"] = build_reminder_time(ev, now)
663
- ev["remind_time_str"] = build_reminder_time_str(ev)
664
-
665
- output = {
666
- "meta": {
667
- "mode": args.mode,
668
- "now": now.strftime("%Y-%m-%d %H:%M"),
669
- "config": cfg,
670
- "parse_mode": schedule_result["mode"],
671
- },
672
- "summary": {
673
- "total_events": len(all_events),
674
- "upcoming_count": len(upcoming),
675
- "conflict_count": len(conflicts),
676
- },
677
- "upcoming": upcoming,
678
- "conflicts": conflicts,
679
- }
680
-
681
- # 列名问题提示(宽松模式下,让 agent 告知用户)
682
- if schedule_result["unresolved_columns"]:
683
- output["warnings"] = {
684
- "unresolved_columns": schedule_result["unresolved_columns"],
685
- "message": (
686
- f"⚠️ 以下列名无法自动识别,其内容已忽略:"
687
- f"{', '.join(schedule_result['unresolved_columns'])}。"
688
- f"如需记录这些列,请告诉我它们对应的字段(如:'会期' = 日期,'主旨' = 事项)。"
689
- ),
690
- }
691
- if schedule_result["missing_required"]:
692
- output["errors"] = {
693
- "missing_required": schedule_result["missing_required"],
694
- "message": (
695
- f"❌ 表格缺少必要字段:{', '.join(schedule_result['missing_required'])},"
696
- f"无法解析。请确认表格中包含日期列和事项/标题列。"
697
- ),
698
- }
699
-
700
- print(json.dumps(output, ensure_ascii=False, indent=2))
701
-
702
-
703
- if __name__ == "__main__":
704
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ parse_schedule.py - 解析 Markdown 日程表,输出今日/近期事项、冲突检测结果
4
+
5
+ 用法:
6
+ python3 parse_schedule.py --file /home/node/.openclaw/workspace/日程.md [--mode today|tomorrow|week|all]
7
+ python3 parse_schedule.py --stdin [--mode today] # 从 stdin 读取 Markdown 内容
8
+
9
+ 输出:JSON,包含 events / upcoming / conflicts / recurring / tasks
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import sys
14
+ import re
15
+ import json
16
+ import argparse
17
+ from datetime import datetime, date, timedelta
18
+ from typing import Optional, List, Dict
19
+
20
+
21
+ PRIORITY_ORDER = {"高": 0, "中": 1, "低": 2, "": 1}
22
+
23
+ TASK_KEYWORDS = {"提交", "截止", "deadline", "交付", "汇报", "发布", "上线"}
24
+
25
+
26
+ def parse_args():
27
+ p = argparse.ArgumentParser(description="解析 Markdown 日程表")
28
+ p.add_argument("--file", help="Markdown 日程文件路径")
29
+ p.add_argument("--stdin", action="store_true", help="从 stdin 读取内容")
30
+ p.add_argument(
31
+ "--mode",
32
+ choices=["today", "tomorrow", "week", "all"],
33
+ default="today",
34
+ help="筛选范围(默认 today)",
35
+ )
36
+ p.add_argument("--now", help="覆盖当前时间(格式 YYYY-MM-DD HH:MM,测试用)")
37
+ p.add_argument(
38
+ "--column-map",
39
+ help=(
40
+ "自定义列名映射,格式:'自定义列名=字段名,...'\n"
41
+ "字段名可选:date/start/end/title/location/participants/priority/advance/note\n"
42
+ "示例:--column-map '会期=date,主旨=title,时间段开始=start'"
43
+ ),
44
+ )
45
+ return p.parse_args()
46
+
47
+
48
+ def read_content(args) -> str:
49
+ if args.stdin:
50
+ return sys.stdin.read()
51
+ if args.file:
52
+ path = args.file.replace("~", __import__("os").path.expanduser("~"))
53
+ with open(path, encoding="utf-8") as f:
54
+ return f.read()
55
+ print("错误:请指定 --file 或 --stdin", file=sys.stderr)
56
+ sys.exit(1)
57
+
58
+
59
+ def parse_global_config(content: str) -> dict:
60
+ """从文件头部注释和引用块提取全局配置"""
61
+ cfg = {
62
+ "channel": "dingtalk",
63
+ "default_advance_minutes": 15,
64
+ "timezone": "Asia/Shanghai",
65
+ }
66
+ for line in content.splitlines():
67
+ line = line.strip()
68
+ m = re.match(r">\s*\*?\*?提醒渠道\*?\*?[::]\s*(\S+)", line)
69
+ if m:
70
+ cfg["channel"] = m.group(1).strip()
71
+ m = re.match(r">\s*\*?\*?默认提前提醒\*?\*?[::]\s*(\d+)", line)
72
+ if m:
73
+ cfg["default_advance_minutes"] = int(m.group(1))
74
+ m = re.match(r">\s*\*?\*?时区\*?\*?[::]\s*(\S+)", line)
75
+ if m:
76
+ cfg["timezone"] = m.group(1)
77
+ return cfg
78
+
79
+
80
+ def normalize_date(raw: str) -> Optional[date]:
81
+ raw = raw.strip()
82
+ for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%m-%d", "%m/%d"):
83
+ try:
84
+ d = datetime.strptime(raw, fmt)
85
+ if d.year == 1900:
86
+ d = d.replace(year=date.today().year)
87
+ return d.date()
88
+ except ValueError:
89
+ pass
90
+ return None
91
+
92
+
93
+ def normalize_time(raw: str) -> Optional[int]:
94
+ """返回分钟数(从当天0点起)"""
95
+ raw = raw.strip().replace(":", ":")
96
+ m = re.match(r"(\d{1,2}):(\d{2})", raw)
97
+ if m:
98
+ return int(m.group(1)) * 60 + int(m.group(2))
99
+ return None
100
+
101
+
102
+ # ─────────────────────────────────────────────────────────
103
+ # 列名自动映射(宽松模式,支持不标准的 Excel/CSV 转换结果)
104
+ # ─────────────────────────────────────────────────────────
105
+
106
+ # 各字段的候选列名(支持中英文、简写、常见变体)
107
+ COLUMN_ALIASES = {
108
+ "date": {"日期", "date", "Date", "DATE", "时间", "日", "活动日期", "会议日期", "事件日期",
109
+ "日程日期", "计划日期"},
110
+ "start": {"开始时间", "开始", "start", "Start", "START", "开始时刻", "起始时间", "from", "From",
111
+ "开始时间段", "时间开始", "起始"},
112
+ "end": {"结束时间", "结束", "end", "End", "END", "结束时刻", "to", "To",
113
+ "时间结束", "截止", "终止"},
114
+ "title": {"事项", "标题", "title", "Title", "TITLE", "内容", "会议", "活动", "事件",
115
+ "subject", "Subject", "name", "Name", "任务", "描述", "会议名称", "活动名称",
116
+ "事项名称", "议题", "主题", "项目", "meeting", "Meeting", "event", "Event",
117
+ "topic", "Topic", "item", "Item"},
118
+ "location": {"地点", "location", "Location", "LOCATION", "地址", "会议室", "venue", "Venue",
119
+ "place", "Place", "room", "Room", "场所", "场地"},
120
+ "participants": {"参与者", "人员", "participants", "Participants", "与会者", "成员", "attendees",
121
+ "Attendees", "负责人", "owner", "Owner", "参会人", "参会人员", "出席人员",
122
+ "相关人员", "联系人"},
123
+ "priority": {"优先级", "priority", "Priority", "PRIORITY", "重要程度", "紧急度", "级别",
124
+ "importance", "Importance", "urgency", "Urgency"},
125
+ "advance": {"提前(分钟)", "提前提醒", "提前", "advance", "Advance", "reminder", "Reminder",
126
+ "提前时间", "提醒时间", "提前分钟"},
127
+ "note": {"备注", "note", "Note", "NOTE", "notes", "Notes", "说明", "描述", "remark",
128
+ "Remark", "comments", "Comments", "附注", "补充", "其他"},
129
+ }
130
+
131
+
132
+ def parse_user_column_map(raw: str) -> dict:
133
+ """
134
+ 解析用户自定义列名映射字符串。
135
+ 输入格式:'会期=date,主旨=title,时间段开始=start'
136
+ 返回:{'会期': 'date', '主旨': 'title', '时间段开始': 'start'}
137
+ """
138
+ VALID_FIELDS = {"date", "start", "end", "title", "location",
139
+ "participants", "priority", "advance", "note"}
140
+ result = {}
141
+ for part in raw.split(","):
142
+ part = part.strip()
143
+ if "=" not in part:
144
+ continue
145
+ custom_name, _, field = part.partition("=")
146
+ custom_name = custom_name.strip()
147
+ field = field.strip()
148
+ if field in VALID_FIELDS and custom_name:
149
+ result[custom_name] = field
150
+ return result
151
+
152
+
153
+ def detect_column_mapping(header_cells: List[str],
154
+ user_map: Optional[dict] = None) -> dict:
155
+ """
156
+ 根据表头自动推断列索引映射。
157
+ 优先级:用户自定义映射 > 精确别名匹配 > 子串匹配。
158
+ 返回 {字段名: 列索引}
159
+ """
160
+ mapping: dict = {}
161
+ used_indices: set = set()
162
+
163
+ # 第零轮:用户自定义映射(最高优先级)
164
+ if user_map:
165
+ for i, cell in enumerate(header_cells):
166
+ cell_clean = cell.strip()
167
+ if cell_clean in user_map:
168
+ field = user_map[cell_clean]
169
+ if field not in mapping: # 同一字段只取第一个
170
+ mapping[field] = i
171
+ used_indices.add(i)
172
+
173
+ # 第一轮:精确匹配
174
+ for field, aliases in COLUMN_ALIASES.items():
175
+ for i, cell in enumerate(header_cells):
176
+ if i in used_indices:
177
+ continue
178
+ if cell.strip() in aliases:
179
+ mapping[field] = i
180
+ used_indices.add(i)
181
+ break
182
+
183
+ # 第二轮:子串匹配(仅对未匹配的字段和未使用的列)
184
+ for field, aliases in COLUMN_ALIASES.items():
185
+ if field in mapping:
186
+ continue
187
+ for i, cell in enumerate(header_cells):
188
+ if i in used_indices:
189
+ continue
190
+ cell_clean = cell.strip()
191
+ matched = False
192
+ for alias in aliases:
193
+ if not alias:
194
+ continue
195
+ # 列名包含别名(如"会议名称"包含"名称"→title)
196
+ # 但要避免短词误匹配(别名长度至少2)
197
+ if len(alias) >= 2 and (alias in cell_clean or cell_clean in alias):
198
+ mapping[field] = i
199
+ used_indices.add(i)
200
+ matched = True
201
+ break
202
+ if matched:
203
+ break
204
+
205
+ return mapping
206
+
207
+
208
+ def report_unresolved_columns(header_cells: List[str], mapping: dict) -> List[str]:
209
+ """返回未能映射到任何已知字段的列名列表"""
210
+ mapped_indices = set(mapping.values())
211
+ return [
212
+ header_cells[i].strip()
213
+ for i in range(len(header_cells))
214
+ if i not in mapped_indices and header_cells[i].strip()
215
+ ]
216
+
217
+
218
+ def check_required_fields(mapping: dict) -> List[str]:
219
+ """检查必要字段是否都已映射,返回缺失字段名"""
220
+ required = ["date", "title"]
221
+ return [f for f in required if f not in mapping]
222
+
223
+
224
+ def is_standard_header(cells: List[str]) -> bool:
225
+ """判断是否是标准格式表头(首列精确为日期/Date/date)"""
226
+ SCHEDULE_HEADERS = {"日期", "Date", "date"}
227
+ return bool(cells) and cells[0].strip() in SCHEDULE_HEADERS
228
+
229
+
230
+ def is_any_schedule_header(cells: List[str]) -> bool:
231
+ """宽松判断:表头中是否含有日期相关列"""
232
+ date_aliases = COLUMN_ALIASES["date"]
233
+ title_aliases = COLUMN_ALIASES["title"]
234
+ has_date = any(c.strip() in date_aliases for c in cells)
235
+ has_title = any(c.strip() in title_aliases for c in cells)
236
+ return has_date and has_title
237
+
238
+
239
+ def extract_event_by_mapping(cells: List[str], mapping: dict, default_advance: int,
240
+ section_name: str) -> Optional[dict]:
241
+ """根据列映射从数据行提取事项"""
242
+ def get(field):
243
+ idx = mapping.get(field)
244
+ if idx is not None and idx < len(cells):
245
+ return cells[idx].strip()
246
+ return ""
247
+
248
+ date_val = normalize_date(get("date"))
249
+ if not date_val:
250
+ return None
251
+
252
+ title = get("title")
253
+ if not title:
254
+ return None
255
+
256
+ start_str = get("start")
257
+ end_str = get("end")
258
+ start_min = normalize_time(start_str) if start_str else None
259
+ end_min = normalize_time(end_str) if end_str else None
260
+
261
+ priority_raw = get("priority")
262
+ priority = priority_raw if priority_raw in ("高", "中", "低") else "中"
263
+
264
+ advance_raw = get("advance")
265
+ try:
266
+ advance_min = int(advance_raw) if advance_raw else default_advance
267
+ except ValueError:
268
+ advance_min = default_advance
269
+
270
+ return {
271
+ "date": date_val.isoformat(),
272
+ "start_min": start_min,
273
+ "end_min": end_min,
274
+ "start_str": start_str,
275
+ "end_str": end_str,
276
+ "title": title,
277
+ "location": get("location"),
278
+ "participants": get("participants"),
279
+ "priority": priority,
280
+ "advance_min": advance_min,
281
+ "note": get("note"),
282
+ "section": section_name,
283
+ "is_task": any(k in title + get("note") for k in TASK_KEYWORDS),
284
+ }
285
+
286
+
287
+ def parse_table_section(lines: List[str], section_name: str,
288
+ default_advance: int,
289
+ user_map: Optional[dict] = None) -> dict:
290
+ """
291
+ 解析日程表格。返回 dict:
292
+ events: 解析出的事项列表
293
+ unresolved_columns: 无法映射的列名列表(宽松模式下)
294
+ missing_required: 缺失的必要字段(date/title)
295
+ mode: 'standard' | 'loose' | 'failed'
296
+ """
297
+ in_table = False
298
+ header_found = False
299
+ column_mapping: Optional[dict] = None
300
+ loose_mode = False
301
+ events = []
302
+ unresolved_columns: List[str] = []
303
+ missing_required: List[str] = []
304
+ parse_mode = "failed"
305
+
306
+ TASK_HEADERS = {"截止日期", "DeadLine", "deadline", "Deadline"}
307
+
308
+ for line in lines:
309
+ stripped = line.strip()
310
+ if not stripped.startswith("|"):
311
+ if in_table:
312
+ in_table = False
313
+ header_found = False
314
+ column_mapping = None
315
+ loose_mode = False
316
+ continue
317
+
318
+ cells = [c.strip() for c in stripped.strip("|").split("|")]
319
+ if len(cells) < 2:
320
+ continue
321
+
322
+ # 跳过任务表头,不被主日程解析器误抓
323
+ if cells[0].strip() in TASK_HEADERS:
324
+ continue
325
+
326
+ # 检测表头
327
+ if not in_table:
328
+ if is_standard_header(cells):
329
+ START_HEADERS = {"开始时间", "开始", "start", "Start", "START", "起始时间"}
330
+ second_col = cells[1].strip() if len(cells) > 1 else ""
331
+ if second_col in START_HEADERS:
332
+ # 标准模式
333
+ in_table = True
334
+ header_found = True
335
+ loose_mode = False
336
+ column_mapping = None
337
+ parse_mode = "standard"
338
+ continue
339
+ else:
340
+ # 首列是"日期"但列序不标准,降级为宽松模式
341
+ mapping = detect_column_mapping(cells, user_map)
342
+ missing = check_required_fields(mapping)
343
+ if not missing:
344
+ in_table = True
345
+ header_found = True
346
+ loose_mode = True
347
+ column_mapping = mapping
348
+ parse_mode = "loose"
349
+ unresolved_columns = report_unresolved_columns(cells, mapping)
350
+ missing_required = []
351
+ else:
352
+ missing_required = missing
353
+ parse_mode = "failed"
354
+ continue
355
+ elif is_any_schedule_header(cells) or user_map:
356
+ # 宽松模式:自动映射 + 用户自定义映射
357
+ mapping = detect_column_mapping(cells, user_map)
358
+ missing = check_required_fields(mapping)
359
+ if not missing:
360
+ in_table = True
361
+ header_found = True
362
+ loose_mode = True
363
+ column_mapping = mapping
364
+ parse_mode = "loose"
365
+ unresolved_columns = report_unresolved_columns(cells, mapping)
366
+ missing_required = []
367
+ else:
368
+ missing_required = missing
369
+ parse_mode = "failed"
370
+ continue
371
+ continue
372
+ continue
373
+
374
+ # 跳过分隔行
375
+ if header_found and all(re.match(r"^[-:]+$", c) for c in cells if c):
376
+ continue
377
+
378
+ if not in_table:
379
+ continue
380
+
381
+ if loose_mode and column_mapping:
382
+ # 宽松模式:用列映射提取
383
+ ev = extract_event_by_mapping(cells, column_mapping, default_advance, section_name)
384
+ if ev:
385
+ events.append(ev)
386
+ else:
387
+ # 标准模式:按固定列序提取
388
+ if len(cells) < 4:
389
+ continue
390
+ date_val = normalize_date(cells[0]) if cells[0] else None
391
+ if not date_val:
392
+ continue
393
+ start_min = normalize_time(cells[1]) if len(cells) > 1 else None
394
+ end_min = normalize_time(cells[2]) if len(cells) > 2 else None
395
+ title = cells[3].strip() if len(cells) > 3 else ""
396
+ location = cells[4].strip() if len(cells) > 4 else ""
397
+ participants = cells[5].strip() if len(cells) > 5 else ""
398
+ priority = cells[6].strip() if len(cells) > 6 else "中"
399
+ advance_raw = cells[7].strip() if len(cells) > 7 else ""
400
+ note = cells[8].strip() if len(cells) > 8 else ""
401
+ if not title:
402
+ continue
403
+ try:
404
+ advance_min = int(advance_raw) if advance_raw else default_advance
405
+ except ValueError:
406
+ advance_min = default_advance
407
+ events.append({
408
+ "date": date_val.isoformat(),
409
+ "start_min": start_min,
410
+ "end_min": end_min,
411
+ "start_str": cells[1].strip() if len(cells) > 1 else "",
412
+ "end_str": cells[2].strip() if len(cells) > 2 else "",
413
+ "title": title,
414
+ "location": location,
415
+ "participants": participants,
416
+ "priority": priority if priority in ("高", "中", "低") else "中",
417
+ "advance_min": advance_min,
418
+ "note": note,
419
+ "section": section_name,
420
+ "is_task": any(k in title + note for k in TASK_KEYWORDS),
421
+ })
422
+
423
+ return {
424
+ "events": events,
425
+ "mode": parse_mode,
426
+ "unresolved_columns": unresolved_columns,
427
+ "missing_required": missing_required,
428
+ }
429
+
430
+
431
+ def parse_task_section(lines: List[str], default_advance: int) -> list:
432
+ """解析待提醒任务表(截止日期形式)"""
433
+ in_table = False
434
+ header_found = False
435
+ tasks = []
436
+
437
+ TASK_HEADERS = {"截止日期", "DeadLine", "deadline", "Deadline"}
438
+
439
+ for line in lines:
440
+ stripped = line.strip()
441
+ if not stripped.startswith("|"):
442
+ if in_table:
443
+ in_table = False
444
+ header_found = False
445
+ continue
446
+
447
+ cells = [c.strip() for c in stripped.strip("|").split("|")]
448
+ if not cells:
449
+ continue
450
+
451
+ if cells[0] in TASK_HEADERS:
452
+ in_table = True
453
+ header_found = True
454
+ continue
455
+
456
+ if header_found and all(re.match(r"^[-:]+$", c) for c in cells if c):
457
+ continue
458
+
459
+ if not in_table:
460
+ continue
461
+
462
+ if len(cells) < 2:
463
+ continue
464
+
465
+ date_val = normalize_date(cells[0]) if cells[0] else None
466
+ if not date_val:
467
+ continue
468
+
469
+ title = cells[1].strip() if len(cells) > 1 else ""
470
+ priority = cells[3].strip() if len(cells) > 3 else "中"
471
+ advance_raw = cells[4].strip() if len(cells) > 4 else ""
472
+ note = cells[5].strip() if len(cells) > 5 else ""
473
+
474
+ # 解析"提前1天"、"提前2天"形式
475
+ advance_min = default_advance
476
+ m = re.search(r"提前\s*(\d+)\s*天", advance_raw)
477
+ if m:
478
+ advance_min = int(m.group(1)) * 24 * 60
479
+ else:
480
+ m = re.search(r"(\d+)", advance_raw)
481
+ if m:
482
+ advance_min = int(m.group(1))
483
+
484
+ tasks.append({
485
+ "date": date_val.isoformat(),
486
+ "start_min": 9 * 60, # 默认早上9点触发
487
+ "end_min": 9 * 60 + 30,
488
+ "start_str": "09:00",
489
+ "end_str": "09:30",
490
+ "title": f"📋 {title}",
491
+ "location": "",
492
+ "participants": cells[2].strip() if len(cells) > 2 else "",
493
+ "priority": priority if priority in ("高", "中", "低") else "中",
494
+ "advance_min": advance_min,
495
+ "note": note,
496
+ "section": "tasks",
497
+ "is_task": True,
498
+ })
499
+
500
+ return tasks
501
+
502
+
503
+ def detect_conflicts(events: list[dict]) -> list[dict]:
504
+ """检测同日时间冲突"""
505
+ by_date: dict[str, list[dict]] = {}
506
+ for e in events:
507
+ if e["start_min"] is None or e["end_min"] is None:
508
+ continue
509
+ by_date.setdefault(e["date"], []).append(e)
510
+
511
+ conflicts = []
512
+ for day, day_events in by_date.items():
513
+ sorted_events = sorted(day_events, key=lambda x: x["start_min"])
514
+ for i in range(len(sorted_events)):
515
+ for j in range(i + 1, len(sorted_events)):
516
+ a, b = sorted_events[i], sorted_events[j]
517
+ if a["end_min"] <= b["start_min"]:
518
+ # 不重叠,但检查是否过于紧凑(< 5分钟间隔)
519
+ gap = b["start_min"] - a["end_min"]
520
+ if 0 < gap < 5:
521
+ conflicts.append({
522
+ "type": "tight",
523
+ "date": day,
524
+ "event_a": a["title"],
525
+ "event_b": b["title"],
526
+ "a_time": f"{a['start_str']}–{a['end_str']}",
527
+ "b_time": f"{b['start_str']}–{b['end_str']}",
528
+ "gap_min": gap,
529
+ "message": (
530
+ f"⚠️ 紧凑安排:【{a['title']}】{a['start_str']}–{a['end_str']} "
531
+ f"与【{b['title']}】{b['start_str']} 之间仅 {gap} 分钟,几乎没有缓冲时间。"
532
+ ),
533
+ })
534
+ else:
535
+ # 重叠
536
+ overlap = a["end_min"] - b["start_min"]
537
+ conflicts.append({
538
+ "type": "overlap",
539
+ "date": day,
540
+ "event_a": a["title"],
541
+ "event_b": b["title"],
542
+ "a_time": f"{a['start_str']}–{a['end_str']}",
543
+ "b_time": f"{b['start_str']}–{b['end_str']}",
544
+ "overlap_min": overlap,
545
+ "message": (
546
+ f"⚠️ 时间冲突:【{a['title']}】{a['start_str']}–{a['end_str']} "
547
+ f"与【{b['title']}】{b['start_str']}–{b['end_str']} "
548
+ f"重叠 {overlap} 分钟。"
549
+ ),
550
+ })
551
+ return conflicts
552
+
553
+
554
+ def filter_upcoming(events: list, mode: str, now: datetime) -> list:
555
+ """按 mode 筛选需要提醒的事项"""
556
+ today = now.date()
557
+ tomorrow = today + timedelta(days=1)
558
+ week_end = today + timedelta(days=7)
559
+ now_min = now.hour * 60 + now.minute
560
+
561
+ result = []
562
+ for e in events:
563
+ try:
564
+ ev_date = date.fromisoformat(e["date"])
565
+ except ValueError:
566
+ continue
567
+
568
+ # 已过期跳过
569
+ if ev_date < today:
570
+ continue
571
+ if ev_date == today and e["end_min"] is not None and e["end_min"] < now_min:
572
+ continue
573
+
574
+ # 低优先级且非任务,默认跳过
575
+ if e["priority"] == "低" and not e["is_task"]:
576
+ continue
577
+
578
+ include = False
579
+ if mode == "today" and ev_date == today:
580
+ include = True
581
+ elif mode == "tomorrow" and ev_date == tomorrow:
582
+ include = True
583
+ elif mode == "week" and today <= ev_date <= week_end:
584
+ include = True
585
+ elif mode == "all":
586
+ include = True
587
+
588
+ # today 模式额外:明日高优先级也纳入
589
+ if mode == "today" and ev_date == tomorrow and e["priority"] == "高":
590
+ include = True
591
+
592
+ if include:
593
+ result.append(e)
594
+
595
+ result.sort(key=lambda x: (x["date"], x["start_min"] or 0))
596
+ return result
597
+
598
+
599
+ def build_reminder_time(ev: dict, now: datetime) -> Optional[str]:
600
+ """计算提醒时刻的 cron 表达式(单次)"""
601
+ if ev["start_min"] is None:
602
+ return None
603
+ try:
604
+ ev_date = date.fromisoformat(ev["date"])
605
+ except ValueError:
606
+ return None
607
+
608
+ remind_total_min = ev["start_min"] - ev["advance_min"]
609
+ if remind_total_min < 0:
610
+ remind_total_min = 0
611
+
612
+ r_hour = remind_total_min // 60
613
+ r_min = remind_total_min % 60
614
+
615
+ # 单次 cron:分 时 日 月 *
616
+ return f"{r_min} {r_hour} {ev_date.day} {ev_date.month} *"
617
+
618
+
619
+ def build_reminder_time_str(ev: dict) -> str:
620
+ """人类可读的提醒时刻"""
621
+ if ev["start_min"] is None:
622
+ return ""
623
+ remind_total_min = max(0, ev["start_min"] - ev["advance_min"])
624
+ r_hour = remind_total_min // 60
625
+ r_min = remind_total_min % 60
626
+ return f"{r_hour:02d}:{r_min:02d}"
627
+
628
+
629
+ def main():
630
+ args = parse_args()
631
+ content = read_content(args)
632
+ cfg = parse_global_config(content)
633
+ now = datetime.now()
634
+ if args.now:
635
+ try:
636
+ now = datetime.strptime(args.now, "%Y-%m-%d %H:%M")
637
+ except ValueError:
638
+ pass
639
+
640
+ lines = content.splitlines()
641
+
642
+ # 解析用户自定义列名映射
643
+ user_map = parse_user_column_map(args.column_map) if args.column_map else None
644
+
645
+ # 解析主日程表
646
+ schedule_result = parse_table_section(
647
+ lines, "schedule", cfg["default_advance_minutes"], user_map
648
+ )
649
+ events = schedule_result["events"]
650
+ # 解析任务表
651
+ tasks = parse_task_section(lines, cfg["default_advance_minutes"])
652
+ all_events = events + tasks
653
+
654
+ # 冲突检测(仅对 schedule 类事项)
655
+ conflicts = detect_conflicts(events)
656
+
657
+ # 筛选近期/今日事项
658
+ upcoming = filter_upcoming(all_events, args.mode, now)
659
+
660
+ # 为每个事项计算提醒时刻
661
+ for ev in upcoming:
662
+ ev["remind_cron"] = build_reminder_time(ev, now)
663
+ ev["remind_time_str"] = build_reminder_time_str(ev)
664
+
665
+ output = {
666
+ "meta": {
667
+ "mode": args.mode,
668
+ "now": now.strftime("%Y-%m-%d %H:%M"),
669
+ "config": cfg,
670
+ "parse_mode": schedule_result["mode"],
671
+ },
672
+ "summary": {
673
+ "total_events": len(all_events),
674
+ "upcoming_count": len(upcoming),
675
+ "conflict_count": len(conflicts),
676
+ },
677
+ "upcoming": upcoming,
678
+ "conflicts": conflicts,
679
+ }
680
+
681
+ # 列名问题提示(宽松模式下,让 agent 告知用户)
682
+ if schedule_result["unresolved_columns"]:
683
+ output["warnings"] = {
684
+ "unresolved_columns": schedule_result["unresolved_columns"],
685
+ "message": (
686
+ f"⚠️ 以下列名无法自动识别,其内容已忽略:"
687
+ f"{', '.join(schedule_result['unresolved_columns'])}。"
688
+ f"如需记录这些列,请告诉我它们对应的字段(如:'会期' = 日期,'主旨' = 事项)。"
689
+ ),
690
+ }
691
+ if schedule_result["missing_required"]:
692
+ output["errors"] = {
693
+ "missing_required": schedule_result["missing_required"],
694
+ "message": (
695
+ f"❌ 表格缺少必要字段:{', '.join(schedule_result['missing_required'])},"
696
+ f"无法解析。请确认表格中包含日期列和事项/标题列。"
697
+ ),
698
+ }
699
+
700
+ print(json.dumps(output, ensure_ascii=False, indent=2))
701
+
702
+
703
+ if __name__ == "__main__":
704
+ main()