钉钉全产品 Skill 通过 命令管理钉钉产品能力。 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 - 不要猜测字段名/参数值,操作前必须先查询确认 严格要求 (MUST DO) - 所有命令必须加 以获取可解析输出 - 删除操作前必须加 并和用户确认 - 单次批量操作不超过 100 条记录 - 所有命令必须 严格遵循 对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) 产品总览 | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| | | AI表格:表格/数据表/字段/记录增删改查/模板搜索 | aitable.md | | | 日历:日程/参与者/会议室/闲忙查询 | calendar.md | | | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | contact.md | | | 文档:搜索/浏览/读取…

)\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef get_my_user_id(dry_run: bool = False) -> Optional[str]:\n data = run_dws([\n 'contact', 'user', 'get-self', '--format', 'json',\n ], dry_run=dry_run)\n if dry_run:\n return '\u003cMY_USER_ID>'\n if not data or not isinstance(data, dict):\n return None\n return data.get('userId') or data.get('userid')\n\n\ndef main():\n dry_run = '--dry-run' in sys.argv\n args = [a for a in sys.argv[1:] if a != '--dry-run']\n\n date_str = args[0] if args else 'today'\n if date_str == 'today':\n date_str = datetime.now().strftime('%Y-%m-%d')\n elif not DATE_PATTERN.match(date_str):\n print(__doc__)\n sys.exit(1)\n\n print('🔍 获取当前用户信息...')\n user_id = get_my_user_id(dry_run=dry_run)\n if not user_id and not dry_run:\n print('错误:无法获取当前用户 ID')\n sys.exit(1)\n\n print(f'📊 查询 {date_str} 考勤记录...\\n')\n data = run_dws([\n 'attendance', 'record', 'get',\n '--user', user_id or '\u003cMY_USER_ID>',\n '--date', date_str,\n '--format', 'json',\n ], dry_run=dry_run)\n\n if dry_run:\n return\n if not data:\n print('未查到考勤记录')\n return\n\n print(f\"📋 考勤记录 ({date_str})\")\n print('=' * 40)\n print(json.dumps(data, ensure_ascii=False, indent=2))\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":2537,"content_sha256":"de23144ada2bd293388b60d05a56e724fff52d6eb5c66185ce8ae89f094cffe2"},{"filename":"scripts/attendance_team_shift.py","content":"#!/usr/bin/env python3\n\"\"\"\n查询团队成员本周排班和出勤统计\n\n用法:\n python attendance_team_shift.py --users userId1,userId2,userId3\n python attendance_team_shift.py --users userId1,userId2 \\\n --from 2026-03-10 --to 2026-03-14\n python attendance_team_shift.py --users userId1 --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime, timedelta\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef get_week_range():\n today = datetime.now()\n monday = today - timedelta(days=today.weekday())\n friday = monday + timedelta(days=4)\n return monday.strftime('%Y-%m-%d'), friday.strftime('%Y-%m-%d')\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='查询团队成员排班和出勤统计'\n )\n parser.add_argument(\n '--users', required=True, help='用户 ID 列表,逗号分隔'\n )\n mon, fri = get_week_range()\n parser.add_argument('--from', dest='from_date', default=mon)\n parser.add_argument('--to', dest='to_date', default=fri)\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n user_count = len(args.users.split(','))\n if user_count > 50:\n print('错误:最多查询 50 人')\n sys.exit(1)\n\n print(f\"📊 团队排班查询 ({args.from_date} ~ {args.to_date})\")\n print(f\" 人数: {user_count}\")\n print('=' * 50)\n\n print('\\n🔍 查询排班信息...')\n data = run_dws([\n 'attendance', 'shift', 'list',\n '--users', args.users,\n '--start', args.from_date,\n '--end', args.to_date,\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n return\n if not data:\n print('未查到排班信息')\n return\n\n print(json.dumps(data, ensure_ascii=False, indent=2))\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":2507,"content_sha256":"23309b6d0e69216cde257fa86eb456e39a7f461f94da880c43901e6d4418fa32"},{"filename":"scripts/bot_broadcast.py","content":"#!/usr/bin/env python3\n\"\"\"\n用机器人向多个群批量发送相同消息(如日报提醒)\n\n用法:\n python bot_broadcast.py \\\n --robot-code \u003cROBOT_CODE> \\\n --chats \"conv_id1,conv_id2,conv_id3\" \\\n --title \"日报提醒\" \\\n --text \"请大家今天下班前提交日报\"\n\n python bot_broadcast.py \\\n --robot-code \u003cROBOT_CODE> \\\n --chats-file groups.txt \\\n --title \"周会通知\" \\\n --text \"明天下午3点周会\"\n\n python bot_broadcast.py --dry-run ...\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom pathlib import Path\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return {'dry_run': True}\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\" ✗ 错误:{result.stderr.strip()}\")\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\" ✗ 错误:{e}\")\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='向多个群批量发送机器人消息'\n )\n parser.add_argument(\n '--robot-code', required=True, help='机器人 Code'\n )\n parser.add_argument('--chats', default='', help='会话 ID 列表')\n parser.add_argument(\n '--chats-file', default='',\n help='会话 ID 文件 (每行一个)',\n )\n parser.add_argument('--title', required=True, help='消息标题')\n parser.add_argument(\n '--text', required=True, help='消息内容 Markdown'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n chat_ids: List[str] = []\n if args.chats:\n chat_ids = [c.strip() for c in args.chats.split(',')\n if c.strip()]\n elif args.chats_file:\n p = Path(args.chats_file)\n if not p.exists():\n print(f\"错误:文件不存在: {p}\")\n sys.exit(1)\n chat_ids = [line.strip() for line in\n p.read_text(encoding='utf-8').splitlines()\n if line.strip() and not line.startswith('#')]\n if not chat_ids:\n print('错误:需要 --chats 或 --chats-file')\n sys.exit(1)\n\n print(f\"📢 批量发送消息到 {len(chat_ids)} 个群\")\n print(f\" 标题: {args.title}\")\n print(f\" 机器人: {args.robot_code}\")\n print('=' * 50)\n\n success, fail = 0, 0\n for i, chat_id in enumerate(chat_ids, 1):\n result = run_dws([\n 'chat', 'message', 'send-by-bot',\n '--robot-code', args.robot_code,\n '--group', chat_id,\n '--title', args.title,\n '--text', args.text,\n '--format', 'json',\n ], dry_run=args.dry_run)\n if result:\n print(f\" ✓ [{i}/{len(chat_ids)}] {chat_id}\")\n success += 1\n else:\n print(f\" ✗ [{i}/{len(chat_ids)}] {chat_id}\")\n fail += 1\n\n print(f\"\\n完成: 成功 {success}, 失败 {fail}\")\n sys.exit(0 if fail == 0 else 1)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3363,"content_sha256":"832e7e59a1e3e6b992422791f0b568bfb311144355ff85d3bd154442ebb75e03"},{"filename":"scripts/bulk_add_fields.py","content":"#!/usr/bin/env python3\n\"\"\"\n批量添加字段到钉钉 AI 表格数据表(新版 schema)\n\n用法:\n python bulk_add_fields.py \u003cbaseId> \u003ctableId> fields.json\n\nfields.json 格式:\n [\n {\"fieldName\": \"字段 1\", \"type\": \"text\"},\n {\"fieldName\": \"字段 2\", \"type\": \"number\", \"config\": {\"formatter\": \"INT\"}},\n {\"fieldName\": \"字段 3\", \"type\": \"singleSelect\", \"config\": {\"options\": [{\"name\": \"高\"}]}}\n ]\n\n兼容写法:\n- name 会自动映射为 fieldName\n- phone 会自动映射为 telephone\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport os\nimport re\nfrom pathlib import Path\nfrom typing import Union, List, Dict, Any, Optional, Tuple\n\nJsonData = Union[List[Any], Dict[str, Any]]\n\nMAX_FILE_SIZE = 10 * 1024 * 1024\nALLOWED_FILE_EXTENSIONS = ['.json']\nRESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}

钉钉全产品 Skill 通过 命令管理钉钉产品能力。 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 - 不要猜测字段名/参数值,操作前必须先查询确认 严格要求 (MUST DO) - 所有命令必须加 以获取可解析输出 - 删除操作前必须加 并和用户确认 - 单次批量操作不超过 100 条记录 - 所有命令必须 严格遵循 对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) 产品总览 | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| | | AI表格:表格/数据表/字段/记录增删改查/模板搜索 | aitable.md | | | 日历:日程/参与者/会议室/闲忙查询 | calendar.md | | | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | contact.md | | | 文档:搜索/浏览/读取…

)\nALLOWED_FIELD_TYPES = {\n 'text', 'number', 'singleSelect', 'multipleSelect', 'date', 'currency',\n 'user', 'department', 'group', 'progress', 'rating', 'checkbox',\n 'attachment', 'url', 'richText', 'telephone', 'email', 'idCard',\n 'barcode', 'geolocation', 'primaryDoc', 'formula', 'unidirectionalLink',\n 'bidirectionalLink', 'creator', 'lastModifier', 'createdTime',\n 'lastModifiedTime',\n}\nFIELD_TYPE_ALIASES = {\n 'phone': 'telephone',\n}\n\n\ndef resolve_safe_path(path: str, allowed_root: Optional[str] = None) -> Path:\n if allowed_root is None:\n allowed_root = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd())\n\n allowed_root = Path(allowed_root).resolve()\n target_path = (\n Path(path).resolve()\n if Path(path).is_absolute()\n else (Path.cwd() / path).resolve()\n )\n\n try:\n target_path.relative_to(allowed_root)\n return target_path\n except ValueError:\n raise ValueError(\n f\"路径超出允许范围:{path}\\n\"\n f\"目标路径:{target_path}\\n\"\n f\"允许根目录:{allowed_root}\\n\"\n f\"提示:设置 OPENCLAW_WORKSPACE 环境变量或确保文件在工作目录内\"\n )\n\n\ndef validate_resource_id(resource_id: str) -> bool:\n return bool(resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip()))\n\n\ndef validate_file_extension(filename: str, allowed_extensions: list) -> bool:\n return any(filename.lower().endswith(ext) for ext in allowed_extensions)\n\n\ndef safe_json_load(file_path: Path, max_size: int = MAX_FILE_SIZE) -> JsonData:\n file_size = file_path.stat().st_size\n if file_size > max_size:\n raise ValueError(\n f\"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)\"\n )\n with open(file_path, 'r', encoding='utf-8') as f:\n return json.load(f)\n\n\ndef normalize_field_config(field: Dict[str, Any]) -> Dict[str, Any]:\n normalized = dict(field)\n if 'fieldName' not in normalized and 'name' in normalized:\n normalized['fieldName'] = normalized.pop('name')\n normalized['type'] = FIELD_TYPE_ALIASES.get(\n normalized.get('type', 'text'), normalized.get('type', 'text')\n )\n return normalized\n\n\ndef validate_field_config(field: Dict[str, Any]) -> Tuple[bool, str]:\n if not isinstance(field, dict):\n return False, '字段配置必须是对象'\n\n field = normalize_field_config(field)\n\n if 'fieldName' not in field:\n return False, '缺少必需字段:fieldName'\n if not isinstance(field['fieldName'], str) or not field['fieldName'].strip():\n return False, 'fieldName 必须是非空字符串'\n\n field_type = field.get('type', 'text')\n if field_type not in ALLOWED_FIELD_TYPES:\n return False, f\"不支持的字段类型:{field_type}\"\n\n config = field.get('config')\n if config is not None and not isinstance(config, dict):\n return False, 'config 必须是对象'\n\n if field_type in {'singleSelect', 'multipleSelect'}:\n options = (config or {}).get('options')\n if not options or not isinstance(options, list):\n return False, (\n 'singleSelect / multipleSelect 必须提供 config.options 数组'\n )\n\n if field_type in {'unidirectionalLink', 'bidirectionalLink'}:\n linked_sheet_id = (config or {}).get('linkedSheetId')\n if not linked_sheet_id or not validate_resource_id(linked_sheet_id):\n return False, '关联字段必须提供合法的 config.linkedSheetId'\n\n return True, ''\n\n\ndef build_fields_json(fields: List[Dict[str, Any]]) -> str:\n \"\"\"构建 --fields 参数的 JSON 字符串。\"\"\"\n payload_fields = []\n for field in fields:\n normalized = normalize_field_config(field)\n item: Dict[str, Any] = {\n 'fieldName': normalized['fieldName'].strip(),\n 'type': normalized.get('type', 'text'),\n }\n if 'config' in normalized and normalized['config'] is not None:\n item['config'] = normalized['config']\n payload_fields.append(item)\n return json.dumps(payload_fields, ensure_ascii=False)\n\n\ndef run_dws(args: List[str]) -> Optional[Dict[str, Any]]:\n if not args:\n print('错误:空命令')\n return None\n\n cmd = ['dws'] + args\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\")\n return None\n try:\n return json.loads(result.stdout)\n except json.JSONDecodeError as e:\n print(f\"无法解析响应:{result.stdout[:200]}...\")\n print(f\"JSON 解析错误:{e}\")\n return None\n except subprocess.TimeoutExpired:\n print('错误:命令执行超时(60 秒)')\n return None\n except FileNotFoundError:\n print('错误:未找到 dws 命令,请确认已安装')\n return None\n\n\ndef bulk_add_fields(\n base_id: str, table_id: str, fields_file: str\n) -> bool:\n try:\n safe_path = resolve_safe_path(fields_file)\n except ValueError as e:\n print(f\"路径验证失败:{e}\")\n return False\n\n if not validate_file_extension(fields_file, ALLOWED_FILE_EXTENSIONS):\n print(f\"错误:只允许 {', '.join(ALLOWED_FILE_EXTENSIONS)} 文件\")\n return False\n if not safe_path.exists():\n print(f\"错误:文件不存在:{safe_path}\")\n return False\n\n try:\n fields = safe_json_load(safe_path)\n except ValueError as e:\n print(f\"错误:{e}\")\n return False\n except json.JSONDecodeError as e:\n print(f\"错误:JSON 格式无效:{e}\")\n return False\n\n if not isinstance(fields, list) or not fields:\n print('错误:fields.json 必须是非空 JSON 数组')\n return False\n if len(fields) > 15:\n print('错误:单次最多创建 15 个字段,请拆分后重试')\n return False\n\n for i, field in enumerate(fields):\n valid, error = validate_field_config(field)\n if not valid:\n print(f\"错误:字段 #{i+1} 配置无效:{error}\")\n return False\n\n fields_json = build_fields_json(fields)\n result = run_dws([\n 'aitable', 'field', 'create',\n '--base-id', base_id,\n '--table-id', table_id,\n '--fields', fields_json,\n '--format', 'json',\n ])\n\n if not result:\n return False\n\n print(json.dumps(result, ensure_ascii=False, indent=2))\n return True\n\n\ndef main():\n if len(sys.argv) != 4:\n print(__doc__)\n print('用法示例:')\n print(' python bulk_add_fields.py basexxx tablexxx fields.json')\n sys.exit(1)\n\n base_id = sys.argv[1]\n table_id = sys.argv[2]\n fields_file = sys.argv[3]\n\n if not validate_resource_id(base_id):\n print('错误:无效的 baseId 格式')\n sys.exit(1)\n if not validate_resource_id(table_id):\n print('错误:无效的 tableId 格式')\n sys.exit(1)\n\n success = bulk_add_fields(base_id, table_id, fields_file)\n sys.exit(0 if success else 1)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":8062,"content_sha256":"6ef9e5cfff493844aaafca3e91afc11b2577c623d6b432e907bfa9846c760354"},{"filename":"scripts/calendar_free_slot_finder.py","content":"#!/usr/bin/env python3\n\"\"\"\n查询多人共同空闲时段,推荐最佳会议时间\n\n用法:\n python calendar_free_slot_finder.py \\\n --users userId1,userId2,userId3 \\\n --date 2026-03-15 \\\n --duration 60\n\n python calendar_free_slot_finder.py \\\n --users userId1,userId2 \\\n --date 2026-03-15 \\\n --start-hour 9 --end-hour 18 \\\n --duration 30 --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime, timedelta, timezone\nfrom typing import List, Dict, Any, Optional, Tuple\n\nTZ = timezone(timedelta(hours=8))\nSLOT_STEP_MIN = 30\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef fmt_iso(dt: datetime) -> str:\n return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')\n\n\ndef parse_busy_intervals(\n data: Any,\n) -> List[Tuple[datetime, datetime]]:\n intervals = []\n if not data:\n return intervals\n items = []\n if isinstance(data, list):\n items = data\n elif isinstance(data, dict):\n for user_data in data.values():\n if isinstance(user_data, list):\n items.extend(user_data)\n elif isinstance(user_data, dict):\n items.extend(\n user_data.get('busyTimes', [])\n )\n for item in items:\n start_str = item.get('startTime') or item.get('start', '')\n end_str = item.get('endTime') or item.get('end', '')\n if not start_str or not end_str:\n continue\n for fmt in (\n '%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S',\n '%Y-%m-%dT%H:%M%z',\n ):\n try:\n s = datetime.strptime(start_str, fmt)\n e = datetime.strptime(end_str, fmt)\n if s.tzinfo is None:\n s = s.replace(tzinfo=TZ)\n if e.tzinfo is None:\n e = e.replace(tzinfo=TZ)\n intervals.append((s, e))\n break\n except ValueError:\n continue\n return intervals\n\n\ndef find_free_slots(\n day_start: datetime, day_end: datetime,\n busy: List[Tuple[datetime, datetime]],\n duration_min: int,\n) -> List[Tuple[datetime, datetime]]:\n busy_sorted = sorted(busy, key=lambda x: x[0])\n merged: List[Tuple[datetime, datetime]] = []\n for s, e in busy_sorted:\n if merged and s \u003c= merged[-1][1]:\n merged[-1] = (merged[-1][0], max(merged[-1][1], e))\n else:\n merged.append((s, e))\n\n free: List[Tuple[datetime, datetime]] = []\n cursor = day_start\n for bs, be in merged:\n if cursor \u003c bs:\n gap = (bs - cursor).total_seconds() / 60\n if gap >= duration_min:\n free.append((cursor, bs))\n cursor = max(cursor, be)\n if cursor \u003c day_end:\n gap = (day_end - cursor).total_seconds() / 60\n if gap >= duration_min:\n free.append((cursor, day_end))\n return free\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='查询多人共同空闲时段'\n )\n parser.add_argument(\n '--users', required=True, help='用户 ID 列表,逗号分隔'\n )\n parser.add_argument(\n '--date', required=True, help='查询日期 YYYY-MM-DD'\n )\n parser.add_argument(\n '--duration', type=int, default=60,\n help='会议时长(分钟),默认 60',\n )\n parser.add_argument(\n '--start-hour', type=int, default=9,\n help='工作日开始小时,默认 9',\n )\n parser.add_argument(\n '--end-hour', type=int, default=18,\n help='工作日结束小时,默认 18',\n )\n parser.add_argument(\n '--dry-run', action='store_true', help='仅显示命令'\n )\n args = parser.parse_args()\n\n try:\n date = datetime.strptime(args.date, '%Y-%m-%d')\n except ValueError:\n print('错误:日期格式应为 YYYY-MM-DD')\n sys.exit(1)\n\n day_start = date.replace(\n hour=args.start_hour, tzinfo=TZ\n )\n day_end = date.replace(hour=args.end_hour, tzinfo=TZ)\n\n data = run_dws([\n 'calendar', 'busy', 'search',\n '--users', args.users,\n '--start', fmt_iso(day_start),\n '--end', fmt_iso(day_end),\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n return\n\n busy = parse_busy_intervals(data)\n free = find_free_slots(day_start, day_end, busy, args.duration)\n\n users_list = args.users.split(',')\n print(f\"\\n🕐 空闲时段查询 ({args.date})\")\n print(f\" 参与人: {len(users_list)} 人\")\n print(f\" 会议时长: {args.duration} 分钟\")\n print(f\" 工作时间: {args.start_hour}:00 ~ \"\n f\"{args.end_hour}:00\")\n print('=' * 50)\n\n if not free:\n print(' ❌ 该日无共同空闲时段')\n return\n\n print(f\"\\n✅ 找到 {len(free)} 个可用时段:\\n\")\n for i, (s, e) in enumerate(free, 1):\n gap_min = int((e - s).total_seconds() / 60)\n label = '⭐ 推荐' if i == 1 else f' 备选{i-1}'\n print(f\" {label} {s.strftime('%H:%M')} ~ \"\n f\"{e.strftime('%H:%M')} ({gap_min}分钟)\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5759,"content_sha256":"e88ff6683045f67227d1e37c8d3a1d6d5c10fd95ea50edf222fc8093317fd440"},{"filename":"scripts/calendar_schedule_meeting.py","content":"#!/usr/bin/env python3\n\"\"\"\n一键创建日程 + 添加参与者 + 搜索并预定空闲会议室\n\n用法:\n python calendar_schedule_meeting.py \\\n --title \"Q1 复盘会\" \\\n --start \"2026-03-15T14:00\" \\\n --end \"2026-03-15T15:00\" \\\n --users userId1,userId2 \\\n --book-room\n\n python calendar_schedule_meeting.py --dry-run \\\n --title \"测试\" --start \"2026-03-15T14:00\" --end \"2026-03-15T15:00\"\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime, timedelta, timezone\nfrom typing import List, Dict, Any, Optional\n\nTZ = timezone(timedelta(hours=8))\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return {'dry_run': True}\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\" ✗ 错误:{result.stderr.strip()}\")\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\" ✗ 错误:{e}\")\n return None\n\n\ndef normalize_time(time_str: str) -> str:\n for fmt in ('%Y-%m-%dT%H:%M', '%Y-%m-%d %H:%M',\n '%Y-%m-%dT%H:%M:%S'):\n try:\n dt = datetime.strptime(time_str, fmt)\n dt = dt.replace(tzinfo=TZ)\n return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')\n except ValueError:\n continue\n if '+' in time_str or time_str.endswith('Z'):\n return time_str\n raise ValueError(f\"无法解析时间:{time_str}\")\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='一键创建日程 + 添加参与者 + 预定会议室'\n )\n parser.add_argument('--title', required=True, help='日程标题')\n parser.add_argument('--start', required=True, help='开始时间')\n parser.add_argument('--end', required=True, help='结束时间')\n parser.add_argument('--desc', default='', help='日程描述')\n parser.add_argument('--users', default='', help='参与者 userId')\n parser.add_argument(\n '--book-room', action='store_true', help='自动预定会议室'\n )\n parser.add_argument(\n '--dry-run', action='store_true', help='仅显示命令'\n )\n args = parser.parse_args()\n\n try:\n start_iso = normalize_time(args.start)\n end_iso = normalize_time(args.end)\n except ValueError as e:\n print(f\"错误:{e}\")\n sys.exit(1)\n\n print('📅 创建日程...')\n create_args = [\n 'calendar', 'event', 'create',\n '--title', args.title,\n '--start', start_iso,\n '--end', end_iso,\n '--format', 'json',\n ]\n if args.desc:\n create_args.extend(['--desc', args.desc])\n\n result = run_dws(create_args, dry_run=args.dry_run)\n if not result:\n sys.exit(1)\n\n event_id = None\n if not args.dry_run and isinstance(result, dict):\n event_id = result.get('eventId') or result.get('id')\n print(f\" ✓ 日程已创建\" +\n (f\" (eventId: {event_id})\" if event_id else \"\"))\n\n if args.users and event_id:\n print('\\n👥 添加参与者...')\n r = run_dws([\n 'calendar', 'participant', 'add',\n '--event', event_id,\n '--users', args.users,\n '--format', 'json',\n ], dry_run=args.dry_run)\n if r:\n print(f\" ✓ 已添加参与者: {args.users}\")\n elif args.users and args.dry_run:\n run_dws([\n 'calendar', 'participant', 'add',\n '--event', '\u003cEVENT_ID>',\n '--users', args.users,\n '--format', 'json',\n ], dry_run=True)\n\n if args.book_room:\n print('\\n🏢 搜索空闲会议室...')\n rooms_data = run_dws([\n 'calendar', 'room', 'search',\n '--start', start_iso,\n '--end', end_iso,\n '--available',\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if not args.dry_run and rooms_data:\n rooms = (rooms_data if isinstance(rooms_data, list)\n else rooms_data.get('rooms', []))\n if rooms:\n room = rooms[0]\n room_id = room.get('roomId') or room.get('id')\n room_name = room.get('roomName') or room.get('name')\n print(f\" 找到空闲会议室: {room_name}\")\n if event_id and room_id:\n r = run_dws([\n 'calendar', 'room', 'add',\n '--event', event_id,\n '--rooms', str(room_id),\n '--format', 'json',\n ], dry_run=args.dry_run)\n if r:\n print(f\" ✓ 已预定: {room_name}\")\n else:\n print(' ⚠ 该时段无空闲会议室')\n\n print('\\n✅ 完成!')\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5075,"content_sha256":"add59c4f6fb35090c441482e5c4761aab69f1774cdc2608ff44c6e7eead4961e"},{"filename":"scripts/calendar_today_agenda.py","content":"#!/usr/bin/env python3\n\"\"\"\n查看今天/明天/本周的日程安排\n\n用法:\n python calendar_today_agenda.py # 今天\n python calendar_today_agenda.py today # 今天\n python calendar_today_agenda.py tomorrow # 明天\n python calendar_today_agenda.py week # 本周\n python calendar_today_agenda.py --dry-run # 仅显示命令\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nfrom datetime import datetime, timedelta, timezone\nfrom typing import List, Dict, Any, Optional\n\nTZ = timezone(timedelta(hours=8))\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef get_range(scope: str):\n now = datetime.now(TZ)\n today = now.replace(hour=0, minute=0, second=0, microsecond=0)\n if scope == 'today':\n return today, today + timedelta(days=1)\n elif scope == 'tomorrow':\n t = today + timedelta(days=1)\n return t, t + timedelta(days=1)\n elif scope == 'week':\n ws = today - timedelta(days=today.weekday())\n return ws, ws + timedelta(days=7)\n return today, today + timedelta(days=1)\n\n\ndef fmt_iso(dt: datetime) -> str:\n return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')\n\n\ndef fmt_time(iso_str: str) -> str:\n if not iso_str:\n return '??:??'\n try:\n for fmt in ('%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S'):\n try:\n dt = datetime.strptime(iso_str, fmt)\n return dt.strftime('%H:%M')\n except ValueError:\n continue\n return iso_str[:16]\n except Exception:\n return iso_str[:16]\n\n\ndef main():\n dry_run = '--dry-run' in sys.argv\n args = [a for a in sys.argv[1:] if a != '--dry-run']\n scope = args[0] if args else 'today'\n if scope not in ('today', 'tomorrow', 'week'):\n print(__doc__)\n sys.exit(1)\n\n start, end = get_range(scope)\n data = run_dws([\n 'calendar', 'event', 'list',\n '--start', fmt_iso(start),\n '--end', fmt_iso(end),\n '--format', 'json',\n ], dry_run=dry_run)\n if dry_run:\n return\n\n events = []\n if isinstance(data, list):\n events = data\n elif isinstance(data, dict):\n events = data.get('events', data.get('result', []))\n\n label = {'today': '今天', 'tomorrow': '明天', 'week': '本周'\n }.get(scope, scope)\n print(f\"\\n📅 {label}日程 ({start.strftime('%m-%d')} ~ \"\n f\"{end.strftime('%m-%d')})\")\n print('=' * 50)\n\n if not events:\n print(' ✅ 暂无日程,自由安排!')\n return\n\n for e in events:\n title = e.get('summary') or e.get('title', '无标题')\n s = e.get('start', {})\n ed = e.get('end', {})\n start_t = fmt_time(\n s.get('dateTime', '') if isinstance(s, dict) else str(s)\n )\n end_t = fmt_time(\n ed.get('dateTime', '') if isinstance(ed, dict) else str(ed)\n )\n loc = e.get('location', {})\n loc_str = (loc.get('displayName', '')\n if isinstance(loc, dict) else str(loc or ''))\n line = f\" 🕐 {start_t}-{end_t} {title}\"\n if loc_str:\n line += f\" 📍{loc_str}\"\n print(line)\n\n print(f\"\\n合计: {len(events)} 场日程\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3840,"content_sha256":"6d96bc2359405f0998fdd4e94bcfcabd2af37a705d7e1389af38c9837e46dc5c"},{"filename":"scripts/chat_export_messages.py","content":"#!/usr/bin/env python3\n\"\"\"\n导出群聊消息到 JSON 文件(从指定时间点拉取)\n\n用法:\n python chat_export_messages.py \\\n --group \u003copenconversation_id> \\\n --time \"2026-03-10 00:00:00\" \\\n --output messages.json\n\n python chat_export_messages.py \\\n --query \"项目冲刺\" \\\n --time \"2026-03-10 00:00:00\" \\\n --no-forward --limit 100\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=120\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef search_group(\n query: str, dry_run: bool = False,\n) -> Optional[str]:\n data = run_dws([\n 'chat', 'search',\n '--query', query, '--format', 'json',\n ], dry_run=dry_run)\n if dry_run:\n return '\u003cCONV_ID>'\n if not data:\n return None\n groups = (data if isinstance(data, list)\n else data.get('items', data.get('groups', [])))\n if not groups:\n print(f\"未找到群聊: {query}\")\n return None\n g = groups[0]\n name = g.get('title') or g.get('name', '未知')\n conv_id = g.get('openConversationId') or g.get('id')\n print(f\" 找到群聊: {name} ({conv_id})\")\n return conv_id\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='导出群聊消息到 JSON'\n )\n parser.add_argument('--group', help='群聊 openconversation_id')\n parser.add_argument('--query', help='按群名搜索')\n parser.add_argument(\n '--time', required=True,\n help='起始时间 yyyy-MM-dd HH:mm:ss',\n )\n parser.add_argument(\n '--no-forward', action='store_true',\n help='拉给定时间之前的消息 (默认拉给定时间之后)',\n )\n parser.add_argument(\n '--limit', type=int, default=0,\n help='返回条数 (不传则不限制)',\n )\n parser.add_argument('--output', default='', help='输出文件')\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n conv_id = args.group\n if not conv_id:\n if not args.query:\n print('错误:需要 --group 或 --query 参数')\n sys.exit(1)\n print(f'🔍 搜索群聊: {args.query}')\n conv_id = search_group(args.query, args.dry_run)\n if not conv_id and not args.dry_run:\n sys.exit(1)\n\n print(f'📥 拉取消息 (起始: {args.time})...')\n cmd_args = [\n 'chat', 'message', 'list',\n '--group', conv_id or '\u003cCONV_ID>',\n '--time', args.time,\n '--format', 'json',\n ]\n if args.no_forward:\n cmd_args.append('--forward=false')\n if args.limit > 0:\n cmd_args.extend(['--limit', str(args.limit)])\n data = run_dws(cmd_args, dry_run=args.dry_run)\n\n if args.dry_run:\n return\n if not data:\n print('未拉取到消息')\n return\n\n messages = (data if isinstance(data, list)\n else data.get('messages', data.get('result', [])))\n\n if args.output:\n with open(args.output, 'w', encoding='utf-8') as f:\n json.dump(messages, f, ensure_ascii=False, indent=2)\n print(f\" ✓ 已导出 {len(messages)} 条消息到 {args.output}\")\n else:\n for m in messages:\n sender = m.get('senderNick') or m.get('sender', '未知')\n text = m.get('text') or m.get('content', '')\n time_str = m.get('createAt') or m.get('time', '')\n print(f\" [{time_str}] {sender}: {text[:80]}\")\n print(f\"\\n合计: {len(messages)} 条消息\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4138,"content_sha256":"b3711a944cf6c4c4d68fbf9e31aa03d73a7d1634815ed97d0b92372e58c61b69"},{"filename":"scripts/chat_history_with_user.py","content":"#!/usr/bin/env python3\n\"\"\"\n查询与某人的单聊聊天记录\n\n用法:\n python chat_history_with_user.py --name \"张三\" --time \"2026-03-10 00:00:00\"\n python chat_history_with_user.py --user \u003cuserId> --time \"2026-03-10 00:00:00\" --limit 50\n python chat_history_with_user.py --name \"张三\" --time \"2026-03-01 00:00:00\" --output history.json\n\n工作流:\n 1. 通过 --name 搜索通讯录,获取 userId(或直接传 --user)\n 2. 调用 chat message list --user \u003cuserId> 拉取单聊消息\n 3. 输出到终端或导出为 JSON 文件\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n \"\"\"执行 dws 命令并解析 JSON 输出\"\"\"\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=120\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef search_user(\n name: str, dry_run: bool = False,\n) -> Optional[str]:\n \"\"\"按关键词搜索用户,返回第一个匹配的 userId\"\"\"\n data = run_dws([\n 'contact', 'user', 'search',\n '--keyword', name, '--format', 'json',\n ], dry_run=dry_run)\n if dry_run:\n return '\u003cUSER_ID>'\n if not data:\n return None\n # 解析返回结构\n users = data\n if isinstance(data, dict):\n users = (data.get('result', [])\n or data.get('users', [])\n or data.get('list', []))\n if not users or not isinstance(users, list):\n print(f\"未找到用户: {name}\")\n return None\n u = users[0]\n user_name = u.get('name') or u.get('nick', '未知')\n user_id = u.get('userId') or u.get('userid', '')\n print(f\" 找到用户: {user_name} ({user_id})\")\n return user_id\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='查询与某人的单聊聊天记录'\n )\n group = parser.add_mutually_exclusive_group(required=True)\n group.add_argument('--name', help='按姓名搜索用户')\n group.add_argument('--user', help='直接指定 userId')\n parser.add_argument(\n '--time', required=True,\n help='起始时间 yyyy-MM-dd HH:mm:ss',\n )\n parser.add_argument(\n '--no-forward', action='store_true',\n help='拉给定时间之前的消息 (默认拉给定时间之后)',\n )\n parser.add_argument(\n '--limit', type=int, default=0,\n help='返回条数 (不传则不限制)',\n )\n parser.add_argument('--output', default='', help='导出到 JSON 文件')\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n # 1. 获取 userId\n user_id = args.user\n if not user_id:\n print(f'🔍 搜索用户: {args.name}')\n user_id = search_user(args.name, args.dry_run)\n if not user_id and not args.dry_run:\n sys.exit(1)\n\n # 2. 拉取单聊消息\n print(f'📥 拉取与 {user_id} 的聊天记录 (起始: {args.time})...')\n cmd_args = [\n 'chat', 'message', 'list',\n '--user', user_id or '\u003cUSER_ID>',\n '--time', args.time,\n '--format', 'json',\n ]\n if args.no_forward:\n cmd_args.append('--forward=false')\n if args.limit > 0:\n cmd_args.extend(['--limit', str(args.limit)])\n data = run_dws(cmd_args, dry_run=args.dry_run)\n\n if args.dry_run:\n return\n if not data:\n print('未拉取到消息')\n return\n\n # 3. 输出结果\n messages = (data if isinstance(data, list)\n else data.get('messages', data.get('result', [])))\n\n if args.output:\n with open(args.output, 'w', encoding='utf-8') as f:\n json.dump(messages, f, ensure_ascii=False, indent=2)\n print(f\" ✓ 已导出 {len(messages)} 条消息到 {args.output}\")\n else:\n for m in messages:\n sender = m.get('senderNick') or m.get('sender', '未知')\n text = m.get('text') or m.get('content', '')\n time_str = m.get('createAt') or m.get('time', '')\n print(f\" [{time_str}] {sender}: {text[:80]}\")\n print(f\"\\n合计: {len(messages)} 条消息\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4602,"content_sha256":"18d89719cf049fba9caf99f27806d2b31c133dfd2c725f5dcea8d9e63d4f3cd1"},{"filename":"scripts/contact_dept_members.py","content":"#!/usr/bin/env python3\n\"\"\"\n按部门名称搜索并列出所有成员(自动 deptId 解析)\n\n用法:\n python contact_dept_members.py --keyword \"技术部\"\n python contact_dept_members.py --keyword \"产品\" --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='按部门名称搜索并列出所有成员'\n )\n parser.add_argument(\n '--keyword', required=True, help='部门名称关键词'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n print(f'🔍 搜索部门: {args.keyword}')\n dept_data = run_dws([\n 'contact', 'dept', 'search',\n '--keyword', args.keyword, '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n run_dws([\n 'contact', 'dept', 'list-members',\n '--ids', '\u003cDEPT_ID>', '--format', 'json',\n ], dry_run=True)\n return\n\n if not dept_data:\n print('未找到匹配部门')\n sys.exit(1)\n\n depts = (dept_data if isinstance(dept_data, list)\n else dept_data.get('result', dept_data.get('items', [])))\n if not depts:\n print('未找到匹配部门')\n sys.exit(1)\n\n for dept in depts:\n dept_id = dept.get('id') or dept.get('deptId')\n dept_name = dept.get('name') or dept.get('deptName', '未知')\n if not dept_id:\n continue\n\n print(f\"\\n📂 {dept_name} (ID: {dept_id})\")\n print('-' * 40)\n\n members_data = run_dws([\n 'contact', 'dept', 'list-members',\n '--ids', str(dept_id), '--format', 'json',\n ])\n if not members_data:\n print(' 无法获取成员列表')\n continue\n\n members = (members_data if isinstance(members_data, list)\n else members_data.get('result',\n members_data.get('userlist', [])))\n if not members:\n print(' (暂无成员)')\n continue\n\n for m in members:\n name = m.get('name') or m.get('userName', '未知')\n title = m.get('title') or m.get('position', '')\n uid = m.get('userId') or m.get('userid', '')\n line = f\" 👤 {name}\"\n if title:\n line += f\" ({title})\"\n if uid:\n line += f\" [ID: {uid}]\"\n print(line)\n\n print(f\" 共 {len(members)} 人\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3182,"content_sha256":"941c7f7f551bd08bd09564a2f6f4a7595355f69a64c18cf4a3fedaa6749f1a87"},{"filename":"scripts/doc_create_and_write.py","content":"#!/usr/bin/env python3\n\"\"\"\n在指定目录创建文档并写入 Markdown 内容(一键完成)\n\n用法:\n python doc_create_and_write.py \\\n --name \"项目周报\" \\\n --content \"# 本周总结\\n\\n## 完成事项\\n- 任务A\"\n\n python doc_create_and_write.py \\\n --name \"会议纪要\" \\\n --content-file notes.md\n\n python doc_create_and_write.py \\\n --name \"知识库文档\" --content \"# 内容\" --folder FOLDER_ID\n\n python doc_create_and_write.py --name \"test\" --content \"hello\" --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom pathlib import Path\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return {'dry_run': True}\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\" ✗ 错误:{result.stderr.strip()}\")\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\" ✗ 错误:{e}\")\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='创建文档并写入内容'\n )\n parser.add_argument('--name', required=True, help='文档名称')\n parser.add_argument('--content', default='', help='Markdown 内容')\n parser.add_argument('--content-file', default='', help='内容文件')\n parser.add_argument('--folder', default='', help='目标文件夹 ID 或 URL')\n parser.add_argument('--workspace', default='', help='目标知识库 ID')\n parser.add_argument(\n '--mode', default='append', choices=['overwrite', 'append'],\n help='写入模式: overwrite=覆盖, append=追加 (默认 append)',\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n content = args.content\n if args.content_file:\n p = Path(args.content_file)\n if not p.exists():\n print(f\"错误:文件不存在: {p}\")\n sys.exit(1)\n content = p.read_text(encoding='utf-8')\n if not content:\n print('错误:需要 --content 或 --content-file')\n sys.exit(1)\n if len(content) > 50000:\n print('警告:内容较长,可能需要分段写入')\n\n create_args = ['doc', 'create', '--name', args.name, '--format', 'json']\n if args.folder:\n create_args.extend(['--folder', args.folder])\n if args.workspace:\n create_args.extend(['--workspace', args.workspace])\n\n print(f'\\n📝 创建文档: {args.name}')\n create_data = run_dws(create_args, dry_run=args.dry_run)\n\n node_id = None\n if not args.dry_run:\n if not create_data:\n sys.exit(1)\n node_id = (create_data.get('nodeId')\n or create_data.get('dentryUuid')\n or create_data.get('id', ''))\n print(f\" ✓ 文档已创建 (ID: {node_id})\")\n\n mode_label = '追加' if args.mode == 'append' else '覆盖'\n print(f'\\n✍️ 写入内容 (模式: {mode_label})...')\n write_data = run_dws([\n 'doc', 'update',\n '--node', node_id or '\u003cNODE_ID>',\n '--markdown', content,\n '--mode', args.mode,\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if write_data:\n print(f\" ✓ 内容已写入 ({len(content)} 字符)\")\n print('\\n✅ 完成!')\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3609,"content_sha256":"7a175c29d4104f5aad433505a7efad6603f6fe7414c561600387b017446f383a"},{"filename":"scripts/drive_tree_list.py","content":"#!/usr/bin/env python3\n\"\"\"\n递归列出钉盘目录树结构(可指定深度)\n\n用法:\n python drive_tree_list.py # 列出根目录\n python drive_tree_list.py --depth 2 # 递归 2 层\n python drive_tree_list.py --parent-id \u003cid> # 指定目录\n python drive_tree_list.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef list_dir(\n parent_id: str = '', dry_run: bool = False,\n) -> list:\n cmd_args = [\n 'drive', 'list', '--max', '100', '--format', 'json',\n ]\n if parent_id:\n cmd_args.extend(['--parent-id', parent_id])\n data = run_dws(cmd_args, dry_run=dry_run)\n if not data:\n return []\n if isinstance(data, list):\n return data\n return data.get('items', data.get('dentryList', []))\n\n\ndef print_tree(\n items: list, depth: int, max_depth: int,\n prefix: str = '', dry_run: bool = False,\n):\n for i, item in enumerate(items):\n is_last = (i == len(items) - 1)\n connector = '└── ' if is_last else '├── '\n name = item.get('name') or item.get('fileName', '?')\n item_type = item.get('type') or item.get('dentryType', '')\n is_dir = str(item_type).lower() in (\n 'folder', 'directory', '1', 'FOLDER'\n )\n icon = '📁' if is_dir else '📄'\n size_str = ''\n size = item.get('size') or item.get('fileSize')\n if size and not is_dir:\n size = int(size)\n if size > 1024 * 1024:\n size_str = f\" ({size / 1024 / 1024:.1f}MB)\"\n elif size > 1024:\n size_str = f\" ({size / 1024:.1f}KB)\"\n else:\n size_str = f\" ({size}B)\"\n\n print(f\"{prefix}{connector}{icon} {name}{size_str}\")\n\n if is_dir and depth \u003c max_depth:\n child_prefix = prefix + (' ' if is_last else '│ ')\n dentry_id = (item.get('dentryUuid')\n or item.get('id', ''))\n if dentry_id:\n children = list_dir(dentry_id, dry_run=dry_run)\n print_tree(\n children, depth + 1, max_depth,\n child_prefix, dry_run,\n )\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='递归列出钉盘目录树'\n )\n parser.add_argument(\n '--parent-id', default='', help='起始目录 ID'\n )\n parser.add_argument(\n '--depth', type=int, default=1,\n help='递归深度 (默认 1, 最大 5)',\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n args.depth = min(args.depth, 5)\n\n root_name = args.parent_id or '我的文件'\n print(f\"📁 {root_name}\")\n\n items = list_dir(args.parent_id, dry_run=args.dry_run)\n if args.dry_run:\n return\n if not items:\n print(' (空目录)')\n return\n\n print_tree(items, 0, args.depth, '', args.dry_run)\n print(f\"\\n共 {len(items)} 个项目 (根目录)\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3714,"content_sha256":"62d12285994d161c9d69eb81f1a40915f4ff88af92c0bc9eca84576d88cdff0e"},{"filename":"scripts/finance_daily_cashflow.py","content":"#!/usr/bin/env python3\n\"\"\"\n查看指定日期现金日报\n\n用法:\n python finance_daily_cashflow.py # 今天\n python finance_daily_cashflow.py --date 2026-03-10\n python finance_daily_cashflow.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='查看现金日报'\n )\n parser.add_argument(\n '--date', default='', help='日期 YYYY-MM-DD (默认今天)'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n date_str = args.date or datetime.now().strftime('%Y-%m-%d')\n\n print(f'💰 现金日报 ({date_str})\\n')\n data = run_dws([\n 'finance', 'journal', 'daily',\n '--date', date_str,\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n return\n if not data:\n print('未查到现金日报')\n return\n\n print('=' * 50)\n print(json.dumps(data, ensure_ascii=False, indent=2))\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":1773,"content_sha256":"bf4dc292c1ab3ed33f5c589bcca40a85084e9f4c37f4360de42b0f27d7d6140b"},{"filename":"scripts/finance_expense_flow.py","content":"#!/usr/bin/env python3\n\"\"\"\n完整报销流程:搜索供应商 → 搜索类别 → 创建付款单\n\n用法:\n python finance_expense_flow.py \\\n --amount 5000 \\\n --supplier \"华为\" \\\n --category \"差旅\" \\\n --category-type expense\n\n python finance_expense_flow.py --dry-run --amount 1000\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return {'dry_run': True}\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\" ✗ 错误:{result.stderr.strip()}\")\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\" ✗ 错误:{e}\")\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='完整报销流程'\n )\n parser.add_argument(\n '--amount', required=True, help='报销金额'\n )\n parser.add_argument(\n '--supplier', default='', help='供应商名称关键词'\n )\n parser.add_argument(\n '--category', default='', help='费用类别关键词'\n )\n parser.add_argument(\n '--category-type', default='expense',\n choices=['income', 'expense'],\n )\n parser.add_argument('--tax', default='', help='税额')\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n supplier_code = ''\n category_code = ''\n\n if args.supplier:\n print(f'🔍 搜索供应商: {args.supplier}')\n data = run_dws([\n 'finance', 'supplier', 'search',\n '--keyword', args.supplier,\n '--format', 'json',\n ], dry_run=args.dry_run)\n if not args.dry_run and data:\n items = (data if isinstance(data, list)\n else data.get('result', []))\n if items:\n supplier_code = (items[0].get('code')\n or items[0].get('supplierCode', ''))\n name = items[0].get('name', '')\n print(f\" ✓ 找到: {name} ({supplier_code})\")\n else:\n print(f\" ⚠ 未找到供应商: {args.supplier}\")\n\n if args.category:\n print(f'🔍 搜索费用类别: {args.category}')\n data = run_dws([\n 'finance', 'category', 'search',\n '--type', args.category_type,\n '--keyword', args.category,\n '--format', 'json',\n ], dry_run=args.dry_run)\n if not args.dry_run and data:\n items = (data if isinstance(data, list)\n else data.get('result', []))\n if items:\n category_code = (items[0].get('code')\n or items[0].get('categoryCode', ''))\n name = items[0].get('name', '')\n print(f\" ✓ 找到: {name} ({category_code})\")\n else:\n print(f\" ⚠ 未找到类别: {args.category}\")\n\n print(f'\\n💰 创建付款单 (金额: {args.amount})')\n cmd_args = [\n 'finance', 'receipt', 'create',\n '--amount', args.amount,\n '--format', 'json',\n ]\n if supplier_code:\n cmd_args.extend(['--supplier-code', supplier_code])\n if category_code:\n cmd_args.extend(['--category-code', category_code])\n if args.tax:\n cmd_args.extend(['--tax', args.tax])\n\n result = run_dws(cmd_args, dry_run=args.dry_run)\n if result:\n print(f\" ✓ 付款单已创建\")\n else:\n print(f\" ✗ 创建失败\")\n sys.exit(1)\n\n print('\\n✅ 报销流程完成!')\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3928,"content_sha256":"3d0725dfce676094fa09754df8d2be419c91f378ea3d7486098c01165a780f9e"},{"filename":"scripts/import_records.py","content":"#!/usr/bin/env python3\n\"\"\"\n从 CSV / JSON 批量导入记录到钉钉 AI 表格(新版 schema)\n\n用法:\n python import_records.py \u003cbaseId> \u003ctableId> data.csv [batch_size]\n python import_records.py \u003cbaseId> \u003ctableId> data.json [batch_size]\n\n说明:\n- CSV 表头默认视为 fieldId\n- JSON 支持两种格式:\n 1. [{\"cells\": {\"fldxxx\": \"value\"}}, ...]\n 2. [{\"fldxxx\": \"value\"}, ...] # 会自动包装成 cells\n\"\"\"\n\nimport sys\nimport csv\nimport json\nimport subprocess\nimport os\nimport re\nfrom pathlib import Path\nfrom typing import Union, List, Dict, Any, Optional, Tuple\n\nJsonData = Union[List[Any], Dict[str, Any]]\nRecordDict = Dict[str, str]\n\nMAX_FILE_SIZE = 50 * 1024 * 1024\nALLOWED_CSV_EXTENSIONS = ['.csv']\nALLOWED_JSON_EXTENSIONS = ['.json']\nRESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}

钉钉全产品 Skill 通过 命令管理钉钉产品能力。 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 - 不要猜测字段名/参数值,操作前必须先查询确认 严格要求 (MUST DO) - 所有命令必须加 以获取可解析输出 - 删除操作前必须加 并和用户确认 - 单次批量操作不超过 100 条记录 - 所有命令必须 严格遵循 对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) 产品总览 | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| | | AI表格:表格/数据表/字段/记录增删改查/模板搜索 | aitable.md | | | 日历:日程/参与者/会议室/闲忙查询 | calendar.md | | | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | contact.md | | | 文档:搜索/浏览/读取…

)\nMAX_RECORDS_PER_BATCH = 100\nDEFAULT_BATCH_SIZE = 50\n\n\ndef resolve_safe_path(\n path: str, allowed_root: Optional[str] = None\n) -> Path:\n if allowed_root is None:\n allowed_root = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd())\n allowed_root = Path(allowed_root).resolve()\n target_path = (\n Path(path).resolve()\n if Path(path).is_absolute()\n else (Path.cwd() / path).resolve()\n )\n try:\n target_path.relative_to(allowed_root)\n return target_path\n except ValueError:\n raise ValueError(\n f\"路径超出允许范围:{path}\\n\"\n f\"目标路径:{target_path}\\n\"\n f\"允许根目录:{allowed_root}\\n\"\n f\"提示:设置 OPENCLAW_WORKSPACE 环境变量或确保文件在工作目录内\"\n )\n\n\ndef validate_resource_id(resource_id: str) -> bool:\n return bool(\n resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip())\n )\n\n\ndef validate_file_extension(\n filename: str, allowed_extensions: list\n) -> bool:\n return any(filename.lower().endswith(ext) for ext in allowed_extensions)\n\n\ndef safe_csv_load(\n file_path: Path, max_size: int = MAX_FILE_SIZE\n) -> List[RecordDict]:\n file_size = file_path.stat().st_size\n if file_size > max_size:\n raise ValueError(\n f\"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)\"\n )\n with open(file_path, 'r', encoding='utf-8', newline='') as f:\n return list(csv.DictReader(f))\n\n\ndef safe_json_load(\n file_path: Path, max_size: int = MAX_FILE_SIZE\n) -> JsonData:\n file_size = file_path.stat().st_size\n if file_size > max_size:\n raise ValueError(\n f\"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)\"\n )\n with open(file_path, 'r', encoding='utf-8') as f:\n return json.load(f)\n\n\ndef sanitize_record_value(\n value: Any,\n) -> Optional[Union[str, int, float, bool, list, dict]]:\n if value is None:\n return None\n if isinstance(value, (bool, int, float, list, dict)):\n return value\n if not isinstance(value, str):\n return value\n if not value.strip():\n return None\n\n value = value.strip()\n if value.lower() == 'true':\n return True\n if value.lower() == 'false':\n return False\n\n try:\n if '.' in value:\n return float(value)\n return int(value)\n except ValueError:\n return value\n\n\ndef normalize_record(record: Dict[str, Any]) -> Dict[str, Any]:\n if 'cells' in record and isinstance(record['cells'], dict):\n cells = record['cells']\n else:\n cells = record\n normalized = {}\n for key, value in cells.items():\n sanitized = sanitize_record_value(value)\n if sanitized is not None:\n normalized[key] = sanitized\n return {'cells': normalized}\n\n\ndef validate_record(\n record: Dict[str, Any], headers: List[str]\n) -> Tuple[bool, str]:\n if not isinstance(record, dict):\n return False, '记录必须是对象'\n normalized = normalize_record(record)\n cells = normalized.get('cells', {})\n if not cells or not isinstance(cells, dict):\n return False, '记录必须包含非空 cells 对象'\n return True, ''\n\n\ndef run_dws(args: List[str]) -> Optional[Dict[str, Any]]:\n if not args:\n print('错误:空命令')\n return None\n cmd = ['dws'] + args\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=120\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\")\n return None\n try:\n return json.loads(result.stdout)\n except json.JSONDecodeError as e:\n print(f\"无法解析响应:{result.stdout[:200]}...\")\n print(f\"JSON 解析错误:{e}\")\n return None\n except subprocess.TimeoutExpired:\n print('错误:命令执行超时(120 秒)')\n return None\n except FileNotFoundError:\n print('错误:未找到 dws 命令,请确认已安装')\n return None\n\n\ndef import_from_csv(\n base_id: str, table_id: str, csv_file: str,\n batch_size: int = DEFAULT_BATCH_SIZE,\n) -> bool:\n try:\n safe_path = resolve_safe_path(csv_file)\n except ValueError as e:\n print(f\"路径验证失败:{e}\")\n return False\n\n if not validate_file_extension(csv_file, ALLOWED_CSV_EXTENSIONS):\n print(f\"错误:只允许 {', '.join(ALLOWED_CSV_EXTENSIONS)} 文件\")\n return False\n if not safe_path.exists():\n print(f\"错误:文件不存在:{safe_path}\")\n return False\n\n try:\n rows = safe_csv_load(safe_path)\n except ValueError as e:\n print(f\"错误:{e}\")\n return False\n except csv.Error as e:\n print(f\"错误:CSV 格式无效:{e}\")\n return False\n\n if not rows:\n print('错误:CSV 文件为空或没有有效数据行')\n return False\n\n records = [\n normalize_record(row)\n for row in rows\n if normalize_record(row)['cells']\n ]\n return import_records(base_id, table_id, records, batch_size)\n\n\ndef import_from_json(\n base_id: str, table_id: str, json_file: str,\n batch_size: int = DEFAULT_BATCH_SIZE,\n) -> bool:\n try:\n safe_path = resolve_safe_path(json_file)\n except ValueError as e:\n print(f\"路径验证失败:{e}\")\n return False\n\n if not validate_file_extension(json_file, ALLOWED_JSON_EXTENSIONS):\n print(f\"错误:只允许 {', '.join(ALLOWED_JSON_EXTENSIONS)} 文件\")\n return False\n if not safe_path.exists():\n print(f\"错误:文件不存在:{safe_path}\")\n return False\n\n try:\n records = safe_json_load(safe_path)\n except ValueError as e:\n print(f\"错误:{e}\")\n return False\n except json.JSONDecodeError as e:\n print(f\"错误:JSON 格式无效:{e}\")\n return False\n\n if not isinstance(records, list) or not records:\n print('错误:JSON 文件必须是非空数组')\n return False\n\n for i, record in enumerate(records):\n valid, error = validate_record(record, [])\n if not valid:\n print(f\"错误:记录 #{i+1} 格式无效:{error}\")\n return False\n\n return import_records(\n base_id, table_id,\n [normalize_record(r) for r in records], batch_size,\n )\n\n\ndef import_records(\n base_id: str, table_id: str,\n records: List[Dict[str, Any]], batch_size: int,\n) -> bool:\n if batch_size \u003c= 0:\n print('错误:batch_size 必须大于 0')\n return False\n if batch_size > MAX_RECORDS_PER_BATCH:\n batch_size = MAX_RECORDS_PER_BATCH\n\n total_batches = (len(records) + batch_size - 1) // batch_size\n success = True\n\n for i in range(0, len(records), batch_size):\n batch = records[i:i + batch_size]\n batch_num = (i // batch_size) + 1\n records_json = json.dumps(batch, ensure_ascii=False)\n result = run_dws([\n 'aitable', 'record', 'create',\n '--base-id', base_id,\n '--table-id', table_id,\n '--records', records_json,\n '--format', 'json',\n ])\n if result:\n print(\n f\"[{batch_num}/{total_batches}] \"\n f\"✓ 已提交 {len(batch)} 条记录\"\n )\n else:\n print(f\"[{batch_num}/{total_batches}] ✗ 导入失败\")\n success = False\n\n return success\n\n\ndef main():\n if len(sys.argv) \u003c 4 or len(sys.argv) > 5:\n print(__doc__)\n print('用法示例:')\n print(\n ' python import_records.py basexxx tablexxx data.csv 50'\n )\n sys.exit(1)\n\n base_id = sys.argv[1]\n table_id = sys.argv[2]\n input_file = sys.argv[3]\n batch_size = (\n int(sys.argv[4]) if len(sys.argv) == 5\n else DEFAULT_BATCH_SIZE\n )\n\n if not validate_resource_id(base_id):\n print('错误:无效的 baseId 格式')\n sys.exit(1)\n if not validate_resource_id(table_id):\n print('错误:无效的 tableId 格式')\n sys.exit(1)\n\n if input_file.lower().endswith('.csv'):\n success = import_from_csv(\n base_id, table_id, input_file, batch_size\n )\n elif input_file.lower().endswith('.json'):\n success = import_from_json(\n base_id, table_id, input_file, batch_size\n )\n else:\n print('错误:仅支持 .csv 或 .json 文件')\n sys.exit(1)\n\n sys.exit(0 if success else 1)\n\n\nif __name__ == '__main__':\n main()\n\n\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9468,"content_sha256":"5b80b90b2596fdbfe03be14d5e672128eca4c15a50a5241b7a73bb18ab2e8b7a"},{"filename":"scripts/mail_send_with_cc.py","content":"#!/usr/bin/env python3\n\"\"\"\n发送带抄送的邮件(自动获取发件地址、校验参数)\n\n用法:\n python mail_send_with_cc.py \\\n --to [email protected] \\\n --cc [email protected],[email protected] \\\n --subject \"周报\" \\\n --body \"本周完成任务A和任务B\"\n\n python mail_send_with_cc.py --dry-run \\\n --to [email protected] --subject \"test\" --body \"hello\"\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport re\nimport argparse\nfrom typing import List, Any, Optional\n\nEMAIL_PATTERN = re.compile(\n r'^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}

钉钉全产品 Skill 通过 命令管理钉钉产品能力。 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 - 不要猜测字段名/参数值,操作前必须先查询确认 严格要求 (MUST DO) - 所有命令必须加 以获取可解析输出 - 删除操作前必须加 并和用户确认 - 单次批量操作不超过 100 条记录 - 所有命令必须 严格遵循 对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) 产品总览 | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| | | AI表格:表格/数据表/字段/记录增删改查/模板搜索 | aitable.md | | | 日历:日程/参与者/会议室/闲忙查询 | calendar.md | | | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | contact.md | | | 文档:搜索/浏览/读取…

\n)\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return {'dry_run': True}\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef validate_emails(emails_str: str) -> bool:\n for email in emails_str.split(','):\n email = email.strip()\n if not EMAIL_PATTERN.match(email):\n print(f\"错误:无效邮箱地址 '{email}'\")\n return False\n return True\n\n\ndef get_my_email(dry_run: bool = False) -> Optional[str]:\n data = run_dws([\n 'mail', 'mailbox', 'list', '--format', 'json',\n ], dry_run=dry_run)\n if dry_run:\n return '\u003cMY_EMAIL>'\n if not data:\n return None\n if isinstance(data, list) and data:\n item = data[0]\n return (item.get('email') or item.get('address')\n if isinstance(item, dict) else str(item))\n if isinstance(data, dict):\n return data.get('email') or data.get('address')\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='发送带抄送的邮件'\n )\n parser.add_argument('--to', required=True, help='收件人')\n parser.add_argument('--cc', default='', help='抄送人')\n parser.add_argument('--subject', required=True, help='标题')\n parser.add_argument('--body', required=True, help='正文')\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n if not validate_emails(args.to):\n sys.exit(1)\n if args.cc and not validate_emails(args.cc):\n sys.exit(1)\n\n print('📬 获取发件邮箱...')\n from_email = get_my_email(dry_run=args.dry_run)\n if not from_email and not args.dry_run:\n print('错误:无法获取发件邮箱')\n sys.exit(1)\n\n cmd_args = [\n 'mail', 'message', 'send',\n '--from', from_email or '\u003cMY_EMAIL>',\n '--to', args.to,\n '--subject', args.subject,\n '--body', args.body,\n '--format', 'json',\n ]\n if args.cc:\n cmd_args.extend(['--cc', args.cc])\n\n print('📤 发送邮件...')\n result = run_dws(cmd_args, dry_run=args.dry_run)\n if result:\n print(f\" ✓ 邮件已发送\")\n print(f\" 收件人: {args.to}\")\n if args.cc:\n print(f\" 抄送: {args.cc}\")\n print(f\" 主题: {args.subject}\")\n else:\n print(' ✗ 发送失败')\n sys.exit(1)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3500,"content_sha256":"26f3934e6a4bdb736c75e5e159c208d75b150a0973deea441343b059c29565b6"},{"filename":"scripts/mail_unread_summary.py","content":"#!/usr/bin/env python3\n\"\"\"\n查询今天未读邮件并汇总(自动获取邮箱地址)\n\n用法:\n python mail_unread_summary.py\n python mail_unread_summary.py --size 30\n python mail_unread_summary.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime, timezone, timedelta\nfrom typing import List, Any, Optional\n\nTZ = timezone(timedelta(hours=8))\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef get_my_email(dry_run: bool = False) -> Optional[str]:\n data = run_dws([\n 'mail', 'mailbox', 'list', '--format', 'json',\n ], dry_run=dry_run)\n if dry_run:\n return '\u003cMY_EMAIL>'\n if not data:\n return None\n if isinstance(data, list) and data:\n item = data[0]\n return (item.get('email') or item.get('address')\n if isinstance(item, dict) else str(item))\n if isinstance(data, dict):\n return data.get('email') or data.get('address')\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='查询今天未读邮件'\n )\n parser.add_argument(\n '--size', type=int, default=20, help='返回数量'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n print('📬 获取邮箱地址...')\n email = get_my_email(dry_run=args.dry_run)\n if not email and not args.dry_run:\n print('错误:无法获取邮箱地址')\n sys.exit(1)\n\n today = datetime.now(TZ).strftime('%Y-%m-%dT00:00:00Z')\n kql = f'isRead:false AND date>{today}'\n\n print(f'🔍 搜索未读邮件...\\n')\n data = run_dws([\n 'mail', 'message', 'search',\n '--email', email or '\u003cMY_EMAIL>',\n '--query', kql,\n '--size', str(args.size),\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n return\n if not data:\n print('未查到邮件')\n return\n\n messages = (data if isinstance(data, list)\n else data.get('items', data.get('messages', [])))\n\n print(f\"📧 今日未读邮件\")\n print('=' * 50)\n if not messages:\n print(' ✅ 收件箱清空,没有未读邮件!')\n return\n\n for m in messages:\n subj = m.get('subject', '(无主题)')\n sender = m.get('from', {})\n sender_name = (sender.get('name') or sender.get('email', '未知')\n if isinstance(sender, dict) else str(sender))\n print(f\" 📩 {subj}\")\n print(f\" 发件人: {sender_name}\")\n\n print(f\"\\n合计: {len(messages)} 封未读邮件\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3212,"content_sha256":"04614351c0de07a8bbdccc88d27dbf4abf23d2e495aa9df0a786dfbb166a8409"},{"filename":"scripts/minutes_extract_todos.py","content":"#!/usr/bin/env python3\n\"\"\"\n从听记中提取所有待办事项并汇总\n\n用法:\n python minutes_extract_todos.py # 最近 5 条听记\n python minutes_extract_todos.py --max 10 # 最近 10 条\n python minutes_extract_todos.py --id \u003cuuid> # 指定听记\n python minutes_extract_todos.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='从听记中提取待办事项'\n )\n parser.add_argument('--max', type=int, default=5)\n parser.add_argument('--id', default='', help='指定听记 UUID')\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n uuids_with_titles = []\n if args.id:\n uuids_with_titles = [(args.id, args.id)]\n else:\n print('🎙️ 获取听记列表...')\n data = run_dws([\n 'minutes', 'list', 'mine',\n '--max', str(args.max),\n '--format', 'json',\n ], dry_run=args.dry_run)\n if args.dry_run:\n run_dws([\n 'minutes', 'get', 'todos',\n '--id', '\u003cTASK_UUID>', '--format', 'json',\n ], dry_run=True)\n return\n if not data:\n return\n items = (data if isinstance(data, list)\n else data.get('result', []))\n uuids_with_titles = [\n (it.get('taskUuid') or it.get('id', ''),\n it.get('title') or it.get('name', '无标题'))\n for it in items\n ]\n\n all_todos = []\n for uuid, title in uuids_with_titles:\n print(f\" 提取待办: {title}\")\n todos_data = run_dws([\n 'minutes', 'get', 'todos',\n '--id', uuid, '--format', 'json',\n ])\n if not todos_data:\n continue\n items = (todos_data if isinstance(todos_data, list)\n else todos_data.get('result',\n todos_data.get('todos', [])))\n for t in items:\n t['_source'] = title\n all_todos.extend(items)\n\n print(f\"\\n📋 听记待办汇总\")\n print('=' * 50)\n\n if not all_todos:\n print(' ✅ 暂无待办事项')\n return\n\n for t in all_todos:\n content = (t.get('content') or t.get('text')\n or t.get('title', ''))\n source = t.get('_source', '')\n print(f\" • {content}\")\n if source:\n print(f\" 来自: {source}\")\n\n print(f\"\\n合计: {len(all_todos)} 条待办\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3228,"content_sha256":"e17aede5a29c75b56547734f29b144b261b077aa7882f43b40f42b3e49724bff"},{"filename":"scripts/minutes_recent_summary.py","content":"#!/usr/bin/env python3\n\"\"\"\n获取最近 N 条听记的 AI 摘要并合并输出\n\n用法:\n python minutes_recent_summary.py # 最近 5 条\n python minutes_recent_summary.py --max 10 # 最近 10 条\n python minutes_recent_summary.py --output summary.md\n python minutes_recent_summary.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='获取最近听记的 AI 摘要'\n )\n parser.add_argument(\n '--max', type=int, default=5, help='获取条数 (默认 5)'\n )\n parser.add_argument(\n '--output', default='', help='输出到 Markdown 文件'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n print('🎙️ 获取听记列表...')\n list_data = run_dws([\n 'minutes', 'list', 'mine',\n '--max', str(args.max),\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n run_dws([\n 'minutes', 'get', 'summary',\n '--id', '\u003cTASK_UUID>', '--format', 'json',\n ], dry_run=True)\n return\n\n if not list_data:\n print('未找到听记')\n return\n\n items = (list_data if isinstance(list_data, list)\n else list_data.get('result', []))\n if not items:\n print('暂无听记')\n return\n\n output_lines = [f\"# 最近 {len(items)} 条听记摘要\\n\"]\n for i, item in enumerate(items, 1):\n uuid = item.get('taskUuid') or item.get('id', '')\n title = item.get('title') or item.get('name', '无标题')\n print(f\" [{i}/{len(items)}] 获取摘要: {title}\")\n\n summary_data = run_dws([\n 'minutes', 'get', 'summary',\n '--id', uuid, '--format', 'json',\n ])\n summary_text = ''\n if summary_data:\n if isinstance(summary_data, str):\n summary_text = summary_data\n elif isinstance(summary_data, dict):\n summary_text = (summary_data.get('summary')\n or summary_data.get('content')\n or json.dumps(summary_data,\n ensure_ascii=False))\n\n output_lines.append(f\"## {i}. {title}\\n\")\n if summary_text:\n output_lines.append(f\"{summary_text}\\n\")\n else:\n output_lines.append(\"(暂无摘要)\\n\")\n\n full_output = '\\n'.join(output_lines)\n\n if args.output:\n with open(args.output, 'w', encoding='utf-8') as f:\n f.write(full_output)\n print(f\"\\n✓ 已输出到 {args.output}\")\n else:\n print('\\n' + full_output)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3404,"content_sha256":"fd18822d906f9cb4a764b11b108bfd28dc6519148e7595830f00ffe5aa7b4d36"},{"filename":"scripts/oa_batch_approve.py","content":"#!/usr/bin/env python3\n\"\"\"\n批量同意/拒绝待审批项(含安全确认)\n\n用法:\n python oa_batch_approve.py --action approve --days 7\n python oa_batch_approve.py --action reject --remark \"不符合要求\"\n python oa_batch_approve.py --action approve --instance-ids id1,id2\n python oa_batch_approve.py --dry-run --action approve\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime, timedelta\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return {'dry_run': True}\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\" ✗ 错误:{result.stderr.strip()}\")\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\" ✗ 错误:{e}\")\n return None\n\n\ndef to_iso(dt: datetime) -> str:\n return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='批量同意/拒绝审批'\n )\n parser.add_argument(\n '--action', required=True,\n choices=['approve', 'reject'], help='审批动作',\n )\n parser.add_argument(\n '--remark', default='', help='审批意见'\n )\n parser.add_argument('--days', type=int, default=7)\n parser.add_argument('--instance-ids', default='')\n parser.add_argument(\n '--yes', action='store_true', help='跳过确认'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n instance_ids: List[str] = []\n if args.instance_ids:\n instance_ids = [x.strip() for x in\n args.instance_ids.split(',') if x.strip()]\n else:\n now = datetime.now()\n start = now - timedelta(days=args.days)\n data = run_dws([\n 'oa', 'approval', 'list-pending',\n '--start', to_iso(start),\n '--end', to_iso(now),\n '--format', 'json',\n ], dry_run=args.dry_run)\n if not args.dry_run and data:\n items = (data if isinstance(data, list)\n else data.get('result', []))\n instance_ids = [\n item.get('processInstanceId') or item.get('id')\n for item in items\n if item.get('processInstanceId') or item.get('id')\n ]\n\n if not instance_ids and not args.dry_run:\n print('✅ 没有待处理的审批')\n return\n\n action_label = '同意' if args.action == 'approve' else '拒绝'\n count = len(instance_ids) if instance_ids else '?'\n print(f\"\\n⚠️ 即将 {action_label} {count} 条审批\")\n if not args.yes and not args.dry_run:\n confirm = input('确认执行?(y/N): ').strip().lower()\n if confirm != 'y':\n print('已取消')\n return\n\n success, fail = 0, 0\n for i, inst_id in enumerate(instance_ids or ['\u003cINST_ID>'], 1):\n tasks_data = run_dws([\n 'oa', 'approval', 'tasks',\n '--instance-id', inst_id,\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n task_id = None\n if not args.dry_run and tasks_data:\n task_ids = (tasks_data if isinstance(tasks_data, list)\n else tasks_data.get('result', []))\n if task_ids:\n task_id = (task_ids[0] if isinstance(task_ids[0], str)\n else task_ids[0].get('taskId', ''))\n\n cmd_args = [\n 'oa', 'approval', args.action,\n '--instance-id', inst_id,\n '--task-id', task_id or '\u003cTASK_ID>',\n '--format', 'json',\n ]\n if args.remark:\n cmd_args.extend(['--remark', args.remark])\n\n result = run_dws(cmd_args, dry_run=args.dry_run)\n if result:\n print(f\" ✓ [{i}/{count}] {inst_id} → {action_label}\")\n success += 1\n else:\n print(f\" ✗ [{i}/{count}] {inst_id}\")\n fail += 1\n\n print(f\"\\n完成: 成功 {success}, 失败 {fail}\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4349,"content_sha256":"15b3f7c0eb526bb4ba3321909c40aedaf1a65a9b019968629dd543114fa596ac"},{"filename":"scripts/oa_pending_review.py","content":"#!/usr/bin/env python3\n\"\"\"\n查看待我审批列表 + 逐条显示详情(自动时间戳计算)\n\n用法:\n python oa_pending_review.py # 最近 7 天\n python oa_pending_review.py --days 30 # 最近 30 天\n python oa_pending_review.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime, timedelta\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef to_iso(dt: datetime) -> str:\n return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='查看待我审批列表'\n )\n parser.add_argument(\n '--days', type=int, default=7, help='查询天数 (默认 7)'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n now = datetime.now()\n start = now - timedelta(days=args.days)\n\n print(f\"📋 查询待审批 (最近 {args.days} 天)...\")\n data = run_dws([\n 'oa', 'approval', 'list-pending',\n '--start', to_iso(start),\n '--end', to_iso(now),\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n run_dws([\n 'oa', 'approval', 'detail',\n '--instance-id', '\u003cINSTANCE_ID>',\n '--format', 'json',\n ], dry_run=True)\n return\n\n if not data:\n print('未查到待审批')\n return\n\n instances = (data if isinstance(data, list)\n else data.get('result', data.get('items', [])))\n if not instances:\n print('✅ 暂无待审批事项')\n return\n\n print(f\"\\n🔔 待审批列表 ({len(instances)} 条)\")\n print('=' * 50)\n\n for i, inst in enumerate(instances, 1):\n inst_id = (inst.get('processInstanceId')\n or inst.get('id', ''))\n title = inst.get('title') or inst.get('name', '无标题')\n status = inst.get('status') or inst.get('result', '')\n create_time = inst.get('createTime', '')\n if isinstance(create_time, (int, float)):\n create_time = datetime.fromtimestamp(\n create_time / 1000\n ).strftime('%Y-%m-%d %H:%M')\n\n print(f\"\\n [{i}] {title}\")\n print(f\" 状态: {status} 创建: {create_time}\")\n print(f\" ID: {inst_id}\")\n\n detail = run_dws([\n 'oa', 'approval', 'detail',\n '--instance-id', inst_id,\n '--format', 'json',\n ])\n if detail and isinstance(detail, dict):\n forms = detail.get('formComponentValues', [])\n if forms:\n print(f\" --- 表单内容 ---\")\n for f in forms[:5]:\n name = f.get('name', '')\n value = f.get('value', '')\n if value:\n print(f\" {name}: {value[:60]}\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3510,"content_sha256":"a109503b24b7946a82267d28070050eb3a7cfe06894142cd5781fc52ee0befdc"},{"filename":"scripts/report_inbox_today.py","content":"#!/usr/bin/env python3\n\"\"\"\n查看今天收到的日志列表及详情\n\n用法:\n python report_inbox_today.py\n python report_inbox_today.py --days 3 # 最近 3 天\n python report_inbox_today.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport argparse\nfrom datetime import datetime, timedelta\nfrom typing import List, Any, Optional\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef to_iso(dt: datetime) -> str:\n return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='查看收到的日志'\n )\n parser.add_argument(\n '--days', type=int, default=1, help='查询天数 (默认 1)'\n )\n parser.add_argument('--dry-run', action='store_true')\n args = parser.parse_args()\n\n now = datetime.now()\n start = now - timedelta(days=args.days)\n start = start.replace(hour=0, minute=0, second=0)\n\n label = '今天' if args.days == 1 else f'最近 {args.days} 天'\n print(f'📓 查看{label}收到的日志...\\n')\n\n data = run_dws([\n 'report', 'list',\n '--start', to_iso(start),\n '--end', to_iso(now),\n '--cursor', '0',\n '--size', '20',\n '--format', 'json',\n ], dry_run=args.dry_run)\n\n if args.dry_run:\n return\n if not data:\n print('未查到日志')\n return\n\n reports = (data if isinstance(data, list)\n else data.get('result', data.get('reports', [])))\n if not reports:\n print(' ✅ 暂无收到的日志')\n return\n\n print(f\"📓 {label}日志 ({len(reports)} 条)\")\n print('=' * 50)\n\n for r in reports:\n rid = r.get('reportId') or r.get('id', '')\n creator = r.get('creatorName') or r.get('creator', '未知')\n template = r.get('templateName') or r.get('template', '')\n create_time = r.get('createTime', '')\n if isinstance(create_time, (int, float)):\n create_time = datetime.fromtimestamp(\n create_time / 1000\n ).strftime('%Y-%m-%d %H:%M')\n\n print(f\"\\n 📝 {template or '日志'} - {creator}\")\n print(f\" 时间: {create_time}\")\n print(f\" ID: {rid}\")\n\n if rid:\n detail = run_dws([\n 'report', 'detail',\n '--report-id', rid, '--format', 'json',\n ])\n if detail and isinstance(detail, dict):\n contents = detail.get('contents', [])\n for c in contents[:3]:\n key = c.get('key') or c.get('title', '')\n val = c.get('value') or c.get('content', '')\n if key and val:\n print(f\" {key}: {str(val)[:60]}\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3339,"content_sha256":"e089c9a2a36e16fb8a31a424a6c12726c32c4d218357cdee2c2856cb1d00f777"},{"filename":"scripts/todo_batch_create.py","content":"#!/usr/bin/env python3\n\"\"\"\n从 JSON 文件批量创建待办(含优先级、截止时间、执行者)\n\n用法:\n python todo_batch_create.py todos.json\n python todo_batch_create.py todos.json --dry-run\n\ntodos.json 格式:\n [\n {\"title\": \"修复线上Bug\", \"executors\": \"userId1,userId2\", \"priority\": 40},\n {\"title\": \"写周报\", \"executors\": \"userId1\", \"due\": \"2026-03-15\"},\n {\"title\": \"代码评审\", \"executors\": \"userId1\"}\n ]\n\n字段说明:\n- title: 待办标题 (必填)\n- executors: 执行者 userId,多人逗号分隔 (必填)\n- priority: 优先级 10=低/20=普通/30=较高/40=紧急 (可选)\n- due: 截止日期 YYYY-MM-DD 或毫秒时间戳 (可选)\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport re\nfrom datetime import datetime\nfrom pathlib import Path\nfrom typing import List, Dict, Any, Optional\n\nALLOWED_PRIORITIES = {10, 20, 30, 40}\nDATE_PATTERN = re.compile(r'^\\d{4}-\\d{2}-\\d{2}

钉钉全产品 Skill 通过 命令管理钉钉产品能力。 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 - 不要猜测字段名/参数值,操作前必须先查询确认 严格要求 (MUST DO) - 所有命令必须加 以获取可解析输出 - 删除操作前必须加 并和用户确认 - 单次批量操作不超过 100 条记录 - 所有命令必须 严格遵循 对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) 产品总览 | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| | | AI表格:表格/数据表/字段/记录增删改查/模板搜索 | aitable.md | | | 日历:日程/参与者/会议室/闲忙查询 | calendar.md | | | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | contact.md | | | 文档:搜索/浏览/读取…

)\nMAX_FILE_SIZE = 10 * 1024 * 1024\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Dict[str, Any]]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return {'dry_run': True}\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\" ✗ 错误:{result.stderr.strip()}\")\n return None\n return json.loads(result.stdout)\n except subprocess.TimeoutExpired:\n print(' ✗ 命令执行超时', file=sys.stderr)\n return None\n except (json.JSONDecodeError, FileNotFoundError) as e:\n print(f\" ✗ 错误:{e}\", file=sys.stderr)\n return None\n\n\ndef parse_due(due_value) -> Optional[str]:\n if not due_value:\n return None\n due_str = str(due_value)\n if due_str.isdigit() and len(due_str) >= 10:\n return due_str\n if DATE_PATTERN.match(due_str):\n dt = datetime.strptime(due_str, '%Y-%m-%d')\n dt = dt.replace(hour=23, minute=59, second=59)\n return str(int(dt.timestamp() * 1000))\n print(f\" ⚠ 无法解析截止时间:{due_value},跳过\")\n return None\n\n\ndef validate_todo(item: Dict[str, Any], idx: int) -> bool:\n if not isinstance(item, dict):\n print(f\" ✗ #{idx+1} 不是有效对象\")\n return False\n if not item.get('title', '').strip():\n print(f\" ✗ #{idx+1} 缺少 title\")\n return False\n if not item.get('executors', '').strip():\n print(f\" ✗ #{idx+1} 缺少 executors\")\n return False\n priority = item.get('priority')\n if priority is not None and int(priority) not in ALLOWED_PRIORITIES:\n print(f\" ✗ #{idx+1} 无效优先级:{priority}\")\n return False\n return True\n\n\ndef main():\n dry_run = '--dry-run' in sys.argv\n args = [a for a in sys.argv[1:] if a != '--dry-run']\n if not args:\n print(__doc__)\n sys.exit(1)\n\n file_path = Path(args[0])\n if not file_path.exists():\n print(f\"错误:文件不存在:{file_path}\")\n sys.exit(1)\n if file_path.stat().st_size > MAX_FILE_SIZE:\n print(f\"错误:文件过大 (限制 {MAX_FILE_SIZE // 1024}KB)\")\n sys.exit(1)\n\n with open(file_path, 'r', encoding='utf-8') as f:\n todos = json.load(f)\n if not isinstance(todos, list) or not todos:\n print('错误:JSON 文件必须是非空数组')\n sys.exit(1)\n\n for i, item in enumerate(todos):\n if not validate_todo(item, i):\n sys.exit(1)\n\n print(f\"📋 准备创建 {len(todos)} 条待办\\n\")\n success, fail = 0, 0\n for i, item in enumerate(todos):\n title = item['title'].strip()\n cmd_args = [\n 'todo', 'task', 'create',\n '--title', title,\n '--executors', item['executors'].strip(),\n '--format', 'json',\n ]\n priority = item.get('priority')\n if priority is not None:\n cmd_args.extend(['--priority', str(int(priority))])\n due = parse_due(item.get('due'))\n if due:\n cmd_args.extend(['--due', due])\n\n result = run_dws(cmd_args, dry_run=dry_run)\n if result:\n print(f\" ✓ [{i+1}/{len(todos)}] {title}\")\n success += 1\n else:\n print(f\" ✗ [{i+1}/{len(todos)}] {title}\")\n fail += 1\n\n print(f\"\\n完成: 成功 {success}, 失败 {fail}\")\n sys.exit(0 if fail == 0 else 1)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":4508,"content_sha256":"a6f2976179aba2c9e3c8aca3ec4904baf817cf718d24dc36b3f110b7b21a436a"},{"filename":"scripts/todo_daily_summary.py","content":"#!/usr/bin/env python3\n\"\"\"\n查询今天/明天/本周未完成的待办并汇总输出\n\n用法:\n python todo_daily_summary.py # 默认查今天\n python todo_daily_summary.py today # 今天的待办\n python todo_daily_summary.py tomorrow # 明天的待办\n python todo_daily_summary.py week # 本周的待办\n python todo_daily_summary.py --dry-run # 仅显示将执行的命令\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nfrom datetime import datetime, timedelta\nfrom typing import List, Dict, Any, Optional\n\nPRIORITY_MAP = {10: '低', 20: '普通', 30: '较高', 40: '紧急'}\nPAGE_SIZE = 50\nMAX_PAGES = 10\n\n\ndef run_dws(args: List[str], dry_run: bool = False) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except subprocess.TimeoutExpired:\n print('错误:命令执行超时', file=sys.stderr)\n return None\n except (json.JSONDecodeError, FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef get_date_range(scope: str):\n now = datetime.now()\n today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)\n if scope == 'today':\n return today_start, today_start + timedelta(days=1)\n elif scope == 'tomorrow':\n tmr = today_start + timedelta(days=1)\n return tmr, tmr + timedelta(days=1)\n elif scope == 'week':\n week_start = today_start - timedelta(days=today_start.weekday())\n return week_start, week_start + timedelta(days=7)\n return today_start, today_start + timedelta(days=1)\n\n\ndef fetch_all_todos(\n dry_run: bool = False,\n) -> List[Dict[str, Any]]:\n all_todos: List[Dict[str, Any]] = []\n for page in range(1, MAX_PAGES + 1):\n data = run_dws([\n 'todo', 'task', 'list',\n '--page', str(page),\n '--size', str(PAGE_SIZE),\n '--status', 'false',\n '--format', 'json',\n ], dry_run=dry_run)\n if dry_run:\n return []\n if not data:\n break\n items = data if isinstance(data, list) else data.get(\n 'result', data.get('todoCards', [])\n )\n if not items or not isinstance(items, list):\n break\n all_todos.extend(items)\n if len(items) \u003c PAGE_SIZE:\n break\n return all_todos\n\n\ndef format_priority(p) -> str:\n try:\n return PRIORITY_MAP.get(int(p), str(p))\n except (ValueError, TypeError):\n return '普通'\n\n\ndef format_due(due_ms) -> str:\n if not due_ms:\n return '无截止时间'\n try:\n dt = datetime.fromtimestamp(int(due_ms) / 1000)\n return dt.strftime('%Y-%m-%d %H:%M')\n except (ValueError, TypeError, OSError):\n return str(due_ms)\n\n\ndef filter_by_due(\n todos: List[Dict[str, Any]], start: datetime, end: datetime,\n) -> List[Dict[str, Any]]:\n start_ms = int(start.timestamp() * 1000)\n end_ms = int(end.timestamp() * 1000)\n result = []\n for t in todos:\n due = t.get('dueTime') or t.get('due')\n if not due:\n result.append(t)\n continue\n try:\n due_val = int(due)\n if start_ms \u003c= due_val \u003c end_ms:\n result.append(t)\n except (ValueError, TypeError):\n result.append(t)\n return result\n\n\ndef print_summary(\n todos: List[Dict[str, Any]], scope: str,\n start: datetime, end: datetime,\n):\n scope_label = {\n 'today': '今天', 'tomorrow': '明天', 'week': '本周',\n }.get(scope, scope)\n print(f\"\\n📋 {scope_label}未完成待办 \"\n f\"({start.strftime('%m-%d')} ~ {end.strftime('%m-%d')})\")\n print('=' * 50)\n if not todos:\n print(' ✅ 暂无待办,轻松一下!')\n return\n urgent = [t for t in todos if format_priority(\n t.get('priority')) == '紧急']\n if urgent:\n print(f\"\\n🔴 紧急 ({len(urgent)} 条)\")\n for t in urgent:\n title = t.get('subject') or t.get('title', '无标题')\n print(f\" • {title} ⏰ {format_due(t.get('dueTime'))}\")\n normal = [t for t in todos if t not in urgent]\n if normal:\n print(f\"\\n📌 其他 ({len(normal)} 条)\")\n for t in normal:\n title = t.get('subject') or t.get('title', '无标题')\n pri = format_priority(t.get('priority'))\n print(f\" • [{pri}] {title} ⏰ {format_due(t.get('dueTime'))}\")\n print(f\"\\n合计: {len(todos)} 条待办\")\n\n\ndef main():\n dry_run = '--dry-run' in sys.argv\n args = [a for a in sys.argv[1:] if a != '--dry-run']\n scope = args[0] if args else 'today'\n if scope not in ('today', 'tomorrow', 'week'):\n print(__doc__)\n sys.exit(1)\n start, end = get_date_range(scope)\n todos = fetch_all_todos(dry_run=dry_run)\n if dry_run:\n return\n filtered = filter_by_due(todos, start, end)\n print_summary(filtered, scope, start, end)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5307,"content_sha256":"056a52a85e779494266431b1ceb16c2b5b99cc55cc5f3b94b721f6735bfd8720"},{"filename":"scripts/todo_overdue_check.py","content":"#!/usr/bin/env python3\n\"\"\"\n扫描已过截止时间但未完成的待办,输出逾期清单\n\n用法:\n python todo_overdue_check.py\n python todo_overdue_check.py --dry-run\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nfrom datetime import datetime\nfrom typing import List, Dict, Any, Optional\n\nPAGE_SIZE = 50\nMAX_PAGES = 10\nPRIORITY_MAP = {10: '低', 20: '普通', 30: '较高', 40: '紧急'}\n\n\ndef run_dws(\n args: List[str], dry_run: bool = False,\n) -> Optional[Any]:\n cmd = ['dws'] + args\n if dry_run:\n print(f\"[dry-run] {' '.join(cmd)}\")\n return None\n try:\n result = subprocess.run(\n cmd, capture_output=True, text=True, timeout=60\n )\n if result.returncode != 0:\n print(f\"错误:{result.stderr.strip()}\", file=sys.stderr)\n return None\n return json.loads(result.stdout)\n except (subprocess.TimeoutExpired, json.JSONDecodeError,\n FileNotFoundError) as e:\n print(f\"错误:{e}\", file=sys.stderr)\n return None\n\n\ndef fetch_all_undone(dry_run: bool = False) -> List[Dict[str, Any]]:\n all_todos: List[Dict[str, Any]] = []\n for page in range(1, MAX_PAGES + 1):\n data = run_dws([\n 'todo', 'task', 'list',\n '--page', str(page), '--size', str(PAGE_SIZE),\n '--status', 'false', '--format', 'json',\n ], dry_run=dry_run)\n if dry_run or not data:\n break\n items = (data if isinstance(data, list)\n else data.get('result', data.get('todoCards', [])))\n if not items or not isinstance(items, list):\n break\n all_todos.extend(items)\n if len(items) \u003c PAGE_SIZE:\n break\n return all_todos\n\n\ndef find_overdue(todos: List[Dict[str, Any]]) -> List[Dict[str, Any]]:\n now_ms = int(datetime.now().timestamp() * 1000)\n overdue = []\n for t in todos:\n due = t.get('dueTime') or t.get('due')\n if not due:\n continue\n try:\n if int(due) \u003c now_ms:\n overdue.append(t)\n except (ValueError, TypeError):\n continue\n return overdue\n\n\ndef days_overdue(due_ms) -> int:\n now = datetime.now()\n try:\n due_dt = datetime.fromtimestamp(int(due_ms) / 1000)\n return max(0, (now - due_dt).days)\n except (ValueError, TypeError, OSError):\n return 0\n\n\ndef main():\n dry_run = '--dry-run' in sys.argv\n todos = fetch_all_undone(dry_run=dry_run)\n if dry_run:\n return\n\n overdue = find_overdue(todos)\n overdue.sort(\n key=lambda t: int(t.get('dueTime') or t.get('due', 0))\n )\n\n print(f\"\\n⏰ 逾期待办检查 ({datetime.now().strftime('%Y-%m-%d %H:%M')})\")\n print('=' * 50)\n\n if not overdue:\n print(' ✅ 没有逾期待办,继续保持!')\n return\n\n for t in overdue:\n title = t.get('subject') or t.get('title', '无标题')\n due = t.get('dueTime') or t.get('due')\n days = days_overdue(due)\n pri = PRIORITY_MAP.get(\n int(t.get('priority', 20)), '普通'\n )\n due_str = datetime.fromtimestamp(\n int(due) / 1000\n ).strftime('%Y-%m-%d')\n print(f\" 🔴 [{pri}] {title}\")\n print(f\" 截止: {due_str} 逾期: {days} 天\")\n\n print(f\"\\n合计: {len(overdue)} 条逾期待办\")\n sys.exit(1 if overdue else 0)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":3421,"content_sha256":"07a575c6a16e0e0ff8f9ac39804095805a20dc5f8ac8a11bb7154555a965f699"},{"filename":"scripts/upload_attachment.py","content":"#!/usr/bin/env python3\n\"\"\"\n上传附件到钉钉 AI 表格 attachment 字段\n\n完整流程(内部自动执行 3 步):\n 1. dws aitable attachment upload → 获取 uploadUrl + fileToken\n 2. HTTP PUT 上传文件到 OSS\n 3. 返回 fileToken,可直接用于 record create/update\n\n用法:\n python upload_attachment.py \u003cbaseId> \u003cfilePath>\n\n输出 (JSON):\n { \"fileToken\": \"ft_xxx\", \"fileName\": \"report.pdf\", \"size\": 204800 }\n\n然后在 record create/update 中使用:\n dws aitable record create --base-id \u003cBASE_ID> --table-id \u003cTABLE_ID> \\\n --records '[{\"cells\":{\"fldAttachId\":[{\"fileToken\":\"ft_xxx\"}]}}]' --format json\n\"\"\"\n\nimport sys\nimport json\nimport subprocess\nimport os\nimport mimetypes\nimport re\nfrom pathlib import Path\nfrom typing import Optional, Dict, Any\nfrom urllib.request import Request, urlopen\nfrom urllib.error import HTTPError, URLError\n\nRESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}

钉钉全产品 Skill 通过 命令管理钉钉产品能力。 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 - 不要猜测字段名/参数值,操作前必须先查询确认 严格要求 (MUST DO) - 所有命令必须加 以获取可解析输出 - 删除操作前必须加 并和用户确认 - 单次批量操作不超过 100 条记录 - 所有命令必须 严格遵循 对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) 产品总览 | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| | | AI表格:表格/数据表/字段/记录增删改查/模板搜索 | aitable.md | | | 日历:日程/参与者/会议室/闲忙查询 | calendar.md | | | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | contact.md | | | 文档:搜索/浏览/读取…

)\nMAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB\n\n\ndef validate_resource_id(resource_id: str) -> bool:\n return bool(resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip()))\n\n\ndef detect_mime_type(file_path: Path) -> str:\n \"\"\"根据文件扩展名推断 MIME type。\"\"\"\n mime_type, _ = mimetypes.guess_type(str(file_path))\n return mime_type or 'application/octet-stream'\n\n\ndef run_dws(args: list) -> Optional[Dict[str, Any]]:\n \"\"\"调用 dws 命令并返回解析后的 JSON 结果。\"\"\"\n cmd = ['dws'] + args\n try:\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)\n if result.returncode != 0:\n print(f\"错误:dws 命令失败: {result.stderr.strip()}\", file=sys.stderr)\n return None\n try:\n return json.loads(result.stdout)\n except json.JSONDecodeError:\n print(f\"错误:无法解析 dws 响应: {result.stdout[:300]}\", file=sys.stderr)\n return None\n except subprocess.TimeoutExpired:\n print('错误:dws 命令超时(60 秒)', file=sys.stderr)\n return None\n except FileNotFoundError:\n print('错误:未找到 dws 命令,请确认已安装并在 PATH 中', file=sys.stderr)\n return None\n\n\ndef upload_to_oss(upload_url: str, file_path: Path, mime_type: str) -> bool:\n \"\"\"通过 HTTP PUT 上传文件到 OSS。\"\"\"\n file_data = file_path.read_bytes()\n req = Request(upload_url, data=file_data, method='PUT')\n req.add_header('Content-Type', mime_type)\n\n try:\n with urlopen(req, timeout=120) as resp:\n if resp.status == 200:\n return True\n print(f\"错误:OSS 上传失败,HTTP {resp.status}\", file=sys.stderr)\n return False\n except HTTPError as e:\n print(f\"错误:OSS 上传 HTTP 错误 {e.code}: {e.reason}\", file=sys.stderr)\n return False\n except URLError as e:\n print(f\"错误:OSS 上传网络错误: {e.reason}\", file=sys.stderr)\n return False\n\n\ndef upload_attachment(base_id: str, file_path_str: str) -> Optional[Dict[str, Any]]:\n \"\"\"\n 执行完整的附件上传流程:\n 1. prepare_attachment_upload → uploadUrl + fileToken\n 2. PUT 文件到 OSS\n 3. 返回 fileToken 信息\n \"\"\"\n # 验证文件\n file_path = Path(file_path_str).resolve()\n if not file_path.exists():\n print(f\"错误:文件不存在: {file_path}\", file=sys.stderr)\n return None\n if not file_path.is_file():\n print(f\"错误:不是文件: {file_path}\", file=sys.stderr)\n return None\n\n file_size = file_path.stat().st_size\n if file_size \u003c= 0:\n print(\"错误:文件为空\", file=sys.stderr)\n return None\n if file_size > MAX_FILE_SIZE:\n print(f\"错误:文件过大 ({file_size:,} 字节,限制 {MAX_FILE_SIZE:,} 字节)\", file=sys.stderr)\n return None\n\n file_name = file_path.name\n mime_type = detect_mime_type(file_path)\n\n # 步骤 1: prepare_attachment_upload\n print(f\"步骤 1/3: 准备上传 {file_name} ({file_size:,} 字节, {mime_type})...\", file=sys.stderr)\n dws_args = [\n 'aitable', 'attachment', 'upload',\n '--base-id', base_id,\n '--file-name', file_name,\n '--size', str(file_size),\n '--mime-type', mime_type,\n '--format', 'json',\n ]\n result = run_dws(dws_args)\n if not result:\n return None\n\n status = result.get('status', '')\n if status != 'success':\n error = result.get('error', {})\n print(f\"错误:准备上传失败: {error.get('message', json.dumps(error, ensure_ascii=False))}\", file=sys.stderr)\n return None\n\n data = result.get('data', {})\n upload_url = data.get('uploadUrl', '')\n file_token = data.get('fileToken', '')\n\n if not upload_url or not file_token:\n print(f\"错误:返回数据缺少 uploadUrl 或 fileToken: {json.dumps(data, ensure_ascii=False)}\", file=sys.stderr)\n return None\n\n # 步骤 2: PUT 文件到 OSS\n print(f\"步骤 2/3: 上传文件到 OSS...\", file=sys.stderr)\n if not upload_to_oss(upload_url, file_path, mime_type):\n return None\n\n # 步骤 3: 返回 fileToken\n print(f\"步骤 3/3: 上传完成!\", file=sys.stderr)\n output = {\n \"fileToken\": file_token,\n \"fileName\": file_name,\n \"size\": file_size,\n \"mimeType\": mime_type,\n }\n\n return output\n\n\ndef main():\n if len(sys.argv) != 3:\n print(__doc__)\n print('用法:')\n print(' python upload_attachment.py \u003cbaseId> \u003cfilePath>')\n print()\n print('示例:')\n print(' python upload_attachment.py G1DKw2zgV2bEk6PMSBooNxlEVB5r9YAn ./report.pdf')\n print()\n print('然后在 record create 中使用返回的 fileToken:')\n print(' dws aitable record create --base-id \u003cBASE_ID> --table-id \u003cTABLE_ID> \\\\')\n print(' --records \\'[{\"cells\":{\"fldAttachId\":[{\"fileToken\":\"ft_xxx\"}]}}]\\' --format json')\n sys.exit(1)\n\n base_id = sys.argv[1]\n file_path = sys.argv[2]\n\n if not validate_resource_id(base_id):\n print('错误:无效的 baseId 格式', file=sys.stderr)\n sys.exit(1)\n\n result = upload_attachment(base_id, file_path)\n if result is None:\n sys.exit(1)\n\n # 正常输出到 stdout(JSON 格式,方便解析)\n print(json.dumps(result, ensure_ascii=False, indent=2))\n sys.exit(0)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6411,"content_sha256":"312fac63da30a6241d8511716aaa2528b277ed718ef8617497cb0332c408078c"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"钉钉全产品 Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"通过 ","type":"text"},{"text":"dws","type":"text","marks":[{"type":"code_inline"}]},{"text":" 命令管理钉钉产品能力。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"严格禁止 (NEVER DO)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"不要编造 UUID、ID 等标识符,必须从命令返回中提取","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"不要猜测字段名/参数值,操作前必须先查询确认","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"严格要求 (MUST DO)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"所有命令必须加 ","type":"text"},{"text":"--format json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 以获取可解析输出","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"删除操作前必须加 ","type":"text"},{"text":"--yes","type":"text","marks":[{"type":"code_inline"}]},{"text":" 并和用户确认","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"单次批量操作不超过 100 条记录","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"所有命令必须","type":"text"},{"text":"严格遵循","type":"text","marks":[{"type":"strong"}]},{"text":"对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"产品总览","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"产品","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"用途","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"参考文件","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"aitable","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AI表格:表格/数据表/字段/记录增删改查/模板搜索","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"aitable.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/aitable.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"calendar","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"日历:日程/参与者/会议室/闲忙查询","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"calendar.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/calendar.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"contact","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"contact.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/contact.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"doc","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"文档:搜索/浏览/读取/创建/更新文档/文件夹管理/块级编辑","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"doc.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/doc.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"chat","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"群聊:群管理(建群/搜索/成员增删/改群名)/消息(拉取/发送/机器人群发/Webhook)/机器人搜索","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"chat.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/chat.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"todo","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"待办:创建(含优先级/截止时间)/查询/修改/标记完成/删除","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"todo.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/todo.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mail","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"邮箱:查询邮箱/搜索/查看/发送邮件","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mail.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/mail.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"minutes","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AI听记:列表/摘要/转写/关键字/标题修改","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"minutes.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/minutes.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"report","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"日志:收件箱/已发送/模版查看/详情/已读统计","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"report.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/report.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"drive","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"钉盘:浏览文件/元数据/下载/创建文件夹/上传文件","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"drive.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/drive.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ding","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DING消息:发送/撤回(应用内/短信/电话)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ding.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/ding.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"devdoc","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"开放平台文档:搜索开发文档","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"simple.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/simple.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"conference","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"视频会议:预约会议","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"simple.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/simple.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"aiapp","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AI应用:创建/查询/修改AI应用","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"aiapp.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/aiapp.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"live","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"直播:查看直播列表","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"simple.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/simple.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"oa","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OA审批:待处理/详情/同意/拒绝/撤销/记录/已发起/任务","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"oa.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/oa.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"attendance","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"考勤:打卡记录/排班查询","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"attendance.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/attendance.md","title":null}}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"意图判断决策树","type":"text"}]},{"type":"paragraph","content":[{"text":"用户提到\"表格/多维表/AI表格/记录/数据\" → ","type":"text"},{"text":"aitable","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"日程/日历/会议室/约会\" → ","type":"text"},{"text":"calendar","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"通讯录/同事/部门/组织架构\" → ","type":"text"},{"text":"contact","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"文档/知识库/写文档\" → ","type":"text"},{"text":"doc","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"待办/TODO/任务提醒\" → ","type":"text"},{"text":"todo","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"邮件/邮箱\" → ","type":"text"},{"text":"mail","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"听记/会议录音/转写/AI摘要以及用户传入听记URL(如 ","type":"text"},{"text":"https://shanji.dingtalk.com/*","type":"text","marks":[{"type":"code_inline"}]},{"text":")\" → ","type":"text"},{"text":"minutes","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"帮我做/建/生成/生成系统/AI应用/创建应用/智能应用\" → ","type":"text"},{"text":"aiapp","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"DING/紧急消息/电话提醒\" → ","type":"text"},{"text":"ding","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"考勤/打卡/排班\" → ","type":"text"},{"text":"attendance","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"群聊/群消息/群成员/聊天记录/建群/机器人发消息/Webhook/通知\" → ","type":"text"},{"text":"chat","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"审批/OA\" → ","type":"text"},{"text":"oa","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"开发/API/调用错误 文档\" → ","type":"text"},{"text":"devdoc","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到“校招/发布职位/我的候选人” → ","type":"text"},{"text":"ai_sincere_hire","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"视频会议/预约会议\" → ","type":"text"},{"text":"conference","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"直播\" → ","type":"text"},{"text":"live","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"日志/日报/周报/日志统计\" → ","type":"text"},{"text":"report","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"钉盘/文件/网盘/下载文件/上传文件\" → ","type":"text"},{"text":"drive","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"企业信用/工商信息/股东/裁判文书/风险/商标/专利/招投标/联系方式/KP\" → ","type":"text"},{"text":"credit","type":"text","marks":[{"type":"code_inline"}]},{"text":" 用户提到\"法律咨询/法规/案例/法条/判例/法律依据\" → ","type":"text"},{"text":"law","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"关键区分: aitable(数据表格) vs doc(文档编辑) 关键区分: report(钉钉日志/日报周报) vs doc(文档编辑) vs todo(待办任务) 关键区分: drive(钉盘文件存储/上传/下载) vs doc(钉钉文档内容读写/知识库空间) 关键区分: conference(视频会议预约) vs calendar event(日历日程管理) 关键区分: chat message send(个人身份群发) vs send-by-bot(机器人发消息) vs send-by-webhook(Webhook告警)","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"更多易混淆场景及用户表达示例,见 ","type":"text"},{"text":"intent-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/intent-guide.md","title":null}}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"核心流程","type":"text"}]},{"type":"paragraph","content":[{"text":"作为一个智能助手,你的首要任务是","type":"text"},{"text":"理解用户的真实、完整的意图","type":"text","marks":[{"type":"strong"}]},{"text":",而不是简单地执行命令。在选择 ","type":"text"},{"text":"dws","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的产品命令前,必须严格遵循以下三步流程:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"意图分类:首先,判断用户指令的核心 动词/动作 属于哪一类。这比关注名词更重要。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"歧义处理与信息追问:如果用户指令模糊或包含多个产品的关键字,严禁猜测。必须主动向用户追问以澄清意图。这是你作为智能助手而非命令执行器的核心价值。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"精准产品映射:在完成前两步,意图已经清晰后,参考产品总览和意图判断决策树 来选择产品。","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"充分阅读产品参考文件,通过编写代码或直接调用指令实现用户意图。","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"错误处理","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"遇到错误,加 ","type":"text"},{"text":"--verbose","type":"text","marks":[{"type":"code_inline"}]},{"text":" 重试一次","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"仍然失败,报告错误信息给用户","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"禁止自行尝试替代方案","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"如果出现身份校验失败,可以使用 dws auth login 进行登录","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"详细参考 (按需读取)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/products/","type":"text","marks":[{"type":"link","attrs":{"href":"./references/products/","title":null}}]},{"text":" — 各产品命令详细参考","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/intent-guide.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/intent-guide.md","title":null}}]},{"text":" — 意图路由指南(易混淆场景对照)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/global-reference.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/global-reference.md","title":null}}]},{"text":" — 全局标志、认证、输出格式","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/field-rules.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/field-rules.md","title":null}}]},{"text":" — AI表格字段类型规则","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/error-codes.md","type":"text","marks":[{"type":"link","attrs":{"href":"./references/error-codes.md","title":null}}]},{"text":" — 错误码 + 调试流程","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/","type":"text","marks":[{"type":"link","attrs":{"href":"./scripts/","title":null}}]},{"text":" — AI表格批量操作脚本","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"dws","author":"@skillopedia","source":{"stars":114,"repo_name":"dingtalk-wukong-skills","origin_url":"https://github.com/stvlynn/dingtalk-wukong-skills/blob/HEAD/dws/SKILL.md","repo_owner":"stvlynn","body_sha256":"5ac7b497c80d49cc1d3dd6a46f2aa96bbb35a09673d934105055ccf13d71d3f6","cluster_key":"fe9d3a275bb23640edafcedebf9a2c6ce5f7f84be420563f53eb5821aad820a6","clean_bundle":{"format":"clean-skill-bundle-v1","source":"stvlynn/dingtalk-wukong-skills/dws/SKILL.md","attachments":[{"id":"f3cc6ab7-0d26-5c21-ba38-a90dbabbd35b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f3cc6ab7-0d26-5c21-ba38-a90dbabbd35b/attachment.md","path":"references/error-codes.md","size":5078,"sha256":"08cd3c49e88b4e7b198ac5c8e25b6e677f5a1a2d834d00077ba1a32a8e944f9a","contentType":"text/markdown; charset=utf-8"},{"id":"d7a542f4-e67b-58a2-88a6-74463d88dd2b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d7a542f4-e67b-58a2-88a6-74463d88dd2b/attachment.md","path":"references/field-rules.md","size":5065,"sha256":"1c6886f0ebcfaaf1b39fb5d8940595049b02acdef6c7442cce15b753e04815c6","contentType":"text/markdown; charset=utf-8"},{"id":"f802251e-670e-5aa2-ab92-857b0b702841","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f802251e-670e-5aa2-ab92-857b0b702841/attachment.md","path":"references/global-reference.md","size":1814,"sha256":"5236a5e0f2903cf7a8dd95fee091956cc63897ec1a654e4f9ed036f012683914","contentType":"text/markdown; charset=utf-8"},{"id":"3547bd1e-a254-5b8d-b25c-72e335ce990e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3547bd1e-a254-5b8d-b25c-72e335ce990e/attachment.md","path":"references/intent-guide.md","size":7358,"sha256":"bbab1fd8deb1d283458f764cbb9de21eea592e612c18a73c9d4fdc18ec48ad03","contentType":"text/markdown; charset=utf-8"},{"id":"ec27b802-2231-56f0-bc76-7bb348642c40","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ec27b802-2231-56f0-bc76-7bb348642c40/attachment.md","path":"references/products/aiapp.md","size":17021,"sha256":"528fcb852d71b26cddea8b4cfb2c5c49ee5f11e6d1ba5d77056f97a10f5badb0","contentType":"text/markdown; charset=utf-8"},{"id":"c2249a13-ffda-5548-977a-a065b71b2aed","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c2249a13-ffda-5548-977a-a065b71b2aed/attachment.md","path":"references/products/aitable.md","size":16084,"sha256":"641bffcc5d807f6be17dae098de22926a0806415efc5d69819b3ca7e59e93a3d","contentType":"text/markdown; charset=utf-8"},{"id":"cf9dda3e-03af-51e1-8495-86c71465b4d0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cf9dda3e-03af-51e1-8495-86c71465b4d0/attachment.md","path":"references/products/attendance.md","size":3269,"sha256":"0b112c5438815e7b4b6c4eeac077d1a7e9ff3977856c5fcff0cdbe75445e4836","contentType":"text/markdown; charset=utf-8"},{"id":"a687978d-c643-5a3b-a897-de1134a0ec85","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a687978d-c643-5a3b-a897-de1134a0ec85/attachment.md","path":"references/products/calendar.md","size":6624,"sha256":"eae21aa6f036fa3cb269a2dea7f63df918b61dd64bb1605194849790c7b8d5b0","contentType":"text/markdown; charset=utf-8"},{"id":"f3cdbb3b-41ed-52d7-9220-01ef9f9c4fb0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f3cdbb3b-41ed-52d7-9220-01ef9f9c4fb0/attachment.md","path":"references/products/chat.md","size":11092,"sha256":"164b40d68ca3342de5e286f27c925c42a1fef2fa5722c0f4d3b8aad563bc688c","contentType":"text/markdown; charset=utf-8"},{"id":"7901e560-a095-5796-a40e-0a0b4ab5c4e4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7901e560-a095-5796-a40e-0a0b4ab5c4e4/attachment.md","path":"references/products/contact.md","size":3213,"sha256":"d82fd99334108845cc537f5717062d7595656d22ee1615c698373aaf0feb75fc","contentType":"text/markdown; charset=utf-8"},{"id":"9da1c1f6-ff43-50f7-bc97-01d0c1a59215","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9da1c1f6-ff43-50f7-bc97-01d0c1a59215/attachment.md","path":"references/products/ding.md","size":1910,"sha256":"83900a9f441072ef18d47365843b7eb53fbed1c12665af1b42df6a15e84eb41c","contentType":"text/markdown; charset=utf-8"},{"id":"ad798684-b938-53d3-81f8-50afa7393459","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ad798684-b938-53d3-81f8-50afa7393459/attachment.md","path":"references/products/doc.md","size":8787,"sha256":"ade9626c395d1a7869a40619be846dc36c4b044f8f957d579f3b703a5a94d3e0","contentType":"text/markdown; charset=utf-8"},{"id":"82a13708-5713-53d9-9af9-8a91a7972a29","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/82a13708-5713-53d9-9af9-8a91a7972a29/attachment.md","path":"references/products/drive.md","size":5061,"sha256":"1ebe49aefdf2b41ce96f6786f8a578153e88f3bf2b1a4ca2742cf746972f93fa","contentType":"text/markdown; charset=utf-8"},{"id":"6e6f8728-992c-5b09-a723-08acaf2f4d1f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6e6f8728-992c-5b09-a723-08acaf2f4d1f/attachment.md","path":"references/products/mail.md","size":3976,"sha256":"89feb2e5828a0ba1f74ad5060f34027ef558ff331dae7d7e3d4e4d3327473999","contentType":"text/markdown; charset=utf-8"},{"id":"89b469cd-55e1-5e20-9e7d-0a94d8c41590","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/89b469cd-55e1-5e20-9e7d-0a94d8c41590/attachment.md","path":"references/products/minutes.md","size":4884,"sha256":"6a20eeeac0928e8c8be1dffbc3140f082ca2f5f122dab91f61e9bffe9da2f1ef","contentType":"text/markdown; charset=utf-8"},{"id":"17721e8f-c671-5dbc-b09a-6f9ba8460ed8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/17721e8f-c671-5dbc-b09a-6f9ba8460ed8/attachment.md","path":"references/products/oa.md","size":6507,"sha256":"204fe48403f59be8a63fa690ed2deae3816a9479b5110c5960e3d1016fae1bef","contentType":"text/markdown; charset=utf-8"},{"id":"461e7d7a-01c2-5589-aaa7-3e27c968cc9c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/461e7d7a-01c2-5589-aaa7-3e27c968cc9c/attachment.md","path":"references/products/report.md","size":3648,"sha256":"ec464171389ab88a2414225c97b375bf56501ef2ca6af84ef4f0b629abcbd773","contentType":"text/markdown; charset=utf-8"},{"id":"475349c1-5cfd-50cb-b8f9-756ab4ad0a6b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/475349c1-5cfd-50cb-b8f9-756ab4ad0a6b/attachment.md","path":"references/products/simple.md","size":1572,"sha256":"260664cbcead56c63e4f5b7b7a3be57e183c903a90518236174e3a01e6d9b1e4","contentType":"text/markdown; charset=utf-8"},{"id":"68216eb8-0b88-5d0d-a93b-7634c8154e24","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/68216eb8-0b88-5d0d-a93b-7634c8154e24/attachment.md","path":"references/products/todo.md","size":4325,"sha256":"c2edc06162961ba5f1aed0ca53827bdbdb047cc68a0e1b985500af9fef0db892","contentType":"text/markdown; charset=utf-8"},{"id":"9c42df80-efbb-5dce-86b8-2b1f7ad04869","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9c42df80-efbb-5dce-86b8-2b1f7ad04869/attachment.md","path":"references/products/workbench.md","size":793,"sha256":"c37c4ec7f293ffaf55b2e185040046c78dc27537fe645a88b48793eb83429c87","contentType":"text/markdown; charset=utf-8"},{"id":"4a464e42-d67f-540b-bd34-d65c109799da","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4a464e42-d67f-540b-bd34-d65c109799da/attachment.py","path":"scripts/aiapp_create_and_poll.py","size":3891,"sha256":"8318e67b851aa9bfd9981e0e08a859a118e469235d617dab8389db3f6ce72098","contentType":"text/x-python; charset=utf-8"},{"id":"af707687-8759-5d11-998e-3fa0f7b3e172","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/af707687-8759-5d11-998e-3fa0f7b3e172/attachment.py","path":"scripts/attendance_my_record.py","size":2537,"sha256":"de23144ada2bd293388b60d05a56e724fff52d6eb5c66185ce8ae89f094cffe2","contentType":"text/x-python; charset=utf-8"},{"id":"23e1f58c-a79e-5e7b-aca6-7fd5c7053a45","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/23e1f58c-a79e-5e7b-aca6-7fd5c7053a45/attachment.py","path":"scripts/attendance_team_shift.py","size":2507,"sha256":"23309b6d0e69216cde257fa86eb456e39a7f461f94da880c43901e6d4418fa32","contentType":"text/x-python; charset=utf-8"},{"id":"14af6060-e016-50fe-b86a-72e45f44edf4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/14af6060-e016-50fe-b86a-72e45f44edf4/attachment.py","path":"scripts/bot_broadcast.py","size":3363,"sha256":"832e7e59a1e3e6b992422791f0b568bfb311144355ff85d3bd154442ebb75e03","contentType":"text/x-python; charset=utf-8"},{"id":"83b481b1-47dd-5019-9c99-34b87e19ce7b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/83b481b1-47dd-5019-9c99-34b87e19ce7b/attachment.py","path":"scripts/bulk_add_fields.py","size":8062,"sha256":"6ef9e5cfff493844aaafca3e91afc11b2577c623d6b432e907bfa9846c760354","contentType":"text/x-python; charset=utf-8"},{"id":"0b987aaa-905c-5d65-8fe4-baf6a10763ef","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0b987aaa-905c-5d65-8fe4-baf6a10763ef/attachment.py","path":"scripts/calendar_free_slot_finder.py","size":5759,"sha256":"e88ff6683045f67227d1e37c8d3a1d6d5c10fd95ea50edf222fc8093317fd440","contentType":"text/x-python; charset=utf-8"},{"id":"fb35e273-0e19-5a43-b31a-e276e663b1f0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fb35e273-0e19-5a43-b31a-e276e663b1f0/attachment.py","path":"scripts/calendar_schedule_meeting.py","size":5075,"sha256":"add59c4f6fb35090c441482e5c4761aab69f1774cdc2608ff44c6e7eead4961e","contentType":"text/x-python; charset=utf-8"},{"id":"520e9da5-63c2-5eb4-883c-baf31f3e94b4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/520e9da5-63c2-5eb4-883c-baf31f3e94b4/attachment.py","path":"scripts/calendar_today_agenda.py","size":3840,"sha256":"6d96bc2359405f0998fdd4e94bcfcabd2af37a705d7e1389af38c9837e46dc5c","contentType":"text/x-python; charset=utf-8"},{"id":"da09e950-5fa2-5db1-b6cb-f527de7facb5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/da09e950-5fa2-5db1-b6cb-f527de7facb5/attachment.py","path":"scripts/chat_export_messages.py","size":4138,"sha256":"b3711a944cf6c4c4d68fbf9e31aa03d73a7d1634815ed97d0b92372e58c61b69","contentType":"text/x-python; charset=utf-8"},{"id":"e6e226ba-dd6b-512f-84c4-877b6f460862","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e6e226ba-dd6b-512f-84c4-877b6f460862/attachment.py","path":"scripts/chat_history_with_user.py","size":4602,"sha256":"18d89719cf049fba9caf99f27806d2b31c133dfd2c725f5dcea8d9e63d4f3cd1","contentType":"text/x-python; charset=utf-8"},{"id":"6d6ecac3-8d16-522f-a017-d8435e0227b6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6d6ecac3-8d16-522f-a017-d8435e0227b6/attachment.py","path":"scripts/contact_dept_members.py","size":3182,"sha256":"941c7f7f551bd08bd09564a2f6f4a7595355f69a64c18cf4a3fedaa6749f1a87","contentType":"text/x-python; charset=utf-8"},{"id":"8a281099-7d36-5920-b6f0-a334700a16f2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8a281099-7d36-5920-b6f0-a334700a16f2/attachment.py","path":"scripts/doc_create_and_write.py","size":3609,"sha256":"7a175c29d4104f5aad433505a7efad6603f6fe7414c561600387b017446f383a","contentType":"text/x-python; charset=utf-8"},{"id":"cea64c93-ea60-5c35-a6ed-0855165ecb07","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cea64c93-ea60-5c35-a6ed-0855165ecb07/attachment.py","path":"scripts/drive_tree_list.py","size":3714,"sha256":"62d12285994d161c9d69eb81f1a40915f4ff88af92c0bc9eca84576d88cdff0e","contentType":"text/x-python; charset=utf-8"},{"id":"799a03de-e4eb-5979-b41b-a7b4e87fc6d3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/799a03de-e4eb-5979-b41b-a7b4e87fc6d3/attachment.py","path":"scripts/finance_daily_cashflow.py","size":1773,"sha256":"bf4dc292c1ab3ed33f5c589bcca40a85084e9f4c37f4360de42b0f27d7d6140b","contentType":"text/x-python; charset=utf-8"},{"id":"a6fe2a95-d884-59f8-9577-d4a2ad88879c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a6fe2a95-d884-59f8-9577-d4a2ad88879c/attachment.py","path":"scripts/finance_expense_flow.py","size":3928,"sha256":"3d0725dfce676094fa09754df8d2be419c91f378ea3d7486098c01165a780f9e","contentType":"text/x-python; charset=utf-8"},{"id":"21f1cd0b-8d0e-5b4c-8d2d-98cfe2c52dbb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/21f1cd0b-8d0e-5b4c-8d2d-98cfe2c52dbb/attachment.py","path":"scripts/import_records.py","size":9468,"sha256":"5b80b90b2596fdbfe03be14d5e672128eca4c15a50a5241b7a73bb18ab2e8b7a","contentType":"text/x-python; charset=utf-8"},{"id":"a3fc8320-2099-5df9-a67f-2ed58aced82d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a3fc8320-2099-5df9-a67f-2ed58aced82d/attachment.py","path":"scripts/mail_send_with_cc.py","size":3500,"sha256":"26f3934e6a4bdb736c75e5e159c208d75b150a0973deea441343b059c29565b6","contentType":"text/x-python; charset=utf-8"},{"id":"d31183d4-3de8-597b-91f8-17951f46658f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d31183d4-3de8-597b-91f8-17951f46658f/attachment.py","path":"scripts/mail_unread_summary.py","size":3212,"sha256":"04614351c0de07a8bbdccc88d27dbf4abf23d2e495aa9df0a786dfbb166a8409","contentType":"text/x-python; charset=utf-8"},{"id":"0e650afe-00ad-5c2f-b198-79082e54c095","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0e650afe-00ad-5c2f-b198-79082e54c095/attachment.py","path":"scripts/minutes_extract_todos.py","size":3228,"sha256":"e17aede5a29c75b56547734f29b144b261b077aa7882f43b40f42b3e49724bff","contentType":"text/x-python; charset=utf-8"},{"id":"c0b3a9f2-ef5a-598d-9fc9-5972bca89c8e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c0b3a9f2-ef5a-598d-9fc9-5972bca89c8e/attachment.py","path":"scripts/minutes_recent_summary.py","size":3404,"sha256":"fd18822d906f9cb4a764b11b108bfd28dc6519148e7595830f00ffe5aa7b4d36","contentType":"text/x-python; charset=utf-8"},{"id":"0bdccac6-4df1-5347-8b55-980f8826b4aa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0bdccac6-4df1-5347-8b55-980f8826b4aa/attachment.py","path":"scripts/oa_batch_approve.py","size":4349,"sha256":"15b3f7c0eb526bb4ba3321909c40aedaf1a65a9b019968629dd543114fa596ac","contentType":"text/x-python; charset=utf-8"},{"id":"11aef748-62ab-5e86-8995-2985b4e3591c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/11aef748-62ab-5e86-8995-2985b4e3591c/attachment.py","path":"scripts/oa_pending_review.py","size":3510,"sha256":"a109503b24b7946a82267d28070050eb3a7cfe06894142cd5781fc52ee0befdc","contentType":"text/x-python; charset=utf-8"},{"id":"deab62ea-03ee-583e-8905-64777cbe82b8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/deab62ea-03ee-583e-8905-64777cbe82b8/attachment.py","path":"scripts/report_inbox_today.py","size":3339,"sha256":"e089c9a2a36e16fb8a31a424a6c12726c32c4d218357cdee2c2856cb1d00f777","contentType":"text/x-python; charset=utf-8"},{"id":"6e971706-6c6c-5dbc-8b7c-389832272470","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6e971706-6c6c-5dbc-8b7c-389832272470/attachment.py","path":"scripts/todo_batch_create.py","size":4508,"sha256":"a6f2976179aba2c9e3c8aca3ec4904baf817cf718d24dc36b3f110b7b21a436a","contentType":"text/x-python; charset=utf-8"},{"id":"0abc5ee8-02b3-5ba8-8c8e-f30f32ffff93","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0abc5ee8-02b3-5ba8-8c8e-f30f32ffff93/attachment.py","path":"scripts/todo_daily_summary.py","size":5307,"sha256":"056a52a85e779494266431b1ceb16c2b5b99cc55cc5f3b94b721f6735bfd8720","contentType":"text/x-python; charset=utf-8"},{"id":"63bbb417-6abd-5270-b5d1-50e0cdaf3552","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/63bbb417-6abd-5270-b5d1-50e0cdaf3552/attachment.py","path":"scripts/todo_overdue_check.py","size":3421,"sha256":"07a575c6a16e0e0ff8f9ac39804095805a20dc5f8ac8a11bb7154555a965f699","contentType":"text/x-python; charset=utf-8"},{"id":"652b34d9-7ad6-54aa-b853-495c0cfee3d4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/652b34d9-7ad6-54aa-b853-495c0cfee3d4/attachment.py","path":"scripts/upload_attachment.py","size":6411,"sha256":"312fac63da30a6241d8511716aaa2528b277ed718ef8617497cb0332c408078c","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"e6afb41a7cb0e40675ef49eb0c4dda69518fe66f14c9fa01a882921f2f40d507","attachment_count":47,"text_attachments":47,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"dws/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"general","category_label":"General"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"general","import_tag":"clean-skills-v1","cli_version":">=0.2.14","description":"管理钉钉产品能力(AI表格/日历/通讯录/文档/机器人/待办/邮箱/听记/AI应用/审批/日志/钉盘等)。当用户需要操作表格数据、管理日程会议、查询通讯录、发送消息通知、处理审批流程、查看听记摘要、创建应用/系统/管理后台/业务工具、查看日报周报、管理钉盘文件时使用。"}},"renderedAt":1782979748558}

钉钉全产品 Skill 通过 命令管理钉钉产品能力。 严格禁止 (NEVER DO) - 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器) - 不要编造 UUID、ID 等标识符,必须从命令返回中提取 - 不要猜测字段名/参数值,操作前必须先查询确认 严格要求 (MUST DO) - 所有命令必须加 以获取可解析输出 - 删除操作前必须加 并和用户确认 - 单次批量操作不超过 100 条记录 - 所有命令必须 严格遵循 对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开) 产品总览 | 产品 | 用途 | 参考文件 | |-------------------|------------------------------------------------------|----------------------------------------------------------------| | | AI表格:表格/数据表/字段/记录增删改查/模板搜索 | aitable.md | | | 日历:日程/参与者/会议室/闲忙查询 | calendar.md | | | 通讯录:用户查询(当前用户/搜索/详情)/部门查询(搜索/子部门/成员列表) | contact.md | | | 文档:搜索/浏览/读取…