新增 `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
159 lines
5.0 KiB
Python
159 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
批量同意/拒绝待审批项(含安全确认)
|
|
|
|
用法:
|
|
python oa_batch_approve.py --action approve --days 7
|
|
python oa_batch_approve.py --action reject --remark "不符合要求"
|
|
python oa_batch_approve.py --action approve --instance-ids id1,id2
|
|
python oa_batch_approve.py --dry-run --action approve
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import subprocess
|
|
import argparse
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Any, Optional
|
|
|
|
|
|
def run_dws(
|
|
args: List[str], dry_run: bool = False,
|
|
) -> Optional[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, json.JSONDecodeError,
|
|
FileNotFoundError) as e:
|
|
print(f" ✗ 错误:{e}")
|
|
return None
|
|
|
|
|
|
def to_iso(dt: datetime) -> str:
|
|
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='批量同意/拒绝审批'
|
|
)
|
|
parser.add_argument(
|
|
'--action', required=True,
|
|
choices=['approve', 'reject'], help='审批动作',
|
|
)
|
|
parser.add_argument(
|
|
'--remark', default='', help='审批意见'
|
|
)
|
|
parser.add_argument('--days', type=int, default=7)
|
|
parser.add_argument('--instance-ids', default='')
|
|
parser.add_argument(
|
|
'--yes', action='store_true', help='跳过确认'
|
|
)
|
|
parser.add_argument('--dry-run', action='store_true')
|
|
args = parser.parse_args()
|
|
|
|
instance_ids: List[str] = []
|
|
if args.instance_ids:
|
|
instance_ids = [x.strip() for x in
|
|
args.instance_ids.split(',') if x.strip()]
|
|
else:
|
|
now = datetime.now()
|
|
start = now - timedelta(days=args.days)
|
|
data = run_dws([
|
|
'oa', 'approval', 'list-pending',
|
|
'--start', to_iso(start),
|
|
'--end', to_iso(now),
|
|
'--format', 'json',
|
|
], dry_run=args.dry_run)
|
|
if not args.dry_run and data:
|
|
if isinstance(data, list):
|
|
items = data
|
|
elif isinstance(data, dict):
|
|
inner = data.get('result', data)
|
|
if isinstance(inner, dict):
|
|
items = inner.get('processInstanceList',
|
|
inner.get('items', []))
|
|
elif isinstance(inner, list):
|
|
items = inner
|
|
else:
|
|
items = []
|
|
else:
|
|
items = []
|
|
instance_ids = [
|
|
item.get('processInstanceId') or item.get('id')
|
|
for item in items
|
|
if isinstance(item, dict)
|
|
and (item.get('processInstanceId') or item.get('id'))
|
|
]
|
|
|
|
if not instance_ids and not args.dry_run:
|
|
print('✅ 没有待处理的审批')
|
|
return
|
|
|
|
action_label = '同意' if args.action == 'approve' else '拒绝'
|
|
count = len(instance_ids) if instance_ids else '?'
|
|
print(f"\n⚠️ 即将 {action_label} {count} 条审批")
|
|
if not args.yes and not args.dry_run:
|
|
confirm = input('确认执行?(y/N): ').strip().lower()
|
|
if confirm != 'y':
|
|
print('已取消')
|
|
return
|
|
|
|
success, fail = 0, 0
|
|
for i, inst_id in enumerate(instance_ids or ['<INST_ID>'], 1):
|
|
tasks_data = run_dws([
|
|
'oa', 'approval', 'tasks',
|
|
'--instance-id', inst_id,
|
|
'--format', 'json',
|
|
], dry_run=args.dry_run)
|
|
|
|
task_id = None
|
|
if not args.dry_run and tasks_data:
|
|
if isinstance(tasks_data, list):
|
|
task_ids = tasks_data
|
|
elif isinstance(tasks_data, dict):
|
|
inner = tasks_data.get('result', tasks_data)
|
|
if isinstance(inner, dict):
|
|
task_ids = inner.get('tasks', inner.get('items', []))
|
|
elif isinstance(inner, list):
|
|
task_ids = inner
|
|
else:
|
|
task_ids = []
|
|
else:
|
|
task_ids = []
|
|
if task_ids:
|
|
task_id = (task_ids[0] if isinstance(task_ids[0], str)
|
|
else task_ids[0].get('taskId', ''))
|
|
|
|
cmd_args = [
|
|
'oa', 'approval', args.action,
|
|
'--instance-id', inst_id,
|
|
'--task-id', task_id or '<TASK_ID>',
|
|
'--format', 'json',
|
|
]
|
|
if args.remark:
|
|
cmd_args.extend(['--remark', args.remark])
|
|
|
|
result = run_dws(cmd_args, dry_run=args.dry_run)
|
|
if result:
|
|
print(f" ✓ [{i}/{count}] {inst_id} → {action_label}")
|
|
success += 1
|
|
else:
|
|
print(f" ✗ [{i}/{count}] {inst_id}")
|
|
fail += 1
|
|
|
|
print(f"\n完成: 成功 {success}, 失败 {fail}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|