Files
dingtalk-workspace-cli/skills/mono/scripts/todo_batch_create.py
修雨 5ce7cb61b0 feat(skill): dws skill setup with mono/multi modes (#332)
新增 `dws skill setup` 交互式安装命令,支持单产品(mono)和多产品(multi,标记为试验版)两种 skill 布局,并与开源 dws v1.0.30 命令对齐。

主要变更:
- 骨架:skills/ 拆为 skills/mono/ 和 skills/multi/(29 个产品 skill),git rename 保留历史
- 命令:新增 `skill setup` cobra 子命令,支持 --mode、--target、--source、--yes,huh 交互
  - multi 模式支持 -s/--skill、-x/--exclude 按产品选择
  - 覆盖全部 14 个 agent home 目录(与 install.sh 一致)
- 发布:install.sh / install.ps1 / post-goreleaser.sh 改读 skills/mono;dws-skills.zip 同时包含 mono 和 multi 内容;~/.dws 本地缓存
- 内容对齐:删除 8 个仅内部使用的产品 skill、dingtalk-a2a;修正 mono/multi 共 15+ 处命令引用以匹配 OSS dws v1.0.30;chat skill 从 dws-wukong 回移更新
- 漂移清理:物理删除 skill 文档中 OSS 未暴露的命令,true-fail 命令转为 caveat;--help diff 100% 覆盖审计
- 测试:新增 Layer A 静态命令调度验证 + Layer B 端到端 mock 调度验证
- 同步 main:drive 9 个命令同步到 mono/multi skill
2026-05-26 09:17:37 +08:00

160 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
从 JSON 文件批量创建待办(含优先级、截止时间、执行者)
用法:
python todo_batch_create.py todos.json
python todo_batch_create.py todos.json --dry-run
todos.json 格式:
[
{"title": "修复线上Bug", "executors": "userId1,userId2", "priority": 40},
{"title": "写周报", "executors": "userId1", "due": "2026-03-15"},
{"title": "代码评审", "executors": "userId1"},
{"title": "每日站会", "executors": "userId1", "due": "2026-03-20",
"recurrence": "DTSTART:20260320T020000Z\\nRRULE:FREQ=DAILY;INTERVAL=1"}
]
字段说明:
- title: 待办标题 (必填)
- executors: 执行者 userId,多人逗号分隔 (必填)
- priority: 优先级 10=低/20=普通/30=较高/40=紧急 (可选)
- due: 截止日期 YYYY-MM-DD 或毫秒时间戳 (可选)
- recurrence: 循环待办规则 (可选,需同时有 due);字符串内需含换行,与 dws --recurrence 一致
"""
import sys
import json
import subprocess
import re
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional
ALLOWED_PRIORITIES = {10, 20, 30, 40}
DATE_PATTERN = re.compile(r'^\d{4}-\d{2}-\d{2}$')
MAX_FILE_SIZE = 10 * 1024 * 1024
def run_dws(
args: List[str], dry_run: bool = False,
) -> Optional[Dict[str, Any]]:
cmd = ['dws'] + args
if dry_run:
print(f"[dry-run] {' '.join(cmd)}")
return {'dry_run': True}
try:
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=60
)
if result.returncode != 0:
print(f" ✗ 错误:{result.stderr.strip()}")
return None
return json.loads(result.stdout)
except subprocess.TimeoutExpired:
print(' ✗ 命令执行超时', file=sys.stderr)
return None
except (json.JSONDecodeError, FileNotFoundError) as e:
print(f" ✗ 错误:{e}", file=sys.stderr)
return None
def parse_due(due_value) -> Optional[str]:
if not due_value:
return None
due_str = str(due_value)
if due_str.isdigit() and len(due_str) >= 10:
return due_str
if DATE_PATTERN.match(due_str):
dt = datetime.strptime(due_str, '%Y-%m-%d')
dt = dt.replace(hour=23, minute=59, second=59)
return str(int(dt.timestamp() * 1000))
print(f" ⚠ 无法解析截止时间:{due_value},跳过")
return None
def validate_todo(item: Dict[str, Any], idx: int) -> bool:
if not isinstance(item, dict):
print(f" ✗ #{idx+1} 不是有效对象")
return False
if not item.get('title', '').strip():
print(f" ✗ #{idx+1} 缺少 title")
return False
if not item.get('executors', '').strip():
print(f" ✗ #{idx+1} 缺少 executors")
return False
priority = item.get('priority')
if priority is not None and int(priority) not in ALLOWED_PRIORITIES:
print(f" ✗ #{idx+1} 无效优先级:{priority}")
return False
recurrence = item.get('recurrence')
if recurrence and not str(recurrence).strip():
print(f" ✗ #{idx+1} recurrence 不能为空字符串")
return False
if recurrence and not item.get('due'):
print(f" ✗ #{idx+1} 设置 recurrence 时必须提供 due")
return False
return True
def main():
dry_run = '--dry-run' in sys.argv
args = [a for a in sys.argv[1:] if a != '--dry-run']
if not args:
print(__doc__)
sys.exit(1)
file_path = Path(args[0])
if not file_path.exists():
print(f"错误:文件不存在:{file_path}")
sys.exit(1)
if file_path.stat().st_size > MAX_FILE_SIZE:
print(f"错误:文件过大 (限制 {MAX_FILE_SIZE // 1024}KB)")
sys.exit(1)
with open(file_path, 'r', encoding='utf-8') as f:
todos = json.load(f)
if not isinstance(todos, list) or not todos:
print('错误:JSON 文件必须是非空数组')
sys.exit(1)
for i, item in enumerate(todos):
if not validate_todo(item, i):
sys.exit(1)
print(f"📋 准备创建 {len(todos)} 条待办\n")
success, fail = 0, 0
for i, item in enumerate(todos):
title = item['title'].strip()
cmd_args = [
'todo', 'task', 'create',
'--title', title,
'--executors', item['executors'].strip(),
'--format', 'json',
]
priority = item.get('priority')
if priority is not None:
cmd_args.extend(['--priority', str(int(priority))])
due = parse_due(item.get('due'))
if due:
cmd_args.extend(['--due', due])
recurrence = item.get('recurrence')
if recurrence:
rr = str(recurrence).replace('\\n', '\n')
cmd_args.extend(['--recurrence', rr])
result = run_dws(cmd_args, dry_run=dry_run)
if result:
print(f" ✓ [{i+1}/{len(todos)}] {title}")
success += 1
else:
print(f" ✗ [{i+1}/{len(todos)}] {title}")
fail += 1
print(f"\n完成: 成功 {success}, 失败 {fail}")
sys.exit(0 if fail == 0 else 1)
if __name__ == '__main__':
main()