sophhub 0.1.0
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.
- package/bin/sophhub.js +21 -0
- package/package.json +32 -0
- package/skills/VERSIONS.md +27 -0
- package/skills/builtin/clawhub/SKILL.md +77 -0
- package/skills/builtin/flight-booking/SKILL.md +288 -0
- package/skills/builtin/flight-booking/scripts/flight_booking.py +1232 -0
- package/skills/builtin/inventory-management/SKILL.md +241 -0
- package/skills/builtin/inventory-management/scripts/inventory.py +1844 -0
- package/skills/builtin/schedule-reminder/SKILL.md +619 -0
- package/skills/builtin/schedule-reminder/schedule_template.md +68 -0
- package/skills/builtin/schedule-reminder/scripts/append_event.py +204 -0
- package/skills/builtin/schedule-reminder/scripts/create_reminders.sh +163 -0
- package/skills/builtin/schedule-reminder/scripts/daily_activate.sh +175 -0
- package/skills/builtin/schedule-reminder/scripts/parse_schedule.py +704 -0
- package/skills/builtin/schedule-reminder/scripts/setup.sh +242 -0
- package/skills/builtin/schedule-reminder//347/224/250/346/210/267/346/214/207/345/215/227.md +311 -0
- package/skills/builtin/skill-creator/SKILL.md +370 -0
- package/skills/builtin/skill-creator/license.txt +202 -0
- package/skills/builtin/skill-creator/scripts/init_skill.py +378 -0
- package/skills/builtin/skill-creator/scripts/package_skill.py +111 -0
- package/skills/builtin/skill-creator/scripts/quick_validate.py +101 -0
- package/skills/builtin/sophnet-customer-management/SKILL.md +271 -0
- package/skills/builtin/sophnet-customer-management/pyproject.toml +15 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/__init__.py +2 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/__main__.py +5 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/cli.py +67 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/__init__.py +2 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/customer.py +60 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/export_file.py +18 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/import_file.py +15 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/reminder.py +26 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/schema.py +28 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/config.py +54 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/__init__.py +2 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/exporter.py +85 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/models.py +84 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/normalizer.py +144 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/parser.py +241 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/query.py +109 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/reminder.py +121 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/repository.py +397 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/schema.py +106 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/service.py +565 -0
- package/skills/builtin/sophnet-customer-management/uv.lock +48 -0
- package/skills/builtin/sophnet-customized-marketing/SKILL.md +144 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/campaign-planning.md +187 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/content-generation.md +124 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/marketing-calendar.md +59 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/multi-channel-bundle.md +94 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/poster-generation.md +182 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/style-profile-workflow.md +103 -0
- package/skills/builtin/sophnet-customized-marketing/pyproject.toml +9 -0
- package/skills/builtin/sophnet-customized-marketing/references/campaign-mechanics.md +168 -0
- package/skills/builtin/sophnet-customized-marketing/references/content-safety.md +26 -0
- package/skills/builtin/sophnet-customized-marketing/references/marketing-date-checklist.md +99 -0
- package/skills/builtin/sophnet-customized-marketing/references/platform-writing-guidelines.md +88 -0
- package/skills/builtin/sophnet-customized-marketing/references/quality-checklist.md +44 -0
- package/skills/builtin/sophnet-customized-marketing/scripts/generate_poster.py +585 -0
- package/skills/builtin/sophnet-customized-marketing/scripts/style_profile.py +215 -0
- package/skills/builtin/sophnet-face-search/SKILL.md +115 -0
- package/skills/builtin/sophnet-face-search/pyproject.toml +11 -0
- package/skills/builtin/sophnet-face-search/scripts/face_search.py +336 -0
- package/skills/builtin/sophnet-face-search/uv.lock +508 -0
- package/skills/builtin/sophnet-image-edit/SKILL.md +140 -0
- package/skills/builtin/sophnet-image-edit/pyproject.toml +9 -0
- package/skills/builtin/sophnet-image-edit/scripts/edit_and_preview.sh +68 -0
- package/skills/builtin/sophnet-image-edit/scripts/edit_image.py +279 -0
- package/skills/builtin/sophnet-image-edit/uv.lock +234 -0
- package/skills/builtin/sophnet-image-generate/SKILL.md +62 -0
- package/skills/builtin/sophnet-image-generate/pyproject.toml +9 -0
- package/skills/builtin/sophnet-image-generate/scripts/generate_image.py +156 -0
- package/skills/builtin/sophnet-image-generate/uv.lock +234 -0
- package/skills/builtin/sophnet-image-ocr/SKILL.md +167 -0
- package/skills/builtin/sophnet-image-ocr/pyproject.toml +13 -0
- package/skills/builtin/sophnet-image-ocr/scripts/ocr.py +226 -0
- package/skills/builtin/sophnet-image-ocr/uv.lock +234 -0
- package/skills/builtin/sophnet-infinite-talk/SKILL.md +140 -0
- package/skills/builtin/sophnet-infinite-talk/pyproject.toml +9 -0
- package/skills/builtin/sophnet-infinite-talk/scripts/gen.py +172 -0
- package/skills/builtin/sophnet-oss/SKILL.md +109 -0
- package/skills/builtin/sophnet-oss/pyproject.toml +8 -0
- package/skills/builtin/sophnet-oss/scripts/upload_file.py +43 -0
- package/skills/builtin/sophnet-qa-install/SKILL.md +210 -0
- package/skills/builtin/sophnet-qa-install/pyproject.toml +6 -0
- package/skills/builtin/sophnet-qa-install/scripts/backup_md.py +35 -0
- package/skills/builtin/sophnet-qa-install/scripts/check_installed.py +143 -0
- package/skills/builtin/sophnet-qa-install/scripts/update_config.py +142 -0
- package/skills/builtin/sophnet-qa-install/scripts/update_md.py +73 -0
- package/skills/builtin/sophnet-training-install/SKILL.md +211 -0
- package/skills/builtin/sophnet-training-install/pyproject.toml +6 -0
- package/skills/builtin/sophnet-training-install/scripts/backup_md.py +35 -0
- package/skills/builtin/sophnet-training-install/scripts/check_installed.py +144 -0
- package/skills/builtin/sophnet-training-install/scripts/update_config.py +142 -0
- package/skills/builtin/sophnet-training-install/scripts/update_md.py +73 -0
- package/skills/builtin/sophnet-tts/SKILL.md +79 -0
- package/skills/builtin/sophnet-tts/pyproject.toml +9 -0
- package/skills/builtin/sophnet-tts/scripts/gen_tts.py +130 -0
- package/skills/builtin/sophnet-video-generate/SKILL.md +116 -0
- package/skills/builtin/sophnet-video-generate/scripts/gen_video.py +304 -0
- package/skills/builtin/video-understand/SKILL.md +79 -0
- package/skills/builtin/video-understand/scripts/video_understand.py +204 -0
- package/skills/builtin/weather/SKILL.md +112 -0
- package/skills/builtin/web-scraper/SKILL.md +101 -0
- package/skills/builtin/web-scraper/scripts/scrape.py +270 -0
- package/skills/builtin/website-builder/SKILL.md +266 -0
- package/skills/builtin/website-builder/scripts/deploy_site.sh +46 -0
- package/skills/store/didi-ride/SKILL.md +309 -0
- package/skills/store/didi-ride/_meta.json +6 -0
- package/skills/store/didi-ride/assets/PREFERENCE.md +58 -0
- package/skills/store/didi-ride/package.json +15 -0
- package/skills/store/didi-ride/references/api_references.md +171 -0
- package/skills/store/didi-ride/references/error_handling.md +68 -0
- package/skills/store/didi-ride/references/setup.md +73 -0
- package/skills/store/didi-ride/references/workflow.md +150 -0
- package/skills/store/flyai/SKILL.md +119 -0
- package/skills/store/flyai/references/fliggy-fast-search.md +53 -0
- package/skills/store/flyai/references/search-flight.md +89 -0
- package/skills/store/flyai/references/search-hotels.md +57 -0
- package/skills/store/flyai/references/search-poi.md +49 -0
- package/src/commands/download.js +103 -0
- package/src/commands/list.js +67 -0
- package/src/utils/config.js +24 -0
- package/src/utils/gitlab.js +67 -0
- package/src/utils/paths.js +19 -0
- package/src/utils/versions.js +38 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
append_event.py - 将一条新事项追加到 Markdown 日程文件
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
python3 append_event.py \
|
|
7
|
+
--file /home/node/.openclaw/workspace/日程.md \
|
|
8
|
+
--date 2026-03-03 \
|
|
9
|
+
--start 15:00 \
|
|
10
|
+
--end 16:00 \
|
|
11
|
+
--title "客户会议" \
|
|
12
|
+
[--location "线上"] \
|
|
13
|
+
[--participants "张三"] \
|
|
14
|
+
[--priority 中] \
|
|
15
|
+
[--advance 15] \
|
|
16
|
+
[--note "带方案"] \
|
|
17
|
+
[--type task] # task = 写入待提醒任务表,默认写入主日程表
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import argparse
|
|
25
|
+
from datetime import datetime, date
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_args():
|
|
30
|
+
p = argparse.ArgumentParser(description="追加日程事项到 Markdown 文件")
|
|
31
|
+
p.add_argument("--file", required=True, help="日程 Markdown 文件路径")
|
|
32
|
+
p.add_argument("--date", required=True, help="日期 YYYY-MM-DD")
|
|
33
|
+
p.add_argument("--start", default="", help="开始时间 HH:MM")
|
|
34
|
+
p.add_argument("--end", default="", help="结束时间 HH:MM")
|
|
35
|
+
p.add_argument("--title", required=True, help="事项名称")
|
|
36
|
+
p.add_argument("--location", default="", help="地点")
|
|
37
|
+
p.add_argument("--participants", default="", help="参与者")
|
|
38
|
+
p.add_argument("--priority", default="中", choices=["高", "中", "低"], help="优先级")
|
|
39
|
+
p.add_argument("--advance", default="15", help="提前提醒分钟数")
|
|
40
|
+
p.add_argument("--note", default="", help="备注")
|
|
41
|
+
p.add_argument("--type", default="schedule", choices=["schedule", "task"],
|
|
42
|
+
help="写入主日程表(schedule)还是待提醒任务表(task)")
|
|
43
|
+
return p.parse_args()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def expand_path(path: str) -> str:
|
|
47
|
+
return os.path.expanduser(path)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def read_file(path: str) -> str:
|
|
51
|
+
if not os.path.exists(path):
|
|
52
|
+
return ""
|
|
53
|
+
with open(path, encoding="utf-8") as f:
|
|
54
|
+
return f.read()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def write_file(path: str, content: str):
|
|
58
|
+
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
|
59
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
60
|
+
f.write(content)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def make_schedule_row(args) -> str:
|
|
64
|
+
"""生成主日程表的一行"""
|
|
65
|
+
advance = args.advance if args.advance else "15"
|
|
66
|
+
return (
|
|
67
|
+
f"| {args.date} | {args.start} | {args.end} | {args.title} "
|
|
68
|
+
f"| {args.location} | {args.participants} | {args.priority} "
|
|
69
|
+
f"| {advance} | {args.note} |"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def make_task_row(args) -> str:
|
|
74
|
+
"""生成待提醒任务表的一行"""
|
|
75
|
+
# 任务表:截止日期 | 事项 | 负责人 | 优先级 | 提前提醒 | 备注
|
|
76
|
+
advance_days = ""
|
|
77
|
+
try:
|
|
78
|
+
adv_min = int(args.advance)
|
|
79
|
+
if adv_min >= 1440:
|
|
80
|
+
days = adv_min // 1440
|
|
81
|
+
advance_days = f"提前{days}天"
|
|
82
|
+
else:
|
|
83
|
+
advance_days = str(adv_min)
|
|
84
|
+
except ValueError:
|
|
85
|
+
advance_days = args.advance
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
f"| {args.date} | {args.title} | {args.participants} "
|
|
89
|
+
f"| {args.priority} | {advance_days} | {args.note} |"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
SCHEDULE_HEADER = "| 日期 | 开始时间 | 结束时间 | 事项"
|
|
94
|
+
SCHEDULE_HEADER_ALT = "| 日期 | 开始时间"
|
|
95
|
+
TASK_HEADER = "| 截止日期"
|
|
96
|
+
|
|
97
|
+
SCHEDULE_TABLE_BLOCK = """\
|
|
98
|
+
## 日程
|
|
99
|
+
|
|
100
|
+
| 日期 | 开始时间 | 结束时间 | 事项 | 地点 | 参与者 | 优先级 | 提前(分钟) | 备注 |
|
|
101
|
+
|------------|----------|----------|--------------|------|--------|--------|------------|------|
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
TASK_TABLE_BLOCK = """\
|
|
105
|
+
## 待提醒任务
|
|
106
|
+
|
|
107
|
+
| 截止日期 | 事项 | 负责人 | 优先级 | 提前提醒 | 备注 |
|
|
108
|
+
|------------|------|--------|--------|----------|------|
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
DEFAULT_FILE_TEMPLATE = """\
|
|
112
|
+
<!-- skill: schedule-reminder -->
|
|
113
|
+
# 我的日程
|
|
114
|
+
|
|
115
|
+
> 提醒渠道:last
|
|
116
|
+
> 默认提前提醒:15
|
|
117
|
+
|
|
118
|
+
{schedule_block}
|
|
119
|
+
{task_block}"""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def find_table_end(lines: list, start_idx: int) -> int:
|
|
123
|
+
"""从 start_idx 往下找到表格结束位置(返回最后一行表格的 index + 1)"""
|
|
124
|
+
i = start_idx
|
|
125
|
+
last_table_line = start_idx
|
|
126
|
+
while i < len(lines):
|
|
127
|
+
stripped = lines[i].strip()
|
|
128
|
+
if stripped.startswith("|"):
|
|
129
|
+
last_table_line = i
|
|
130
|
+
elif stripped == "" and i > start_idx:
|
|
131
|
+
# 空行可能是表格后的分隔,继续看下一行
|
|
132
|
+
if i + 1 < len(lines) and not lines[i + 1].strip().startswith("|"):
|
|
133
|
+
break
|
|
134
|
+
elif not stripped.startswith("|") and stripped != "":
|
|
135
|
+
break
|
|
136
|
+
i += 1
|
|
137
|
+
return last_table_line + 1
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def insert_row_into_table(content: str, new_row: str, header_pattern: str) -> Optional[str]:
|
|
141
|
+
"""在找到的表格末尾插入新行,返回修改后的内容;找不到返回 None"""
|
|
142
|
+
lines = content.splitlines(keepends=True)
|
|
143
|
+
header_idx = None
|
|
144
|
+
for i, line in enumerate(lines):
|
|
145
|
+
if line.strip().startswith(header_pattern.strip()):
|
|
146
|
+
header_idx = i
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
if header_idx is None:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
end_idx = find_table_end(lines, header_idx)
|
|
153
|
+
lines.insert(end_idx, new_row + "\n")
|
|
154
|
+
return "".join(lines)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def append_to_file(file_path: str, args) -> str:
|
|
158
|
+
"""主逻辑:读取文件 → 找表格 → 插入新行 → 写回"""
|
|
159
|
+
content = read_file(file_path)
|
|
160
|
+
|
|
161
|
+
if args.type == "task":
|
|
162
|
+
new_row = make_task_row(args)
|
|
163
|
+
updated = insert_row_into_table(content, new_row, "| 截止日期")
|
|
164
|
+
if updated is None:
|
|
165
|
+
# 没有任务表,追加一个
|
|
166
|
+
if content and not content.endswith("\n"):
|
|
167
|
+
content += "\n"
|
|
168
|
+
content += "\n" + TASK_TABLE_BLOCK + new_row + "\n"
|
|
169
|
+
updated = content
|
|
170
|
+
else:
|
|
171
|
+
new_row = make_schedule_row(args)
|
|
172
|
+
updated = insert_row_into_table(content, new_row, "| 日期")
|
|
173
|
+
if updated is None:
|
|
174
|
+
# 没有日程表,追加一个
|
|
175
|
+
if not content:
|
|
176
|
+
# 文件为空,创建完整模板
|
|
177
|
+
updated = DEFAULT_FILE_TEMPLATE.format(
|
|
178
|
+
schedule_block=SCHEDULE_TABLE_BLOCK + new_row,
|
|
179
|
+
task_block=TASK_TABLE_BLOCK,
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
if not content.endswith("\n"):
|
|
183
|
+
content += "\n"
|
|
184
|
+
content += "\n" + SCHEDULE_TABLE_BLOCK + new_row + "\n"
|
|
185
|
+
updated = content
|
|
186
|
+
|
|
187
|
+
return updated
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main():
|
|
191
|
+
args = parse_args()
|
|
192
|
+
file_path = expand_path(args.file)
|
|
193
|
+
|
|
194
|
+
updated_content = append_to_file(file_path, args)
|
|
195
|
+
write_file(file_path, updated_content)
|
|
196
|
+
|
|
197
|
+
# 输出确认信息
|
|
198
|
+
event_type = "任务" if args.type == "task" else "日程"
|
|
199
|
+
time_str = f"{args.start}" + (f" – {args.end}" if args.end else "")
|
|
200
|
+
print(f"✅ 已写入{event_type}:{args.date} {time_str} {args.title} → {file_path}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
main()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# create_reminders.sh - 从解析结果 JSON 批量创建 cron 提醒任务
|
|
3
|
+
#
|
|
4
|
+
# 用法:
|
|
5
|
+
# bash create_reminders.sh \
|
|
6
|
+
# --events-json /tmp/schedule_upcoming.json \
|
|
7
|
+
# --channel dingtalk \
|
|
8
|
+
# --to "user_id_or_sender"
|
|
9
|
+
#
|
|
10
|
+
# 环境变量(可覆盖):
|
|
11
|
+
# SCHEDULE_CHANNEL - 推送渠道(dingtalk / feishu / wecom / telegram)
|
|
12
|
+
# SCHEDULE_TO - 接收者 ID
|
|
13
|
+
# SCHEDULE_TZ - 时区(默认 Asia/Shanghai)
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# ── 参数解析 ──────────────────────────────────────────────
|
|
18
|
+
EVENTS_JSON=""
|
|
19
|
+
CHANNEL="${SCHEDULE_CHANNEL:-}"
|
|
20
|
+
TO="${SCHEDULE_TO:-}"
|
|
21
|
+
TZ="${SCHEDULE_TZ:-Asia/Shanghai}"
|
|
22
|
+
DRY_RUN=0
|
|
23
|
+
|
|
24
|
+
while [[ $# -gt 0 ]]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--events-json) EVENTS_JSON="$2"; shift 2 ;;
|
|
27
|
+
--channel) CHANNEL="$2"; shift 2 ;;
|
|
28
|
+
--to) TO="$2"; shift 2 ;;
|
|
29
|
+
--tz) TZ="$2"; shift 2 ;;
|
|
30
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
31
|
+
*) echo "未知参数: $1" >&2; exit 1 ;;
|
|
32
|
+
esac
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
# ── 前置检查 ──────────────────────────────────────────────
|
|
36
|
+
if [[ -z "$EVENTS_JSON" ]]; then
|
|
37
|
+
echo "错误:请指定 --events-json <file>" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [[ ! -f "$EVENTS_JSON" ]]; then
|
|
42
|
+
echo "错误:文件不存在: $EVENTS_JSON" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if ! command -v jq &>/dev/null; then
|
|
47
|
+
echo "错误:需要安装 jq" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if ! command -v openclaw &>/dev/null; then
|
|
52
|
+
echo "错误:openclaw 命令未找到" >&2
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# ── 读取事项列表 ──────────────────────────────────────────
|
|
57
|
+
COUNT=$(jq '.upcoming | length' "$EVENTS_JSON")
|
|
58
|
+
|
|
59
|
+
if [[ "$COUNT" -eq 0 ]]; then
|
|
60
|
+
echo "✅ 没有需要创建提醒的事项。"
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
echo "📅 共 $COUNT 个事项需要创建提醒(渠道:$CHANNEL)"
|
|
65
|
+
echo ""
|
|
66
|
+
|
|
67
|
+
# ── 构建提醒消息 ──────────────────────────────────────────
|
|
68
|
+
build_message() {
|
|
69
|
+
local title="$1"
|
|
70
|
+
local start_str="$2"
|
|
71
|
+
local end_str="$3"
|
|
72
|
+
local location="$4"
|
|
73
|
+
local participants="$5"
|
|
74
|
+
local note="$6"
|
|
75
|
+
local advance="$7"
|
|
76
|
+
|
|
77
|
+
local msg="⏰ 日程提醒\n\n📌 【${title}】即将在 ${advance} 分钟后开始\n🕐 ${start_str}"
|
|
78
|
+
[[ -n "$end_str" ]] && msg="${msg} – ${end_str}"
|
|
79
|
+
[[ -n "$location" ]] && msg="${msg}\n📍 ${location}"
|
|
80
|
+
[[ -n "$participants" ]] && msg="${msg}\n👥 ${participants}"
|
|
81
|
+
[[ -n "$note" ]] && msg="${msg}\n📝 ${note}"
|
|
82
|
+
echo "$msg"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# ── 遍历事项并创建 cron 任务 ─────────────────────────────
|
|
86
|
+
CREATED=0
|
|
87
|
+
SKIPPED=0
|
|
88
|
+
FAILED=0
|
|
89
|
+
|
|
90
|
+
for i in $(seq 0 $((COUNT - 1))); do
|
|
91
|
+
ev=$(jq ".upcoming[$i]" "$EVENTS_JSON")
|
|
92
|
+
|
|
93
|
+
title=$(echo "$ev" | jq -r '.title')
|
|
94
|
+
date_str=$(echo "$ev" | jq -r '.date')
|
|
95
|
+
start_str=$(echo "$ev" | jq -r '.start_str')
|
|
96
|
+
end_str=$(echo "$ev" | jq -r '.end_str')
|
|
97
|
+
location=$(echo "$ev" | jq -r '.location // ""')
|
|
98
|
+
participants=$(echo "$ev" | jq -r '.participants // ""')
|
|
99
|
+
note=$(echo "$ev" | jq -r '.note // ""')
|
|
100
|
+
advance=$(echo "$ev" | jq -r '.advance_min')
|
|
101
|
+
remind_cron=$(echo "$ev" | jq -r '.remind_cron // ""')
|
|
102
|
+
remind_time=$(echo "$ev" | jq -r '.remind_time_str // ""')
|
|
103
|
+
priority=$(echo "$ev" | jq -r '.priority')
|
|
104
|
+
|
|
105
|
+
# 没有 cron 表达式则跳过
|
|
106
|
+
if [[ -z "$remind_cron" || "$remind_cron" == "null" ]]; then
|
|
107
|
+
echo " ⏭️ 跳过(无时间信息):${title}"
|
|
108
|
+
((SKIPPED++)) || true
|
|
109
|
+
continue
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# 构建任务名(避免特殊字符)
|
|
113
|
+
safe_title=$(echo "$title" | tr ' 【】《》""''()()' '_' | tr -cd '[:alnum:]_-' | cut -c1-40)
|
|
114
|
+
task_name="schedule-${date_str}-${safe_title}"
|
|
115
|
+
|
|
116
|
+
# 构建提醒消息
|
|
117
|
+
msg=$(build_message "$title" "$start_str" "$end_str" "$location" "$participants" "$note" "$advance")
|
|
118
|
+
|
|
119
|
+
echo " ➕ [${priority}] ${title}"
|
|
120
|
+
echo " 提醒时刻:${remind_time}(提前 ${advance} 分钟)"
|
|
121
|
+
echo " Cron:${remind_cron}"
|
|
122
|
+
|
|
123
|
+
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
124
|
+
echo " [DRY-RUN] 跳过实际创建"
|
|
125
|
+
((CREATED++)) || true
|
|
126
|
+
continue
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# 构建 openclaw cron 命令
|
|
130
|
+
# 使用 --system-event + --session main,触发 main session 直接显示提醒消息,
|
|
131
|
+
# 不走 announce 推送流程,兼容 webchat/控制台等所有渠道,无需渠道配置。
|
|
132
|
+
CRON_CMD=(
|
|
133
|
+
openclaw cron add
|
|
134
|
+
--name "$task_name"
|
|
135
|
+
--cron "$remind_cron"
|
|
136
|
+
--tz "$TZ"
|
|
137
|
+
--session main
|
|
138
|
+
--system-event "$msg"
|
|
139
|
+
--delete-after-run
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if "${CRON_CMD[@]}" 2>/dev/null; then
|
|
143
|
+
echo " ✅ 已创建"
|
|
144
|
+
((CREATED++)) || true
|
|
145
|
+
else
|
|
146
|
+
echo " ❌ 创建失败" >&2
|
|
147
|
+
((FAILED++)) || true
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
echo ""
|
|
151
|
+
done
|
|
152
|
+
|
|
153
|
+
# ── 汇总 ─────────────────────────────────────────────────
|
|
154
|
+
echo "────────────────────────────────"
|
|
155
|
+
echo "✅ 成功创建:${CREATED} 个提醒任务"
|
|
156
|
+
[[ "$SKIPPED" -gt 0 ]] && echo "⏭️ 跳过:${SKIPPED} 个"
|
|
157
|
+
[[ "$FAILED" -gt 0 ]] && echo "❌ 失败:${FAILED} 个"
|
|
158
|
+
echo ""
|
|
159
|
+
echo "查看所有提醒任务:"
|
|
160
|
+
echo " openclaw cron list"
|
|
161
|
+
echo ""
|
|
162
|
+
echo "删除指定任务:"
|
|
163
|
+
echo " openclaw cron remove <task-id>"
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# daily_activate.sh - 每天自动激活:读取日程文件 → 解析今日事项 → 创建提醒 → 发日报
|
|
3
|
+
#
|
|
4
|
+
# 由 setup.sh 注册为每日定时任务,无需用户手动触发。
|
|
5
|
+
#
|
|
6
|
+
# 依赖:
|
|
7
|
+
# ~/.config/schedule-reminder/config.env (由 setup.sh 写入)
|
|
8
|
+
# python3, jq, openclaw
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
CONFIG_FILE="${HOME}/.config/schedule-reminder/config.env"
|
|
14
|
+
|
|
15
|
+
# ── 读取配置 ──────────────────────────────────────────────
|
|
16
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
17
|
+
echo "[schedule-reminder] 错误:配置文件不存在,请先运行 setup.sh" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# shellcheck source=/dev/null
|
|
22
|
+
source "$CONFIG_FILE"
|
|
23
|
+
|
|
24
|
+
# 必填变量检查
|
|
25
|
+
: "${SCHEDULE_FILE:?'请在 config.env 中设置 SCHEDULE_FILE'}"
|
|
26
|
+
: "${SCHEDULE_TZ:=Asia/Shanghai}"
|
|
27
|
+
: "${SCHEDULE_ADVANCE_MIN:=15}"
|
|
28
|
+
: "${SCHEDULE_DAILY_HOUR:=7}"
|
|
29
|
+
: "${SCHEDULE_DAILY_MIN:=0}"
|
|
30
|
+
: "${SCHEDULE_SEND_SUMMARY:=true}"
|
|
31
|
+
# SCHEDULE_CHANNEL / SCHEDULE_RECIPIENT 为可选,留空则 cron 默认回到上次对话渠道(channel=last)
|
|
32
|
+
SCHEDULE_CHANNEL="${SCHEDULE_CHANNEL:-}"
|
|
33
|
+
SCHEDULE_RECIPIENT="${SCHEDULE_RECIPIENT:-}"
|
|
34
|
+
|
|
35
|
+
TODAY=$(TZ="$SCHEDULE_TZ" date +%Y-%m-%d)
|
|
36
|
+
NOW=$(TZ="$SCHEDULE_TZ" date +"%Y-%m-%d %H:%M")
|
|
37
|
+
LOG_DIR="${HOME}/.config/schedule-reminder/logs"
|
|
38
|
+
mkdir -p "$LOG_DIR"
|
|
39
|
+
LOG_FILE="${LOG_DIR}/activate-${TODAY}.log"
|
|
40
|
+
|
|
41
|
+
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
|
|
42
|
+
|
|
43
|
+
log "===== 日程提醒 每日激活 ====="
|
|
44
|
+
log "日期:$TODAY 渠道:${SCHEDULE_CHANNEL:-last} 接收者:${SCHEDULE_RECIPIENT:-(上次对话用户)}"
|
|
45
|
+
|
|
46
|
+
# ── 展开文件路径 ──────────────────────────────────────────
|
|
47
|
+
SCHEDULE_FILE_EXPANDED="${SCHEDULE_FILE/#\~/$HOME}"
|
|
48
|
+
|
|
49
|
+
if [[ ! -f "$SCHEDULE_FILE_EXPANDED" ]]; then
|
|
50
|
+
MSG="⚠️ 日程文件未找到:${SCHEDULE_FILE}\n请检查文件路径是否正确。"
|
|
51
|
+
SEND_CMD=(openclaw message send --message "$MSG")
|
|
52
|
+
[[ -n "$SCHEDULE_CHANNEL" ]] && SEND_CMD+=(--channel "$SCHEDULE_CHANNEL")
|
|
53
|
+
[[ -n "$SCHEDULE_RECIPIENT" ]] && SEND_CMD+=(--to "$SCHEDULE_RECIPIENT")
|
|
54
|
+
"${SEND_CMD[@]}" 2>>"$LOG_FILE" || true
|
|
55
|
+
log "错误:日程文件不存在:$SCHEDULE_FILE_EXPANDED"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ── 解析日程 ──────────────────────────────────────────────
|
|
60
|
+
PARSE_OUT="/tmp/schedule_parsed_${TODAY}.json"
|
|
61
|
+
|
|
62
|
+
python3 "${SCRIPT_DIR}/parse_schedule.py" \
|
|
63
|
+
--file "$SCHEDULE_FILE_EXPANDED" \
|
|
64
|
+
--mode today \
|
|
65
|
+
--now "$NOW" \
|
|
66
|
+
> "$PARSE_OUT" 2>>"$LOG_FILE"
|
|
67
|
+
|
|
68
|
+
UPCOMING_COUNT=$(jq '.summary.upcoming_count' "$PARSE_OUT")
|
|
69
|
+
CONFLICT_COUNT=$(jq '.summary.conflict_count' "$PARSE_OUT")
|
|
70
|
+
|
|
71
|
+
log "解析完成:today=$UPCOMING_COUNT 个事项,冲突=$CONFLICT_COUNT"
|
|
72
|
+
|
|
73
|
+
# ── 过滤掉「提醒时刻已过」的事项 ─────────────────────────
|
|
74
|
+
NOW_MIN=$(TZ="$SCHEDULE_TZ" date +%H:%M | awk -F: '{print $1*60+$2}')
|
|
75
|
+
FUTURE_EVENTS=$(jq --argjson now_min "$NOW_MIN" '
|
|
76
|
+
.upcoming | map(select(
|
|
77
|
+
(.remind_cron != null) and (.start_min != null) and (.start_min > $now_min)
|
|
78
|
+
))
|
|
79
|
+
' "$PARSE_OUT")
|
|
80
|
+
|
|
81
|
+
FUTURE_COUNT=$(echo "$FUTURE_EVENTS" | jq 'length')
|
|
82
|
+
log "有效(未过期)提醒事项:$FUTURE_COUNT 个"
|
|
83
|
+
|
|
84
|
+
# ── 构建今日日报消息 ──────────────────────────────────────
|
|
85
|
+
build_summary() {
|
|
86
|
+
local date_cn
|
|
87
|
+
date_cn=$(TZ="$SCHEDULE_TZ" date +"%m月%d日")
|
|
88
|
+
|
|
89
|
+
local lines="📅 今日日程 · ${date_cn}\n\n"
|
|
90
|
+
|
|
91
|
+
if [[ "$FUTURE_COUNT" -eq 0 && "$UPCOMING_COUNT" -eq 0 ]]; then
|
|
92
|
+
lines+="今日没有日程安排,好好休息!\n"
|
|
93
|
+
else
|
|
94
|
+
# 有效提醒列表
|
|
95
|
+
if [[ "$FUTURE_COUNT" -gt 0 ]]; then
|
|
96
|
+
lines+="待提醒事项(共 ${FUTURE_COUNT} 个):\n"
|
|
97
|
+
while IFS= read -r ev; do
|
|
98
|
+
local title start_str end_str location advance remind_str priority_icon
|
|
99
|
+
title=$(echo "$ev" | jq -r '.title')
|
|
100
|
+
start_str=$(echo "$ev" | jq -r '.start_str')
|
|
101
|
+
end_str=$(echo "$ev" | jq -r '.end_str')
|
|
102
|
+
location=$(echo "$ev" | jq -r '.location // ""')
|
|
103
|
+
advance=$(echo "$ev" | jq -r '.advance_min')
|
|
104
|
+
remind_str=$(echo "$ev" | jq -r '.remind_time_str // ""')
|
|
105
|
+
local priority
|
|
106
|
+
priority=$(echo "$ev" | jq -r '.priority')
|
|
107
|
+
case "$priority" in
|
|
108
|
+
高) priority_icon="🔴" ;;
|
|
109
|
+
中) priority_icon="🟡" ;;
|
|
110
|
+
低) priority_icon="⚪" ;;
|
|
111
|
+
*) priority_icon="🔵" ;;
|
|
112
|
+
esac
|
|
113
|
+
|
|
114
|
+
lines+=" ${priority_icon} ${start_str}–${end_str} 【${title}】"
|
|
115
|
+
[[ -n "$location" ]] && lines+=" @ ${location}"
|
|
116
|
+
[[ -n "$remind_str" ]] && lines+="(⏰ ${remind_str} 提醒,提前${advance}分钟)"
|
|
117
|
+
lines+="\n"
|
|
118
|
+
done < <(echo "$FUTURE_EVENTS" | jq -c '.[]')
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# 已过期事项说明
|
|
122
|
+
local expired_count
|
|
123
|
+
expired_count=$(( UPCOMING_COUNT - FUTURE_COUNT ))
|
|
124
|
+
if [[ "$expired_count" -gt 0 ]]; then
|
|
125
|
+
lines+="\n(另有 ${expired_count} 个事项已开始或已结束,不再提醒)\n"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# 冲突提示
|
|
130
|
+
if [[ "$CONFLICT_COUNT" -gt 0 ]]; then
|
|
131
|
+
lines+="\n⚠️ 检测到 ${CONFLICT_COUNT} 个时间冲突:\n"
|
|
132
|
+
while IFS= read -r conflict; do
|
|
133
|
+
local msg
|
|
134
|
+
msg=$(echo "$conflict" | jq -r '.message')
|
|
135
|
+
lines+=" ${msg}\n"
|
|
136
|
+
done < <(jq -c '.conflicts[]' "$PARSE_OUT")
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
lines+="\n祝今天顺利!"
|
|
140
|
+
echo -e "$lines"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ── 发送日报 ──────────────────────────────────────────────
|
|
144
|
+
if [[ "$SCHEDULE_SEND_SUMMARY" == "true" && "$UPCOMING_COUNT" -gt 0 ]]; then
|
|
145
|
+
SUMMARY=$(build_summary)
|
|
146
|
+
SEND_CMD=(openclaw message send --message "$SUMMARY")
|
|
147
|
+
[[ -n "$SCHEDULE_CHANNEL" ]] && SEND_CMD+=(--channel "$SCHEDULE_CHANNEL")
|
|
148
|
+
[[ -n "$SCHEDULE_RECIPIENT" ]] && SEND_CMD+=(--to "$SCHEDULE_RECIPIENT")
|
|
149
|
+
"${SEND_CMD[@]}" 2>>"$LOG_FILE" && log "日报已发送" || log "日报发送失败"
|
|
150
|
+
elif [[ "$UPCOMING_COUNT" -eq 0 ]]; then
|
|
151
|
+
log "今日无日程,跳过日报"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# ── 创建今日提醒 cron 任务 ───────────────────────────────
|
|
155
|
+
if [[ "$FUTURE_COUNT" -gt 0 ]]; then
|
|
156
|
+
# 将有效事项写入临时文件(按 create_reminders.sh 期望的结构)
|
|
157
|
+
echo "$FUTURE_EVENTS" | jq '{
|
|
158
|
+
meta: {},
|
|
159
|
+
summary: { upcoming_count: length },
|
|
160
|
+
upcoming: .
|
|
161
|
+
}' > "/tmp/schedule_upcoming_${TODAY}.json"
|
|
162
|
+
|
|
163
|
+
bash "${SCRIPT_DIR}/create_reminders.sh" \
|
|
164
|
+
--events-json "/tmp/schedule_upcoming_${TODAY}.json" \
|
|
165
|
+
--channel "$SCHEDULE_CHANNEL" \
|
|
166
|
+
--to "$SCHEDULE_RECIPIENT" \
|
|
167
|
+
--tz "$SCHEDULE_TZ" \
|
|
168
|
+
2>>"$LOG_FILE" | tee -a "$LOG_FILE"
|
|
169
|
+
|
|
170
|
+
log "提醒任务创建完成"
|
|
171
|
+
else
|
|
172
|
+
log "无有效提醒事项,跳过创建"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
log "===== 激活完成 ====="
|