@yionr/plan-kit 1.0.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.
Files changed (28) hide show
  1. package/cli.mjs +175 -0
  2. package/package.json +21 -0
  3. package/scripts/collect_monthly_data.py +126 -0
  4. package/scripts/config.py +38 -0
  5. package/scripts/create_next_week.py +85 -0
  6. package/scripts/generate_monthly_md.py +91 -0
  7. package/scripts/get_current_week.py +79 -0
  8. package/scripts/get_user_info.py +85 -0
  9. package/scripts/query_weekly_records.py +90 -0
  10. package/scripts/update_completion.py +69 -0
  11. package/skills/sab-monthly-summary/SKILL.md +85 -0
  12. package/skills/sab-monthly-summary/scripts/collect_monthly_data.py +126 -0
  13. package/skills/sab-monthly-summary/scripts/config.py +41 -0
  14. package/skills/sab-monthly-summary/scripts/create_next_week.py +85 -0
  15. package/skills/sab-monthly-summary/scripts/generate_monthly_md.py +91 -0
  16. package/skills/sab-monthly-summary/scripts/get_current_week.py +79 -0
  17. package/skills/sab-monthly-summary/scripts/get_user_info.py +85 -0
  18. package/skills/sab-monthly-summary/scripts/query_weekly_records.py +90 -0
  19. package/skills/sab-monthly-summary/scripts/update_completion.py +69 -0
  20. package/skills/sab-weekly-plan/SKILL.md +109 -0
  21. package/skills/sab-weekly-plan/scripts/collect_monthly_data.py +126 -0
  22. package/skills/sab-weekly-plan/scripts/config.py +41 -0
  23. package/skills/sab-weekly-plan/scripts/create_next_week.py +85 -0
  24. package/skills/sab-weekly-plan/scripts/generate_monthly_md.py +91 -0
  25. package/skills/sab-weekly-plan/scripts/get_current_week.py +79 -0
  26. package/skills/sab-weekly-plan/scripts/get_user_info.py +85 -0
  27. package/skills/sab-weekly-plan/scripts/query_weekly_records.py +90 -0
  28. package/skills/sab-weekly-plan/scripts/update_completion.py +69 -0
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ """按模板生成月总结 Markdown。
3
+
4
+ 用法: python3 generate_monthly_md.py --data '<json_from_collect>'
5
+ 输出: Markdown 文本到 stdout
6
+ """
7
+
8
+ import json
9
+ import sys
10
+ import argparse
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser()
15
+ parser.add_argument("--data", required=True)
16
+ args = parser.parse_args()
17
+
18
+ try:
19
+ data = json.loads(args.data)
20
+ except json.JSONDecodeError as e:
21
+ print("ERROR: invalid JSON: {}".format(e), file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+ name = data.get("name", "")
25
+ month = data.get("month", "")
26
+ records = data.get("records", [])
27
+ temp_work = data.get("temp_work", [])
28
+ year = 2026
29
+
30
+ lines = []
31
+ lines.append("# 信息技术科 {}年{}月工作总结\n".format(year, month))
32
+ lines.append(name + "\n")
33
+
34
+ # 一、工作计划完成情况
35
+ lines.append("## 一、工作计划完成情况(包括计划外完成)\n")
36
+
37
+ table_rows = []
38
+ idx = 0
39
+
40
+ for rec in records:
41
+ idx += 1
42
+ completion = rec.get("completion_status", "")
43
+ remark = rec.get("remark", "")
44
+ follow_up = ""
45
+ if completion == "未完成":
46
+ follow_up = "移至下月继续跟进"
47
+
48
+ table_rows.append("| {} | {} | {} | {} | {} |".format(
49
+ idx, rec.get("content", ""), completion, remark, follow_up
50
+ ))
51
+
52
+ # 临时工作也插入表格
53
+ for tw in temp_work:
54
+ idx += 1
55
+ table_rows.append("| {} | {} | {} | {} | {} |".format(
56
+ idx, tw.get("content", ""), "(临时工作)", tw.get("content", "")[:50], "-"
57
+ ))
58
+
59
+ if table_rows:
60
+ lines.append("| 序号 | 内容 | 完成情况 | 原因说明 | 跟进措施 |")
61
+ lines.append("|------|------|---------|---------|---------|")
62
+ lines.extend(table_rows)
63
+ lines.append("")
64
+
65
+ # 二、重点工作回顾
66
+ lines.append("## 二、重点工作回顾\n")
67
+ completed = [r for r in records if r.get("completion_status") == "完成"]
68
+ if completed:
69
+ lines.append("<!-- AI: 从以下已完成项中提炼1-3个重点工作 -->")
70
+ for r in completed[:5]:
71
+ lines.append("<!-- - {} -->".format(r.get("content", "")))
72
+ lines.append("(待 AI 根据上述材料生成)\n")
73
+
74
+ # 三、存在问题及分析
75
+ lines.append("## 三、存在问题及分析\n")
76
+ undone = [r for r in records if r.get("completion_status") == "未完成"]
77
+ if undone:
78
+ lines.append("<!-- AI: 从以下未完成项汇总共性问题 -->")
79
+ for r in undone:
80
+ lines.append("<!-- - {}: {} -->".format(r.get("content", ""), r.get("remark", "")))
81
+ lines.append("(待 AI 根据上述材料生成)\n")
82
+
83
+ # 四、下月工作重点
84
+ lines.append("## 四、下月工作重点\n")
85
+ lines.append("(待 AI 根据上述材料生成)\n")
86
+
87
+ print("\n".join(lines))
88
+
89
+
90
+ if __name__ == "__main__":
91
+ main()
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env python3
2
+ """计算当前周和下周的月周值,输出 JSON 到 stdout。
3
+
4
+ 月周归属规则:
5
+ - 以周一为周起始
6
+ - 一周中在某个月天数 >= 4 天 → 归属该月
7
+ - 该月第一周从归属该月的第一个周一开始编号
8
+
9
+ 用法: python3 get_current_week.py [--date YYYY-MM-DD]
10
+ """
11
+
12
+ import json
13
+ import sys
14
+ from datetime import date, timedelta
15
+
16
+
17
+ def get_monday(d):
18
+ """返回日期 d 所在周的周一。"""
19
+ return d - timedelta(days=d.weekday())
20
+
21
+
22
+ def count_month_days(week_monday):
23
+ """统计一周在各个月的天数。"""
24
+ counts = {}
25
+ for i in range(7):
26
+ day = week_monday + timedelta(days=i)
27
+ counts[day.month] = counts.get(day.month, 0) + 1
28
+ return counts
29
+
30
+
31
+ def get_yuezhou(week_monday):
32
+ """根据周一日期,计算对应的月周字符串。"""
33
+ counts = count_month_days(week_monday)
34
+ target_month = max(counts, key=counts.get)
35
+
36
+ if counts[target_month] < 4:
37
+ target_month = (week_monday + timedelta(days=3)).month
38
+
39
+ year = week_monday.year
40
+ first_day = date(year, target_month, 1)
41
+ cursor = get_monday(first_day)
42
+
43
+ week_num = 0
44
+ while cursor <= week_monday + timedelta(days=7):
45
+ cursor_counts = count_month_days(cursor)
46
+ assigned = max(cursor_counts, key=cursor_counts.get)
47
+
48
+ if assigned == target_month:
49
+ week_num += 1
50
+ if cursor == week_monday:
51
+ return "{}月第{}周".format(target_month, week_num)
52
+
53
+ cursor += timedelta(days=7)
54
+
55
+ return None
56
+
57
+
58
+ def main():
59
+ specified_date = None
60
+ for i, arg in enumerate(sys.argv):
61
+ if arg == "--date" and i + 1 < len(sys.argv):
62
+ specified_date = date.fromisoformat(sys.argv[i + 1])
63
+ break
64
+
65
+ today = specified_date or date.today()
66
+ this_monday = get_monday(today)
67
+ next_monday = this_monday + timedelta(days=7)
68
+
69
+ result = {
70
+ "this_week": get_yuezhou(this_monday),
71
+ "next_week": get_yuezhou(next_monday),
72
+ "this_monday": this_monday.isoformat(),
73
+ "next_monday": next_monday.isoformat(),
74
+ }
75
+ print(json.dumps(result, ensure_ascii=False))
76
+
77
+
78
+ if __name__ == "__main__":
79
+ main()
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ """查部门人员表,返回当前用户的姓名、open_id、所属组。
3
+
4
+ 流程:
5
+ 1. lark-cli contact +get-user --as user → 获取当前用户 open_id
6
+ 2. lark-cli base +record-search → 在部门人员表按人员字段筛选
7
+ 3. 返回 {open_id, name, group}
8
+
9
+ 用法: python3 get_user_info.py
10
+ 输出: {"open_id": "ou_xxx", "name": "张三", "group": "移动"}
11
+ """
12
+
13
+ import json
14
+ import subprocess
15
+ import sys
16
+ import os
17
+
18
+ # 允许脚本从上级目录 import config
19
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
20
+ import config
21
+
22
+
23
+ def run_lark(args):
24
+ """执行 lark-cli 命令并返回 JSON 结果。失败时退出。"""
25
+ result = subprocess.run(
26
+ ["lark-cli"] + args,
27
+ capture_output=True,
28
+ text=True,
29
+ timeout=30,
30
+ )
31
+ try:
32
+ data = json.loads(result.stdout)
33
+ except json.JSONDecodeError:
34
+ print(json.dumps({"error": "parse_failed", "stdout": result.stdout[:500], "stderr": result.stderr[:500]}, ensure_ascii=False))
35
+ sys.exit(1)
36
+
37
+ if not data.get("ok"):
38
+ print(json.dumps({"error": "lark_cli_failed", "detail": data.get("error", {})}, ensure_ascii=False))
39
+ sys.exit(1)
40
+
41
+ return data
42
+
43
+
44
+ def main():
45
+ # 1. 获取当前用户
46
+ user_data = run_lark(["contact", "+get-user", "--as", "user"])
47
+ user = user_data["data"]["user"]
48
+ open_id = user["open_id"]
49
+ name = user["name"]
50
+
51
+ # 2. 在部门人员表中查找
52
+ record_data = run_lark([
53
+ "base", "+record-list",
54
+ "--base-token", config.BASE_TOKEN,
55
+ "--table-id", config.TABLE_IDS["部门人员"],
56
+ "--filter-json", json.dumps(
57
+ {"logic": "and", "conditions": [
58
+ ["人员", "intersects", [{"id": open_id}]],
59
+ ]},
60
+ ensure_ascii=False
61
+ ),
62
+ "--as", "user",
63
+ "--format", "json",
64
+ ])
65
+
66
+ records = record_data["data"]["data"]
67
+ if not records:
68
+ print(json.dumps({"error": "user_not_found", "open_id": open_id, "name": name}, ensure_ascii=False))
69
+ sys.exit(1)
70
+
71
+ # fields 顺序: [人名, 人员, 所属组, 推送日期]
72
+ record = records[0]
73
+ group_arr = record[2] # 所属组
74
+ group = group_arr[0] if group_arr else ""
75
+
76
+ result = {
77
+ "open_id": open_id,
78
+ "name": name,
79
+ "group": group,
80
+ }
81
+ print(json.dumps(result, ensure_ascii=False))
82
+
83
+
84
+ if __name__ == "__main__":
85
+ main()
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ """按用户和月周查询周计划记录。
3
+
4
+ 用法: python3 query_weekly_records.py --open-id ou_xxx --week "7月第4周"
5
+ 输出: JSON 数组
6
+ """
7
+
8
+ import json
9
+ import subprocess
10
+ import sys
11
+ import argparse
12
+ import os
13
+
14
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
15
+ import config
16
+
17
+
18
+ def run_lark(args):
19
+ result = subprocess.run(
20
+ ["lark-cli"] + args,
21
+ capture_output=True,
22
+ text=True,
23
+ timeout=30,
24
+ )
25
+ data = json.loads(result.stdout)
26
+ if not data.get("ok"):
27
+ print(json.dumps({"error": "lark_cli_failed", "detail": data.get("error", {})}, ensure_ascii=False))
28
+ sys.exit(1)
29
+ return data
30
+
31
+
32
+ def main():
33
+ parser = argparse.ArgumentParser()
34
+ parser.add_argument("--open-id", required=True)
35
+ parser.add_argument("--week", required=True)
36
+ args = parser.parse_args()
37
+
38
+ open_id = args.open_id
39
+ week = args.week
40
+
41
+ data = run_lark([
42
+ "base", "+record-list",
43
+ "--base-token", config.BASE_TOKEN,
44
+ "--table-id", config.TABLE_IDS["周计划"],
45
+ "--filter-json", json.dumps(
46
+ {"logic": "and", "conditions": [
47
+ ["月周", "intersects", [week]],
48
+ ["责任人", "intersects", [{"id": open_id}]],
49
+ ]},
50
+ ensure_ascii=False
51
+ ),
52
+ "--as", "user",
53
+ "--format", "json",
54
+ ])
55
+
56
+ records = data["data"]["data"]
57
+ field_ids = data["data"]["field_id_list"]
58
+
59
+ idx_content = field_ids.index("fldjdmxh69")
60
+ idx_completion = field_ids.index("fldF9ebqDi")
61
+ idx_remark = field_ids.index("fldu5uAqGU")
62
+ idx_hours = field_ids.index("fldQXhORVG")
63
+ idx_week = field_ids.index("fldAivwku1")
64
+
65
+ result = []
66
+ for i, rec in enumerate(records):
67
+ def safe_val(arr, default=""):
68
+ if isinstance(arr, list):
69
+ return arr[0] if arr else default
70
+ if arr is None:
71
+ return default
72
+ return arr
73
+
74
+ record_id = (
75
+ rec.get("_record_id") if isinstance(rec, dict) else None
76
+ ) or (data["data"].get("record_id_list") or [None])[i] or "rec_{}".format(i)
77
+ result.append({
78
+ "record_id": record_id,
79
+ "content": safe_val(rec[idx_content]),
80
+ "completion_status": safe_val(rec[idx_completion]),
81
+ "remark": safe_val(rec[idx_remark]),
82
+ "hours": safe_val(rec[idx_hours], None),
83
+ "week": safe_val(rec[idx_week]),
84
+ })
85
+
86
+ print(json.dumps(result, ensure_ascii=False))
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ """批量更新周计划记录的完成情况和备注。
3
+
4
+ 用法: python3 update_completion.py --records '<json_array>'
5
+ json_array 格式: [{"record_id": "rec_xxx", "completion": "完成", "remark": "..."}]
6
+
7
+ 输出: {"ok": true, "updated": N}
8
+ """
9
+
10
+ import json
11
+ import subprocess
12
+ import sys
13
+ import argparse
14
+ import os
15
+
16
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
17
+ import config
18
+
19
+
20
+ def main():
21
+ parser = argparse.ArgumentParser()
22
+ parser.add_argument("--records", required=True)
23
+ args = parser.parse_args()
24
+
25
+ try:
26
+ records = json.loads(args.records)
27
+ except json.JSONDecodeError:
28
+ print(json.dumps({"ok": False, "error": "invalid_json"}, ensure_ascii=False))
29
+ sys.exit(1)
30
+
31
+ update_records = {}
32
+ for r in records:
33
+ rid = r["record_id"]
34
+ fields = {}
35
+ if "completion" in r and r["completion"]:
36
+ fields["完成情况"] = r["completion"]
37
+ if "remark" in r:
38
+ fields["备注"] = r["remark"] if r["remark"] else ""
39
+ update_records[rid] = fields
40
+
41
+ payload = json.dumps({"update_records": update_records}, ensure_ascii=False)
42
+
43
+ result = subprocess.run(
44
+ [
45
+ "lark-cli", "base", "+record-batch-update",
46
+ "--base-token", config.BASE_TOKEN,
47
+ "--table-id", config.TABLE_IDS["周计划"],
48
+ "--json", payload,
49
+ "--as", "user",
50
+ ],
51
+ capture_output=True,
52
+ text=True,
53
+ timeout=30,
54
+ )
55
+
56
+ try:
57
+ data = json.loads(result.stdout)
58
+ except json.JSONDecodeError:
59
+ data = json.loads(result.stderr) if result.stderr else {"ok": False}
60
+
61
+ if data.get("ok"):
62
+ print(json.dumps({"ok": True, "updated": len(update_records)}, ensure_ascii=False))
63
+ else:
64
+ print(json.dumps({"ok": False, "error": data.get("error", {})}, ensure_ascii=False))
65
+ sys.exit(1)
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()