first commit
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: dingtalk-calendar
|
||||
description: 钉钉日历与会议室。Use when 用户说 约会议/查日程/订会议室/查闲忙/加参会人/改期/取消会议/今天的日程/本周日程/共同空闲。不做视频会议发起/邀请入会/会中控制(走 dingtalk-misc)、AI 听记(走 dingtalk-minutes)、待办任务(走 dingtalk-todo)。命令前缀:dws calendar。
|
||||
metadata:
|
||||
cli_version: ">=0.2.14"
|
||||
category: product
|
||||
requires:
|
||||
bins:
|
||||
- dws
|
||||
---
|
||||
|
||||
# 钉钉日历 Skill
|
||||
|
||||
## 前置条件 — 执行操作前必读
|
||||
|
||||
> **CRITICAL — 执行任何 `dws` 操作前,MUST 先用 Read 工具完整读取 [`dingtalk-shared`](../dingtalk-shared/SKILL.md)。**该轻量文件包含全局执行契约、安全底线及 shared references 的按需加载导航;不要预加载其全部 references。
|
||||
|
||||
> 命令参考:[calendar.md](references/calendar.md);剧本:[03-meeting.md](references/03-meeting.md)。
|
||||
|
||||
<!-- VISIBLE_SHORTCUTS_START -->
|
||||
## Shortcuts(无专用脚本/recipe 时优先)
|
||||
|
||||
以下 shortcut 同时进入公开 catalog 与 Runtime Schema。先按本 skill 的意图表、脚本和 recipe 路由:存在精确覆盖该场景的专用脚本/recipe 时按其执行;否则用户意图命中时,shortcut 优先于手写原子命令。命令已选中时直接执行;只在参数或安全语义不确定时读取 Agent leaf Schema(例如 `dws schema --cli-path "calendar +<shortcut>" --compact --format json`),在当前 Cobra flags 不确定时读取 `dws calendar <shortcut> --help`。只有参数映射、接口绑定或 provenance 审计才省略 `--compact`。仅当现有路由和 reference 都无法定位低频能力时,才用 `dws shortcut list --service calendar --format json` 批量发现。
|
||||
|
||||
| Shortcut | 风险 | 适用场景 |
|
||||
|---|---|---|
|
||||
| `dws calendar +agenda` | read | 查询日程列表(不传时间默认查询今天) |
|
||||
| `dws calendar +attendee-list` | read | 查看日程参会人 |
|
||||
| `dws calendar +book` | write | 创建日程,并可按姓名邀请参会人(自动解析 userId,失败自动回滚删除日程) |
|
||||
| `dws calendar +book-list` | read | 查询用户的日历本列表 |
|
||||
| `dws calendar +book-search` | read | 按名称模糊搜索日历本 |
|
||||
| `dws calendar +cancel-event` | high-risk-write | 取消(删除)一个已有日程(删除前先确认它真实存在) |
|
||||
| `dws calendar +conflicts` | read | 检测我某天日程的时间冲突(重叠/双重预订,默认今天) |
|
||||
| `dws calendar +free` | read | 按姓名查询某人在指定时间段内的忙闲状态(自动解析 userId) |
|
||||
| `dws calendar +free-slots` | read | 找我某天工作时段内的空闲时间段(默认今天 09:00-18:00) |
|
||||
| `dws calendar +freebusy` | read | 查询用户 / 会议室闲忙状态(--users 与 --rooms 至少其一) |
|
||||
| `dws calendar +invite` | write | 按姓名把参会人加入已有日程(自动解析 userId 后批量添加) |
|
||||
| `dws calendar +my-free` | read | 查我自己在某时间段的忙闲(默认今天,无需输入姓名) |
|
||||
| `dws calendar +next-event` | read | 查看接下来最近的一个日程(默认扫描未来 7 天) |
|
||||
| `dws calendar +reschedule` | write | 改一个已有日程的时间(只动开始/结束时间,其他字段不变) |
|
||||
| `dws calendar +room-find` | read | 按时间段搜索可用会议室(不传时间默认当前起 1 小时) |
|
||||
| `dws calendar +room-groups` | read | 会议室分组列表 |
|
||||
| `dws calendar +room-search` | read | 按名称模糊搜索会议室(不检查可用性) |
|
||||
| `dws calendar +suggest-time` | read | 按姓名解析多位参与者,推荐大家都有空的可开会时间段(自动解析 userId) |
|
||||
| `dws calendar +today` | read | 列出我今天的日程(自动计算今天的起止时间,无需手动填时间范围) |
|
||||
| `dws calendar +tomorrow` | read | 列出我明天的日程(自动计算明天的起止时间,无需手动填时间范围) |
|
||||
| `dws calendar +week` | read | 列出我本周的日程(自动按周一为周首计算本周起止时间,无需手动填时间范围) |
|
||||
<!-- VISIBLE_SHORTCUTS_END -->
|
||||
|
||||
## 意图表
|
||||
|
||||
| 用户说 | 命令 |
|
||||
|--------|------|
|
||||
| "今天 / 明天 / 本周日程" | `python scripts/calendar_today_agenda.py [today\|tomorrow\|week]` |
|
||||
| "约会议(含参会人 + 会议室)" | `python scripts/calendar_schedule_meeting.py --title "<主题>" --start "<起>" --end "<止>" [--users <ids>] [--book-room]` |
|
||||
| "多人共同空闲" | `python scripts/calendar_free_slot_finder.py --users <ids> --date <yyyy-MM-dd>` |
|
||||
| "查闲忙" | `dws calendar busy search --users <userIds> --start "<ISO>" --end "<ISO>"` |
|
||||
| "加参会人" / "订房" / "取消" | `dws calendar attendee add` / `room add` / `event delete` |
|
||||
|
||||
## 标准 SOP(必遵流程)
|
||||
|
||||
> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 userId/eventId。每条命令必须带 `--format json`,时间参数**必须**是 ISO-8601(如 `2026-07-03T14:00:00+08:00`)。
|
||||
|
||||
### SOP-1 查日程(list-events)
|
||||
|
||||
**触发**:今天/明天/本周日程/我有什么会/某时段日程。
|
||||
|
||||
1. **首选脚本(必须)**:`python scripts/calendar_today_agenda.py today|tomorrow|week`(聚合今日议程)。
|
||||
2. **降级 CLI(必须)**:脚本不可用时 `dws calendar event list --start "<起始ISO>" --end "<结束ISO>" --format json`;不传 `--start/--end` 默认查今天(00:00:00~23:59:59)。`hasMore=true` 用 `--limit`/翻页。
|
||||
3. **解析(必须)**:取真实 `eventId`、`attendees[]`、`start/end`;按需抽取,**禁止**把整段 JSON 原样贴出。
|
||||
|
||||
**禁止**:用 `event list` 替代闲忙查询(查闲忙走 SOP-3)、编造时间窗口、用非 ISO 时间格式。
|
||||
|
||||
### SOP-2 建日程(create-event)
|
||||
|
||||
**触发**:建日程/约会议/加日程。
|
||||
|
||||
1. **解析与会人(必须)**:对每个姓名 `dws aisearch person --query "<姓名>" --dimension name --format json` 取 `userId`,多人逗号拼接。
|
||||
2. **执行(必须)**:`dws calendar event create --title "<主题>" --start "<ISO>" --end "<ISO>" --attendees <userId1,userId2> --format json`(按需加 `--location`/`--desc`/`--rooms`)。
|
||||
3. **验证(必须)**:从返回 `result.id` 取日程 ID(下游参数语义称 `eventId`),再执行 `dws calendar event list --start "<ISO>" --end "<ISO>" --format json` 复核标题、描述和时段。
|
||||
|
||||
**禁止**:跳过与会人 userId 解析直接传姓名、编造会议室 roomId。
|
||||
|
||||
### SOP-3 查闲忙(check-busy)
|
||||
|
||||
**触发**:某人/会议室是否有空/找空闲时段/避免冲突。
|
||||
|
||||
1. **解析对象(必须)**:姓名 → `dws aisearch person --query "<姓名>" --dimension name --format json` 取 `userId`;会议室用 `roomId`。
|
||||
2. **收敛时段(必须)**:`--start`/`--end` **必须**由用户给出或明确收敛;时段不明确**必须先追问**,**禁止**默认全天窗口。
|
||||
3. **执行(必须)**:`dws calendar busy search --users <userId1,userId2> --start "<ISO>" --end "<ISO>" --format json`(查会议室换 `--rooms <roomId...>`,可同时传)。**禁止**用 `event list` 扫日程替代闲忙查询。
|
||||
4. **空闲时段(必须)**:找共同空闲用 `python scripts/calendar_free_slot_finder.py`。
|
||||
|
||||
**禁止**:用 `event list` 冒充 `busy search`、未确认时段就默认全天查询。
|
||||
|
||||
## 执行硬约束
|
||||
|
||||
- 多轮日程任务必须保留 `eventId`,后续加人、移人、订房、换房、改描述、删除都基于同一个 `eventId` 执行;不要重新创建重复日程。
|
||||
- 用户明确说"帮我订一个空闲会议室"时,`room search` 返回可用会议室后直接选择第一个可预订且不需要自定义审批的 `roomId` 执行 `room add`;不要把选择权抛回用户导致任务停住。
|
||||
- 已有日程订房:`dws calendar room search --start ... --end ... --format json` → `dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID> --format json` → `event get` 或 `room/busy` 验证。
|
||||
- 换会议室:先 `room delete --event <EVENT_ID> --rooms <OLD_ROOM_ID>`,再 `room add --event <EVENT_ID> --rooms <NEW_ROOM_ID>`,最后回查;不要只更新 `--location`。
|
||||
- 参会人变化用 `attendee add/delete`,日程描述变化用 `event update --desc`,删除日程用 `event delete --id`。用户当前消息已明确要求删除/取消时可直接执行;否则先确认。
|
||||
- 脚本失败或参数不完整时,立即降级到明确的 `dws calendar event/attendee/room` 命令,不要停在"我要查看用法"。
|
||||
- 所有 dws 命令带 `--format json`;查询时间必须显式 `--start` / `--end`。
|
||||
|
||||
## 跨产品协作
|
||||
|
||||
- 视频会议发起 / 入会链接 / 邀请入会 / 会中控制 → 当前 CLI **不支持**;请在钉钉客户端完成
|
||||
- 会后摘要 / 待办 → 切到 `dingtalk-minutes`
|
||||
- 参会人按人名 → 先用 `dingtalk-aisearch` 解析
|
||||
|
||||
## 注意
|
||||
|
||||
`schedule-meeting` 必须读 [03-meeting.md](references/03-meeting.md) 中的「两准则」「搜房失败硬门禁」,禁止假设 `roomId`。
|
||||
## 局部意图与短流程
|
||||
|
||||
- [局部意图消歧](references/intent-guide.md);[短流程](references/lite-recipes.md)。
|
||||
@@ -0,0 +1,56 @@
|
||||
# 会议管理(日程与会议室)
|
||||
|
||||
> lite recipe(`list-today-meetings`、`check-users-busy`)见 [lite-recipes.md](./lite-recipes.md);视频会议 `start-conference` 当前 CLI 不支持。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。**`schedule-meeting` 不做内联**:须读本文件 **「两准则」「搜房失败硬门禁」** 及下表 **schedule-meeting** 行全文。
|
||||
> **听记、会后待办、摘要分享** 见 `dingtalk-minutes/references/07-minutes.md`。
|
||||
|
||||
## 日程与会议室两准则(强制)
|
||||
|
||||
1. **时段**:用户已明确会议起止时间 → **禁止**自动改期、禁止用闲忙结果或「推荐时段」覆盖用户给定时段;只能在此时段内建日程、订会议室;该时段内无可用或指定资源不可用 → **立刻如实告知**,不得偷偷换时间段再试。
|
||||
2. **会议室**:用户点名具体会议室 → **禁止**换其他会议室;在用户给定时段内查无该房 → **立刻告知**。**用户未给出时段时,必须先显式向用户追问具体开始/结束时间;禁止默认用「当前时刻至当日 23:59:59」之类窗口代查。** **`calendar_schedule_meeting.py`**:仅需 `--title`、`--start`、`--end`;先创建日程,再邀请参会人,最后搜房/订房。未给会议室范围时,`--book-room` 为 **单次**无 `--group-id` 的 `room search --available`,取返回的**第一个**会议室并 `room add`;无结果则告警、不删日程。**若用户明确限定楼层/楼宇/园区/分组,应先用 `room list-groups` 解析允许的 `group-id`,再把这些 `group-id` 传给脚本 `--room-group-id`(或手工 `room search --group-id ...`);脚本只会在这些 group 内查找,若无空房则直接返回。对于同一地点(同园区/楼栋/楼层)的会议室,必须优先锁定最相关、最贴近该地点的承载 group;该 group 查无 roomId/空房,即可判定该地点当前时段无可订会议室,**不得**再去别的无关 group 继续碰运气,因为同一地点的会议室只会挂在其所属 group 下。** **在组织内、按早停规则已把应查的分组(或未限范围时的根目录一次查询)全部查完仍无可用会议室时,必须立即向用户说明「当前时段没有可预订的会议室」或「范围内未检索到可用会议室/资源」并收束,禁止继续扩区、换参重试或虚构有房。** 手工 `room search` **禁止**为试出空闲擅自改日或拉长时间窗。
|
||||
|
||||
### 会议室搜索早停
|
||||
|
||||
> 专用于 `calendar room list-groups` / `room search` / `room add`;与通用规范「无新参数不重复 search」一致。
|
||||
|
||||
**`room search --available`**(与传入的 `--start` / `--end` 配对):返回的是在**该整段时段内**可被预订的空闲会议室(不是「有一段空就算」);脚本与用户手工选房均应沿用同一时间窗,避免误以为分段凑满即等价于整段可用。
|
||||
|
||||
**`dws calendar room search` 合法参数**(与 [calendar.md](./calendar.md) 一致):仅 `--start`、`--end`、`--group-id`(可选)、`--available`(可选)、`--format json` 等;**禁止使用 `--query`**,否则会报 `unknown flag: --query`。
|
||||
|
||||
**地点归组早停**:若用户给的是同一地点范围(如“西溪园区 C6 楼 3-5 层”或具体楼层/楼栋),先用 `room list-groups` 找到**最相关的承载 group**(通常是该楼层;若楼层下无会议室则为直接挂会议室的上一级)。在这个最相关 group 下查不到有效 `rooms[].roomId` 或空房时,**不得**再跳去别的同级/异地 group 继续搜;同一地点的会议室不会散落在别的 group 里。只有用户明确放宽到别的楼层、楼栋或园区,才能重新解析新的 group 并继续。
|
||||
|
||||
**用户点名具体会议室(如「C6-4-06-N / 贡嘎山」)**:**不要**尝试 `room search --query "<名称>"`;**禁止**把用户原文(含「C6-4-06-N 贡嘎山」整句)或展示名当作 `room add --rooms` 的 `roomId`。用户输入**几乎从不会是**有效 `roomId`。须先 `dws calendar room list-groups` 定位所在楼层/分组的 `group-id`,再 `dws calendar room search --start "<ISO>" --end "<ISO>" --group-id <GROUP_ID> [--available] --format json`,在返回 `rooms[]` 中对 `roomName`、`name` 等与用户表述匹配,**仅**取 JSON 里的 `roomId`(典型为小写十六进制串,长度以返回为准),最后 `dws calendar room add --event <eventId> --rooms <roomId>`。该时段无匹配或房间忙 → 如实告知;**禁止**为通过校验而编造、拼接或猜测 `roomId`。
|
||||
|
||||
### 搜房失败硬门禁(园区/范围搜尽仍无 roomId)
|
||||
|
||||
在用户限定的园区、楼宇、楼层或固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),**本回合订房流程结束**。
|
||||
|
||||
**用户汇报硬门禁**:一旦触发上面的失败条件,**下一条对外输出必须直接面向用户汇报结果**,不得继续在会话里自言自语式地延长推理。允许的后续只有两类:
|
||||
1. **失败汇报**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订”
|
||||
2. **确认放宽条件**:仅在需要继续推进时,明确问用户是否放宽地点范围、改时间或接受不订会议室
|
||||
|
||||
以下表述/行为视为**违例**:继续写“让我再试一次”“也许是 Mock/测试环境”“可能存在预设 roomId 映射”“我去别的 group 看看”“我换个时间验证一下”“我先看看脚本/示例还能不能推断出 roomId”。
|
||||
|
||||
以下行为**一律禁止**(与是否「想多试一次」无关):编造/假设 `roomId` 格式做「预订测试」;在**没有**合法 `roomId` 时调用 `room add` 试探错误详情;拉 `event get` / 日程详情等试图**绕开** `room search` 推断 roomId;换无关园区、扩大关键词、换工具名做未经用户授权的新搜索。
|
||||
|
||||
**失败后强制回读**:若出现以下任一信号,下一步**必须重新读取本文件本节与 `schedule-meeting` recipe**,不得沿着当前假设继续试:
|
||||
1. 连续 **2 次** `room search` 空结果/无 `roomId`
|
||||
2. 任意一次 `roomId invalid`
|
||||
3. 已开始尝试「换园区 / 换楼栋 / 看 event 详情 / 猜 roomId」
|
||||
|
||||
回读后只允许二选一:
|
||||
1. **报错收束**:已搜尽允许范围/整园仍无 `roomId` 或无空房
|
||||
2. **用户确认**:明确询问是否放宽范围、换时间,或接受不订会议室
|
||||
|
||||
| # | 规范 |
|
||||
|---|------|
|
||||
| 1 | **一键脚本**:`calendar_schedule_meeting.py` 做「建日程 → 加人 → 可选搜房/订房」;未限范围时可直接 `--book-room`,脚本按根目录单次 `room search --available` 订第一家。**若搜房失败,脚本应输出明确失败原因并返回非零退出码,促使上层立即向用户汇报,而不是继续试探。** |
|
||||
| 2 | **要限范围/具名**:先 `list-groups` 解析允许的 `group-id`。若用户说的是同一地点(同园区/楼栋/楼层),应优先锁定**最相关的承载 group** 并只查它;该 group 无结果即可按该地点无房收束,不再试别的无关 group。仅当用户明确给出多个允许地点时,才分别对这些 group 各 **1 次** `room search --available` 再 `room add` |
|
||||
| 3 | **禁止**:无新信息时反复 `--verbose`、反复切 `--available`、父组子组试探、在最相关 group 无结果后改搜别的同级/异地 group、超 100 条后仍根分组或未授权区域全量搜;**禁止**对 `room search` 使用不存在的 `--query` |
|
||||
| 4 | **`roomId` 门禁**:`room add --rooms` **只能**填 `room search` 返回 JSON 中的 `rooms[].roomId`;**禁止**将用户说的会议室名、编号文案、或「假 UUID / 试数字」当作 `roomId` |
|
||||
| 5 | **全量无结果即收束**:在用户允许的搜索范围内(含**整园/全 campus** 若用户要求已逐组查尽)仍无任何可用会议室或有效 `roomId` → **直接报错/告知失败并结束订房**,且**下一条消息必须汇报给用户**;**不得**假设 ID、不得用 `room add` 试探、不得绕路查日程、不得继续自说自话分析 Mock/测试环境 |
|
||||
| 6 | **失败触发回读**:连续 2 次空结果、任意一次 `roomId invalid`、或开始换园区/绕路时 → **必须回读本节**;回读后只允许「报错收束」或「向用户确认是否放宽条件」 |
|
||||
|
||||
| Recipe | 行动指南(固定路线) |
|
||||
| ------------------ | ------------------- |
|
||||
| schedule-meeting | **见上文「两准则」**、**「搜房失败硬门禁」**。**未给时段且仅说「发起/开个会」**→ 不走本 recipe;当前 CLI 不支持实时视频会议,告知用户请在钉钉客户端操作。**未给时段但有预约意图**("安排""约""定"等词):追问具体开始/结束时间。**已有时段后**,按固定顺序执行:1. `dws calendar event create` 建日程;2. 有参会人则 `dws calendar participant add`;3. 再处理会议室。**无明确会议室范围**:可直接 `python scripts/calendar_schedule_meeting.py --title "<主题>" --start "<起始>" --end "<结束>" [--users <userIds>] [--book-room] [--dry-run]`。**有明确范围(某楼/层)**:先 `dws calendar room list-groups`,锁定该地点**最相关的承载 group**;若只有一个地点,`--room-group-id` 应只传这个最相关 group,**不要**把同楼内多个楼层 group 打包传入碰运气。只有用户明确给出多个允许地点时,才把这些 `group-id` 一并传给 `python scripts/calendar_schedule_meeting.py ... --book-room --room-group-id "<id1,id2,...>"`。**用户点名具体会议室**:须手工 `dws calendar room search --start "<ISO>" --end "<ISO>" --group-id <GROUP_ID> [--available] --format json`(**无** `--query`),在 JSON 中匹配名称取 **`rooms[].roomId` 唯一真值** → `dws calendar room add --event <eventId> --rooms <roomId>`;**不得**把用户输入的会议室名当 `roomId`。**一旦连续 2 次空结果 / 任意一次 `roomId invalid`**:**必须回读本节并立即收束判断**;若整园/限定范围内搜尽仍无 roomId 或无空房 → **下一条消息必须直接向用户汇报失败结论**;否则只能向用户确认是否放宽范围/改时间。**禁止**假设 roomId、禁止无 ID 调用 `room add`、禁止用日程详情绕路、禁止继续猜测 Mock/测试环境。细则见「会议室搜索早停」。 |
|
||||
| reschedule-meeting | 1. `calendar event list --start "<起始ISO>" --end "<结束ISO>"` → 取 `eventId` 2. `calendar event update --id <eventId> --start "<新起始ISO>" --end "<新结束ISO>"` 更新时间 3. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message send --conversation-id <openConversationId> --content "<变更通知>"` 通知变更 |
|
||||
@@ -0,0 +1,756 @@
|
||||
# 日历 (calendar) 命令参考
|
||||
|
||||
## CLI 命令树与黄金路径
|
||||
|
||||
- **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(我能看哪些日历本)、`acl`(我的日历共享给了谁)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。
|
||||
- **个人日程 / 给自己留时间块 / 专注时段**:统一走 **`dws calendar event create`**。当前**没有**单独的 `personal schedule create` / `calendar create` 命令。
|
||||
- **查日程列表**:`dws calendar event list --start "<ISO-8601>" --end "<ISO-8601>" --format json`,或优先使用脚本 `python scripts/calendar_today_agenda.py [today|tomorrow|week]`(见文末「自动化脚本」)。
|
||||
- **查循环日程实例**:`dws calendar event instances --id <EVENT_ID> --start "<ISO-8601>" --end "<ISO-8601>" --format json`,用于按时间范围展开重复日程的每一个实例。**注意:此接口只能查询重复性日程;普通非循环日程将查不到任何实例信息。**
|
||||
- **查用户日历本列表**:`dws calendar book list`(返回主日历 `id == "primary"` 等)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。
|
||||
- **CLI 不存在**独立的 `dws calendar list`;若误跑无子命令的 `dws calendar`,会打印整段 Usage,**切勿**将该段 help 当作工具结果再次塞进对话(会急剧增加 token 与首字延迟)。
|
||||
- **必须**遵循指令说明进行调用。**绝对禁止**使用虚构指令,使用虚构参数。
|
||||
|
||||
## 反模式(禁止)
|
||||
1. **禁止**执行 `dws calendar` 且不带二级子命令(会刷出大量帮助文本)。合法二级子命令:`event` / `attendee` / `room` / `busy` / `attachment` / `book` / `acl`。
|
||||
2. **禁止**使用不存在的子命令试探(如臆造 `dws calendar list`);需要日程列表时一律使用 **`dws calendar event list`**(带 `--start` / `--end`,见下文「查询日程列表」示例);需要日历本列表时使用 **`dws calendar book list`**。
|
||||
3. **禁止**将完整 `--help`/Usage 输出作为「观察」重复提交给模型;若误触,应直接改用本节黄金路径中的合法命令并重试。
|
||||
5. **禁止**为已有日程重新创建日程来预订会议室。若日程已存在(同一会话中刚创建、或用户明确指向某日程),必须使用 `room add --event <已有EVENT_ID> --rooms <ROOM_ID>` 追加会议室,**绝不能**再调一次 `event create --rooms`(会创建重复日程)。
|
||||
6. **禁止**用 `--location` 替代会议室预订。`--location` 是纯文本地点备注字段,填入会议室名称**不会**完成任何预订或占用。预订会议室必须通过 `room add --rooms <roomId>` 或 `event create --rooms <roomId>`,roomId 来自 `room search` 返回;`--location` 与 `--rooms` 是两个独立字段,用途完全不同。
|
||||
7. **禁止**只传 `--recurrence-*` 部分 flag **并不是彼此独立的参数**:只传其中一项(比如只改 `--recurrence-count`、只设 `--recurrence-type`)会让服务端收到不完整的 recurrence 结构,CLI 现已前置校验并直接拒绝这类调用。**修改已有周期日程的任何一个循环字段时,都必须重新提供完整的 pattern+range 字段集合**——必要时先 `event get` 读取现有 `recurrence`,再在命令中整体重传。
|
||||
8. **禁止**用一条 指令 实现串行调用。比如当用户要求一次性安排多场不同的日程(例如「上午 10 点开项目评审、下午 2 点开复盘会、晚上 7 点聚餐」)时,必须**拆解成 N 条独立的 `event create`,依次串行执行**;每条命令自己写完整的 `--title` / `--start` / `--end`,绝不能把多个标题或多段时间塞进同一行。
|
||||
|
||||
|
||||
## 核心概念
|
||||
日历(calendar):日程的容器。每个用户有一个主日历(我的日历,id: primary),还可以订阅公共/团队日历,以及他人共享的日历。
|
||||
日程(event):日历中的单个日程,包含起止时间、地点、标题、参会人等属性。支持单次日程和重复日程(有recurrence rule的日程,又称SeriesMaster),遵循RFC5545 iCalendar国际标准。
|
||||
日程实例(event instance):日程的具体时间实例,可以通过event list指令查询时间段内的所有实例。1个普通日程和对应1个Instance,而1个重复性日程(SeriesMaster)对应N个Instance(同属一个日程序列)。
|
||||
- 同一个日程序列具有相同的iCalUid,并且重复性日程,其eventId和iCalUid的值相同。因此可以通过重复性日程实例的iCalUid得到重复性日程(SeriesMaster)的eventId
|
||||
重复规则(recurrence rule):定义重复性日程的重复规则。
|
||||
参会人(attendee):日程的参与者。按姓名统一使用 `dws aisearch person --query "姓名" --dimension name --format json` 查询 userId。
|
||||
响应状态(response):参会人对日程的回应,包括:未响应、接受、待定、拒绝。
|
||||
忙闲时间(busy):查询用户在指定时间段的忙闲状态,查询会议室在指定时间段的预定状态,用于会议时间协调。
|
||||
会议室(room):room是 会议室 ,room可视为日程的资源类参会人,需要加入日程完成预订。注意和location区分,location只是地点,和room不同。
|
||||
访问控制 (acl):用户可通过设置acl将自己的日历访问权限授予给他人(即:共享日历),授予后,他人就可以查看日历下的日程信息。通过 `dws calendar acl list` 可查看当前要用户已经授权出去的权限。若 privilege >= reader,可查询此日历下的日程数据。若 privilege >= writer ,可操作(创建/修改/删除/响应等)此日历下的日程数据。
|
||||
共享日历:将自己日历的访问权限授予给他人,用于协作。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `shared` 即他人共享给当前用户的日历。
|
||||
订阅日历:创建一个公共日历供他人订阅,他人订阅后即可查询日历下的日程数据。通过 `dws calendar book list` 查询当前用户日历列表,其中type == `subscribed` 即当前用户已订阅的日历。注意:订阅日历下的日程无参会人概念,无法执行 attendee 相关指令,也无法添加会议室。
|
||||
|
||||
## 命令概览
|
||||
|
||||
### event 相关三级子命令
|
||||
```
|
||||
# 针对单个日程: 创建 | 修改 | 单查询 | 删除 | 响应日程(接受、暂定、拒绝)
|
||||
dws calendar event [create|update|get|delete|respond] [flags]
|
||||
# 按时间范围批量查询
|
||||
dws calendar event list [flags]
|
||||
# 查询循环日程的实例列表(按时间范围展开重复日程)
|
||||
dws calendar event instances [flags]
|
||||
# 获取日程的分享信息(日程主题、组织人、地点、入会信息等,用于向他人分享日程)
|
||||
dws calendar event share-info [flags]
|
||||
# 对于非明确时间或一段时间范围的约会场景,可基于所有参会人的忙闲状态,推荐多个可用的时间块方案
|
||||
dws calendar event suggest [flags]
|
||||
```
|
||||
|
||||
### attendee 相关三级子命令
|
||||
```
|
||||
# 日程中参会人操作:添加 | 删除 | 查询
|
||||
dws calendar attendee [add|delete|list] [flags]
|
||||
```
|
||||
|
||||
### room 相关三级子命令
|
||||
```
|
||||
# 查询分组
|
||||
dws calendar room list-groups [flags]
|
||||
# 会议室搜索
|
||||
dws calendar room search [flags]
|
||||
# 预定会议室
|
||||
dws calendar room add [flags]
|
||||
# 释放会议室
|
||||
dws calendar room delete [flags]
|
||||
```
|
||||
> room是会议室,用于线下开会场景。
|
||||
|
||||
### busy 相关三级子命令
|
||||
```
|
||||
# 按用户 / 会议室 + 时间窗查闲忙状态(--users 与 --rooms 至少其一),会议室的忙闲等同于预定记录
|
||||
dws calendar busy search [flags]
|
||||
```
|
||||
|
||||
### attachment 相关三级子命令
|
||||
```
|
||||
# 把已上传到钉盘的文件挂到日程上(不负责上传,只负责挂载)
|
||||
dws calendar attachment add [flags]
|
||||
```
|
||||
|
||||
### book 相关三级子命令(查"我能看哪些日历本")
|
||||
```
|
||||
# 查询我拥有和可访问的所有日历本(含他人共享给我的)
|
||||
dws calendar book list [flags]
|
||||
# 查询指定日历本信息
|
||||
dws calendar book get [flags]
|
||||
# 按名称模糊搜索日历本
|
||||
dws calendar book search [flags]
|
||||
# 更新日历本信息(需 owner 权限)
|
||||
dws calendar book update [flags]
|
||||
```
|
||||
|
||||
### acl 相关三级子命令(查"我的日历共享给了谁")
|
||||
```
|
||||
# 查询我的日历共享给了哪些人、各自什么权限
|
||||
dws calendar acl list [flags]
|
||||
# 把我的日历共享给某人
|
||||
dws calendar acl add [flags]
|
||||
# 取消我的日历对某人的共享
|
||||
dws calendar acl delete [flags]
|
||||
```
|
||||
> **说明**: 可以通过 --help 进一步查看指令明细,也可以继续查看下一节 命令总览
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 查询日程列表
|
||||
```
|
||||
Usage:
|
||||
dws calendar event list [flags]
|
||||
Example:
|
||||
dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||||
dws calendar event list --start "2026-03-10T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00" --limit 50
|
||||
dws calendar event list --calendar-id primary
|
||||
dws calendar event list --cursor "<nextCursor从上一次查询结果获取>"
|
||||
Flags:
|
||||
--calendar-id string 日历 ID (默认 primary 主日历,仅在查询其他日历本时填写;通过 `book list` 获取)
|
||||
--cursor string 分页游标 (从上一次返回的 nextCursor 获取,首次查询无需传入)
|
||||
--end string 结束时间 ISO-8601 (例如 2026-03-10T18:00:00+08:00)
|
||||
--limit int 每页返回条数 (默认 100,最大 100)
|
||||
--start string 开始时间 ISO-8601 (例如 2026-03-10T14:00:00+08:00)
|
||||
```
|
||||
|
||||
**默认行为**:不传 `--start` / `--end` 时,默认返回今天的日程(00:00:00 ~ 23:59:59)。
|
||||
**权限**:查询共享日历下的日程时,至少要有reader权限。
|
||||
**分页**:单次最多返回 `--limit` 指定的条数(默认/最大 100);当结果超过 limit 时,返回体包含 `nextCursor` 字段。首次查询无需传 `--cursor`,仅在翻页时将上一次返回的 `nextCursor` 作为 `--cursor` 传入。
|
||||
|
||||
|
||||
### 获取日程详情
|
||||
```
|
||||
Usage:
|
||||
dws calendar event get [flags]
|
||||
Example:
|
||||
dws calendar event get --id <EVENT_ID>
|
||||
dws calendar event get --id <EVENT_ID> --calendar-id primary
|
||||
Flags:
|
||||
--id string 日程 ID (必填)
|
||||
--calendar-id string 日历 ID (默认 primary 主日历)
|
||||
```
|
||||
|
||||
### 查询循环日程实例
|
||||
```
|
||||
Usage:
|
||||
dws calendar event instances [flags]
|
||||
Example:
|
||||
dws calendar event instances --id <EVENT_ID>
|
||||
dws calendar event instances --id <EVENT_ID> --start "2026-03-10T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00"
|
||||
dws calendar event instances --id <EVENT_ID> --limit 50
|
||||
dws calendar event instances --id <EVENT_ID> --cursor "<nextCursor>"
|
||||
Flags:
|
||||
--id string 日程 ID (必填,重复性日程 SeriesMaster 的 eventId)
|
||||
--calendar-id string 日历 ID (默认 primary 主日历,仅在查询其他日历本时填写;通过 `book list` 获取)
|
||||
--start string 开始时间 ISO-8601 (例如 2026-03-10T00:00:00+08:00,不传则默认今天 00:00:00)
|
||||
--end string 结束时间 ISO-8601 (例如 2026-03-31T23:59:59+08:00,不传则默认今天 23:59:59)
|
||||
--limit int 每页返回条数 (默认 100,最大 100)
|
||||
--cursor string 分页游标 (从上一次返回的 nextCursor 获取,首次查询无需传入)
|
||||
```
|
||||
|
||||
> **说明**:用于按时间范围展开重复日程(SeriesMaster)的每一个实例。**此接口只能查询重复性日程;若传入普通非循环日程,将查不到任何实例信息。**`--id` 必须是重复性日程的 eventId,可通过 `event list` 获取。
|
||||
> **默认行为**:不传 `--start` / `--end` 时,默认返回今天的实例(00:00:00 ~ 23:59:59)。
|
||||
> **分页**:单次最多返回 `--limit` 指定的条数(默认/最大 100);当结果超过 limit 时,返回体包含 `nextCursor` 字段。
|
||||
|
||||
### 获取日程分享信息
|
||||
```
|
||||
Usage:
|
||||
dws calendar event share-info [flags]
|
||||
Example:
|
||||
dws calendar event share-info --id <EVENT_ID>
|
||||
dws calendar event share-info --id <EVENT_ID> --language zh-CN
|
||||
dws calendar event share-info --id <EVENT_ID> --calendar-id primary
|
||||
Flags:
|
||||
--id string 日程 ID (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历)
|
||||
--language string 语言代码 (可选,如 zh-CN)
|
||||
```
|
||||
|
||||
> **说明**:根据日程 ID 获取日程的分享信息,展示日程主题、组织人、地点、入会信息等,用于向他人分享日程(如发送到群聊、邮件)。中文内容建议传 `--language zh-CN`。
|
||||
|
||||
### 创建日程
|
||||
```
|
||||
Usage:
|
||||
dws calendar event create [flags]
|
||||
Example:
|
||||
dws calendar event create --title "Q1 复盘会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
|
||||
dws calendar event create --title "周会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||||
--attendees userId1,userId2
|
||||
dws calendar event create --title "项目评审" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||||
--rooms roomId1,roomId2 # 创建时直接预定会议室
|
||||
dws calendar event create --title "每日站会" \
|
||||
--start "2026-03-10T09:00:00+08:00" --end "2026-03-10T09:30:00+08:00" \
|
||||
--recurrence-type daily --recurrence-interval 1 --recurrence-range-type numbered --recurrence-count 10
|
||||
dws calendar event create --title "团队周会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||||
--calendar-id <SHARED_CALENDAR_ID> # 在指定日历本下创建日程
|
||||
dws calendar event create --title "重要会议" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||||
--remind-minutes 5,10 # 开始前5分钟和10分钟各提醒一次
|
||||
Flags:
|
||||
--title string 日程标题 (必填,最大2048字符)
|
||||
--start string 开始时间 ISO-8601 (必填,例如 2026-03-10T14:00:00+08:00)
|
||||
--end string 结束时间 ISO-8601 (必填,例如 2026-03-10T15:00:00+08:00)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;仅在共享/订阅日历本下创建时填写,通过 `book list` 获取)
|
||||
--timezone string 时区 IANA 格式 (例如 Asia/Shanghai,默认 Asia/Shanghai)
|
||||
--desc string 日程描述 (最大5000字符)
|
||||
--attendees string 参会人 userId 列表,逗号分隔 (最多500人) 日程组织人自动放入参会人列表,无需传入userId
|
||||
--open-dingtalk-ids string openDingTalkId 列表,逗号分隔 (与 --attendees 至少传一个)
|
||||
--rooms string 会议室 roomId 列表,逗号分隔 (创建时直接预定,roomId 必须来自 `room search` 返回,若是循环会议,必须设置recurrence-end-date,避免长期预订)
|
||||
# 以下 --recurrence-* 一旦使用任一 flag,必须同时提供完整的 pattern+range 字段(至少 --recurrence-type、--recurrence-interval(>0) 与 --recurrence-range-type)
|
||||
# 否则 CLI 会报 "recurrence 结构不完整" 并拒绝执行
|
||||
--recurrence-type string 循环类型: daily|weekly|absoluteMonthly|relativeMonthly|absoluteYearly
|
||||
--recurrence-interval int 循环间隔 (如 daily 时表示每N天)
|
||||
--recurrence-days-of-week string 周几: sunday,monday,...,saturday (weekly/relativeMonthly 时必填)
|
||||
--recurrence-day-of-month int 每月第几天 (absoluteMonthly/absoluteYearly 时必填)
|
||||
--recurrence-index string 每月第几周: first|second|third|fourth|last (relativeMonthly 时必填)
|
||||
--recurrence-first-day-of-week string 一周起始日,默认 sunday
|
||||
--recurrence-range-type string 循环范围: noEnd|endDate|numbered (与 --recurrence-type 必须成对出现)
|
||||
--recurrence-end-date string 循环结束时间 ISO-8601 (range-type=endDate 时必填)
|
||||
--recurrence-count int 循环次数 (range-type=numbered 时必填)
|
||||
--rich-text-desc string html格式的富文本类型日程描述,用于复杂内容的展示
|
||||
--location string 地点信息(纯文本备注,如‘3号楼A区’;**不等于**预订会议室)
|
||||
--free-busy string 此日程的忙碌状态,默认值为busy。busy - 在忙闲视图中,此日程时间段为忙碌; free - 此日程不占用忙闲
|
||||
--remind-minutes string 日程开始前提醒,逗号分隔分钟数 (可选,例如 5,10,15 表示开始前5/10/15分钟提醒;不传则默认15分钟提醒)
|
||||
```
|
||||
|
||||
> **说明**:个人日程也走 `event create`。如果只是给自己安排时间,不传 `--attendees` / `--open-dingtalk-ids` 即可。
|
||||
|
||||
### 修改日程
|
||||
```
|
||||
Usage:
|
||||
dws calendar event update [flags]
|
||||
Example:
|
||||
dws calendar event update --id <EVENT_ID> --title "新标题"
|
||||
dws calendar event update --id <EVENT_ID> --desc "新描述" --timezone Asia/Tokyo
|
||||
dws calendar event update --id <EVENT_ID> --recurrence-type daily --recurrence-interval 1 \
|
||||
--recurrence-range-type numbered --recurrence-count 5
|
||||
dws calendar event update --id <EVENT_ID> --calendar-id <SHARED_CALENDAR_ID> --title "新标题" # 修改其他日历本下的日程
|
||||
Flags:
|
||||
--id string 日程 ID (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取)
|
||||
--title string 新标题
|
||||
--start string 新开始时间 ISO-8601
|
||||
--end string 新结束时间 ISO-8601
|
||||
--desc string 新描述 (最大5000字符)
|
||||
--timezone string 时区 IANA 格式 (例如 Asia/Shanghai)
|
||||
# 以下 --recurrence-* 在 修改周期日程的循环规则时必须**整体**传入:MCP 不合并部分字段,只改其中一项(例如只传 --recurrence-count)会把规则覆盖成不完整状态
|
||||
# 若只想微调已有规则,请先 `event get --id <ID>` 读取现有 recurrence,再在本命令重传完整的 pattern+range
|
||||
--recurrence-type string 循环类型: daily|weekly|absoluteMonthly|relativeMonthly|absoluteYearly
|
||||
--recurrence-interval int 循环间隔 (如 daily 时表示每N天)
|
||||
--recurrence-days-of-week string 周几: sunday,monday,...,saturday (weekly/relativeMonthly 时必填)
|
||||
--recurrence-day-of-month int 每月第几天 (absoluteMonthly/absoluteYearly 时必填)
|
||||
--recurrence-index string 每月第几周: first|second|third|fourth|last (relativeMonthly 时必填)
|
||||
--recurrence-first-day-of-week string 一周起始日,默认 sunday
|
||||
--recurrence-range-type string 循环范围: noEnd|endDate|numbered (与 --recurrence-type 必须成对出现)
|
||||
--recurrence-end-date string 循环结束时间 ISO-8601 (range-type=endDate 时必填)
|
||||
--recurrence-count int 循环次数 (range-type=numbered 时必填)
|
||||
--rich-text-desc string html格式的富文本类型日程描述,用于复杂内容的展示
|
||||
--location string 地点信息(纯文本备注,如‘3号楼A区’;**不等于**预订会议室)
|
||||
--free-busy string 修改此日程的忙碌状态,无需修改则不传。busy - 在忙闲视图中,此日程时间段为忙碌; free - 此日程不占用忙闲
|
||||
```
|
||||
> 支持修改标题、描述、时间、地点、忙碌状态等。如需修改会议室,请使用 dws calendar room [add|delete];如需修改参会人,请使用 dws calendar attendee [add|delete]
|
||||
|
||||
### 删除日程
|
||||
|
||||
> **CAUTION:** 不可逆操作 — 所有参会人同步取消,必须先向用户确认。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws calendar event delete [flags]
|
||||
Example:
|
||||
dws calendar event delete --id <EVENT_ID> --yes
|
||||
dws calendar event delete --id <EVENT_ID> --calendar-id <SHARED_CALENDAR_ID> --yes # 删除其他日历本下的日程
|
||||
Flags:
|
||||
--id string 日程 ID (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取)
|
||||
```
|
||||
|
||||
### 查看参会人
|
||||
```
|
||||
Usage:
|
||||
dws calendar attendee list [flags]
|
||||
Example:
|
||||
dws calendar attendee list --event <EVENT_ID>
|
||||
dws calendar attendee list --event <EVENT_ID> --calendar-id <SHARED_CALENDAR_ID> # 查看其他日历本下日程的参会人
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取, 注意:订阅日历下的日程无参会人,因此不可查看)
|
||||
```
|
||||
|
||||
### 添加参会人
|
||||
```
|
||||
Usage:
|
||||
dws calendar attendee add [flags]
|
||||
Example:
|
||||
dws calendar attendee add --event <EVENT_ID> --attendees <USER_ID_1>,<USER_ID_2>
|
||||
dws calendar attendee add --event <EVENT_ID> --attendees <USER_ID> --optional
|
||||
dws calendar attendee add --event <EVENT_ID> --attendees <USER_ID> --calendar-id <SHARED_CALENDAR_ID> # 给其他日历本下的日程添加参会人
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--attendees string 参会人 userId 列表,逗号分隔 (必填,最多500人)
|
||||
--optional 参会人可选 (默认必选参会人)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可添加)
|
||||
```
|
||||
|
||||
### 移除参会人
|
||||
|
||||
> **CAUTION:** 写操作 — 执行前须用户确认。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws calendar attendee delete [flags]
|
||||
Example:
|
||||
dws calendar attendee delete --event <EVENT_ID> --attendees <USER_ID> --yes
|
||||
dws calendar attendee delete --event <EVENT_ID> --attendees <USER_ID> --calendar-id <SHARED_CALENDAR_ID> --yes # 移除其他日历本下日程的参会人
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--attendees string 参会人 userId 列表,逗号分隔 (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可移除)
|
||||
```
|
||||
|
||||
### 搜索会议室
|
||||
> 此指令支持两种模式:**按名称搜索**(不传 --start/--end)和**按时间段搜索可用会议室**(传 --start/--end 或不传任何参数)。
|
||||
> 此指令搜索到的会议室结果中,有两个值需要注意:
|
||||
> - customApprovalProcess: true - 表示该会议室设置了自定义审批流程,只能通过客户端完成预订。
|
||||
> - supportRecurring: true - 表示该会议室支持循环预定;false - 表示不支持循环预定,直接加入到循环日程会失败。
|
||||
|
||||
**模式路由规则**:
|
||||
- **按名称搜索**:仅传 `--room-name`,不传 `--start`/`--end` → 返回所有匹配名称的会议室,**不检查可用性**。适用于「找到某个会议室」的场景。
|
||||
- **按时间段搜索可用会议室**:传 `--start`/`--end`,或不传任何参数 → 返回指定时间段内**可用**的会议室。不传时间时默认当前时间起 1 小时。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws calendar room search [flags]
|
||||
Example:
|
||||
# 按名称搜索(不检查可用性,返回所有匹配的会议室)
|
||||
dws calendar room search --room-name 永澄亭 # 注意:用户即使说「永澄亭会议室」,也应仅传「永澄亭」
|
||||
|
||||
# 按时间段搜索可用会议室
|
||||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
|
||||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --group-id <GROUP_ID>
|
||||
dws calendar room search # 不传 --start/--end 时默认当前时间起 1 小时
|
||||
|
||||
# 名称 + 时间段:搜索指定名称的可用会议室
|
||||
dws calendar room search --room-name 永澄亭 --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
|
||||
|
||||
# 分页(仅按时间段搜索时有效)
|
||||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --limit 20 --page 0
|
||||
Flags:
|
||||
--start string 开始时间 ISO-8601 (可选,不传则默认当前时间+1分钟缓冲)
|
||||
--end string 结束时间 ISO-8601 (可选,不传则默认当前时间+1 小时)
|
||||
--group-id string 会议室分组ID(可选,留空查根目录;超100条时需按分组查询;仅按时间段搜索时有效)
|
||||
--room-name string 会议室名称(按名称搜索时必填;按时间段搜索时可选,用于过滤)
|
||||
--limit string 页大小 (可选,不填默认 100,超过 100 按 100 处理;仅按时间段搜索时有效)
|
||||
--page string 分页起始位置 (可选,不填默认 0;仅按时间段搜索时有效)
|
||||
```
|
||||
|
||||
> **时间约束(API 限制)**:`start` 必须是未来的时间(服务端校验:start can not less current time)。
|
||||
> - 若传入的 `--start` 早于当前时间,CLI 会自动修正为 `now + 1min`,调用方无需额外处理。
|
||||
> - 若传入的 `--end` 早于当前时间,CLI 直接报错——无法检索已过去的时间段。
|
||||
> - **最佳实践**:调用方在组装时间参数时应确保 start/end 都是未来时间;若不确定,可省略 `--start`/`--end` 让 CLI 使用默认值(当前时间起 1 小时)。
|
||||
> - **注意**:时间约束仅在「按时间段搜索」模式下生效。按名称搜索(仅传 `--room-name`)不受时间约束。
|
||||
|
||||
**名称过滤使用规范**:`--room-name` 适用于用户说「预定永澄亭」「约西湖厅」这类按名找会议室的场景。
|
||||
- **服务端是模糊匹配,但匹配词越精简命中率越高**,关键疗法:**调用方必须在调用 CLI 前自行精简名称,CLI 不会再做任何删减**。
|
||||
- 常见需要剔除的用户口语后缀(仅示例,实际场景由模型自行判断):「会议室」「大会议室」「小会议室」「厅」「房」等。
|
||||
- 示例对映:
|
||||
- 用户:「帮我订永澄亭会议室」 → `--room-name 永澄亭`
|
||||
- 用户:「西湖厅有空吗」 → `--room-name 西湖厅`(本身就是专名,不删即可)
|
||||
- 用户:「预定贡嘎山大会议室」 → `--room-name 贡嘎山`
|
||||
|
||||
**优先路径**:
|
||||
- 当用户仅给出会议室名称、未指定时间段时,用 `room search --room-name <核心专名>` 快速找到会议室(不检查可用性)。
|
||||
- 当用户给出会议室名称且指定了时间段时,用 `room search --room-name <核心专名> --start <开始时间> --end <结束时间>` 查询该名称的可用会议室。
|
||||
- 若返回空列表,再降级使用 `list-groups` 定位分组再查。`--room-name` 可与 `--group-id` 同时使用(仅按时间段搜索时),表示「在指定分组内按名称过滤」。
|
||||
|
||||
**`roomId` 与用户说的话不是一回事**:用户说的「C6-4-06-N 贡嘎山」等是**展示名/编号文案**,**绝不能**直接填进 `room add --rooms`。`--rooms` 只接受上一步 `room search`(或同类接口)返回 JSON 里的 **`rooms[].roomId`**。形态上多为**小写十六进制串**(长度以接口为准,例如 `e6b7b65b8b30fb707afcf6c3b699f028003e6834fdd7fee7`)。含**中文、空格、连字符拼接的楼层编号**、或凭空调 UUID/纯数字「试格式」——一律视为非法,必须先搜房再取返回字段。
|
||||
|
||||
> 如果知道roomId,想查该会议室的预订记录,直接用dws calendar busy search 指令
|
||||
|
||||
---
|
||||
|
||||
### 预定会议室
|
||||
```
|
||||
Usage:
|
||||
dws calendar room add [flags]
|
||||
Example:
|
||||
dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID>
|
||||
dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID> --calendar-id <SHARED_CALENDAR_ID> # 给其他日历本下的日程预定会议室
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--rooms string 会议室 ID 列表 (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程不可添加会议室)
|
||||
```
|
||||
> room是会议室,用于线下开会场景。将room加入到日程完成预订
|
||||
> 重复性日程,预订会议室时,必须设置 循环结束时间(recurrence-end-date),noEnd 或者 指定循环次数 都无法完成预定。
|
||||
|
||||
|
||||
### 移除会议室
|
||||
|
||||
> **CAUTION:** 写操作 — 执行前须用户确认。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws calendar room delete [flags]
|
||||
Example:
|
||||
dws calendar room delete --event <EVENT_ID> --rooms <ROOM_ID> --yes
|
||||
dws calendar room delete --event <EVENT_ID> --rooms <ROOM_ID> --calendar-id <SHARED_CALENDAR_ID> --yes # 移除其他日历本下日程的会议室
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--rooms string 会议室 ID 列表 (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程不可添加会议室)
|
||||
```
|
||||
|
||||
### 会议室分组列表
|
||||
```
|
||||
Usage:
|
||||
dws calendar room list-groups [flags]
|
||||
Example:
|
||||
dws calendar room list-groups
|
||||
dws calendar room list-groups --limit 20 --page 0
|
||||
Flags:
|
||||
--limit string 页大小 (可选,不填默认 100,超过 100 按 100 处理)
|
||||
--page string 分页起始位置 (可选,不填默认 0)
|
||||
```
|
||||
|
||||
### 添加日程附件
|
||||
```
|
||||
Usage:
|
||||
dws calendar attachment add [flags]
|
||||
Example:
|
||||
dws calendar attachment add --event <EVENT_ID> --files <FILE_ID>:report.pdf,<FILE_ID2>:slides.pptx
|
||||
dws calendar attachment add --event <EVENT_ID> --files <FILE_ID>:report.pdf --calendar-id <SHARED_CALENDAR_ID> # 给其他日历本下的日程添加附件
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--files string 附件列表,格式 <fileId>:<name>,多项逗号分隔 (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程不可添加附件)
|
||||
```
|
||||
|
||||
> 上传文件得到 `fileId` 需配合钉盘相关流程;本命令只负责把已上传的文件挂载到日程上。
|
||||
|
||||
### 查询我能看的所有日历本
|
||||
```
|
||||
Usage:
|
||||
dws calendar book list [flags]
|
||||
Example:
|
||||
dws calendar book list
|
||||
```
|
||||
> 查询"我能看哪些日历本",包含:我的主日历、他人共享**给我**的日历、订阅的公共/团队日历。注意区分:`acl list` 是查"我的日历共享**给了谁**",方向相反。
|
||||
> 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id <xxx的日历本id> `可查到xxx完整的日程安排。
|
||||
> 主日历 `id` 固定为 `primary`,绝大多数日程操作都默认走主日历,只有当用户明确要求查/写其他日历本时才需要带 `--calendar-id`。
|
||||
|
||||
### 查询指定日历本
|
||||
```
|
||||
Usage:
|
||||
dws calendar book get [flags]
|
||||
Example:
|
||||
dws calendar book get --id primary
|
||||
dws calendar book get --id CALENDAR_ID
|
||||
Flags:
|
||||
--id string 日历 ID (必填,主日历固定为 primary)
|
||||
```
|
||||
|
||||
> **说明**:根据日历 id 查询指定日历的详细信息。用户主日历本 id 固定为 `primary`。
|
||||
|
||||
### 搜索日历本
|
||||
```
|
||||
Usage:
|
||||
dws calendar book search [flags]
|
||||
Example:
|
||||
dws calendar book search --query "项目"
|
||||
dws calendar book search --query "团队周报"
|
||||
Flags:
|
||||
--query string 按日历本名称模糊检索 (必填)
|
||||
```
|
||||
|
||||
> **说明**:搜索当前用户拥有的日历本,支持按日历本名模糊搜索。获取全部日历请使用 `book list`。
|
||||
|
||||
### 更新日历本
|
||||
```
|
||||
Usage:
|
||||
dws calendar book update [flags]
|
||||
Example:
|
||||
dws calendar book update --id CALENDAR_ID --summary "新日历名"
|
||||
dws calendar book update --id CALENDAR_ID --desc "日历描述"
|
||||
Flags:
|
||||
--id string 日历 ID (必填)
|
||||
--summary string 日历标题
|
||||
--desc string 日历描述
|
||||
```
|
||||
|
||||
> **说明**:更新日历信息,最低权限要求:privilege == "owner"。注意:用户主日历本 以及 他人共享的日历本 **不支持更新**。
|
||||
|
||||
### 查询我的日历共享给了谁
|
||||
```
|
||||
Usage:
|
||||
dws calendar acl list [flags]
|
||||
Example:
|
||||
dws calendar acl list
|
||||
```
|
||||
|
||||
> **说明**:查询"我的日历共享给了哪些人、各自什么权限"(即主日历的访问控制列表)。注意区分:`book list` 是查"我能看哪些日历本",方向相反。
|
||||
|
||||
### 把我的日历共享给某人
|
||||
```
|
||||
Usage:
|
||||
dws calendar acl add [flags]
|
||||
Example:
|
||||
dws calendar acl add --user USER_ID --privilege reader
|
||||
dws calendar acl add --user USER_ID --privilege writer --no-notification
|
||||
Flags:
|
||||
--user string 授予权限的目标用户 ID (必填)
|
||||
--privilege string 授予的日历权限 (必填): free_busy_reader(查看忙闲)|title_reader(查看标题)|reader(查看详情)|writer(创建和编辑)
|
||||
--no-notification 不向被授权用户发送提醒 (默认发送)
|
||||
```
|
||||
|
||||
> **说明**:把我的日历共享给指定用户,授予对方相应权限。`--privilege` 可选值:`free_busy_reader`(查看忙闲)、`title_reader`(查看标题)、`reader`(查看详情)、`writer`(创建和编辑)。
|
||||
|
||||
### 取消我的日历对某人的共享
|
||||
```
|
||||
Usage:
|
||||
dws calendar acl delete [flags]
|
||||
Example:
|
||||
dws calendar acl delete --acl-id ACL_ID
|
||||
Flags:
|
||||
--acl-id string 已授予权限的 ID (必填,可通过 acl list 查询)
|
||||
```
|
||||
|
||||
> **说明**:取消我的日历对某人的共享(撤回已授予的访问权限)。aclId 可通过 `acl list` 获取。
|
||||
|
||||
### 查询用户 / 会议室闲忙状态
|
||||
```
|
||||
Usage:
|
||||
dws calendar busy search [flags]
|
||||
Example:
|
||||
# 查用户闲忙
|
||||
dws calendar busy search --users <USER_ID_1>,<USER_ID_2> \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||||
# 查会议室闲忙
|
||||
dws calendar busy search --rooms <ROOM_ID_1>,<ROOM_ID_2> \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||||
# 同时查用户 + 会议室
|
||||
dws calendar busy search --users <USER_ID> --rooms <ROOM_ID> \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||||
Flags:
|
||||
--end string 结束时间 ISO-8601 (必填)
|
||||
--start string 开始时间 ISO-8601 (必填)
|
||||
--users string 用户 ID 列表,逗号分隔 (与 --rooms 至少其一)
|
||||
--rooms string 会议室 ID 列表,逗号分隔 (与 --users 至少其一)
|
||||
```
|
||||
|
||||
> **说明**:
|
||||
> - `--users` 与 `--rooms` 必须至少指定其一,可以同时指定;CLI 会做前置校验,两者都为空会直接报错。
|
||||
> - 查询会议室闲忙前,可先用 `dws calendar room search` 或 `dws calendar room list-groups` 拿到 roomId。
|
||||
> - 返回结果中的忙碌时段仅包含粗粒度的时间信息,不包含日程内容细节(如标题、参会人、地点),以保护隐私。
|
||||
|
||||
### 建议日程时间
|
||||
```
|
||||
Usage:
|
||||
dws calendar event suggest [flags]
|
||||
Example:
|
||||
dws calendar event suggest --users userId1,userId2 --duration 60
|
||||
dws calendar event suggest --start "2026-03-10T09:00:00+08:00" --end "2026-03-10T18:00:00+08:00" --users userId1
|
||||
dws calendar event suggest --users userId1 --duration 30 --timezone Asia/Tokyo
|
||||
Flags:
|
||||
--start string 推荐时间范围开始 ISO-8601 (默认当前时间)
|
||||
--end string 推荐时间范围结束 ISO-8601 (默认次日18点)
|
||||
--timezone string 时区 IANA 格式 (默认 Asia/Shanghai)
|
||||
--users string 参会人 userId 列表,逗号分隔
|
||||
--duration string 日程持续时间,单位分钟 (默认30)
|
||||
|
||||
> 对于非明确时间或一段时间范围的约会场景,可基于所有参会人的忙闲状态,推荐多个可用的时间块方案,用于解决会议时间协调问题。
|
||||
```
|
||||
|
||||
### 响应日程
|
||||
```
|
||||
Usage:
|
||||
dws calendar event respond [flags]
|
||||
Example:
|
||||
dws calendar event respond --id <EVENT_ID> --status accepted
|
||||
dws calendar event respond --id <EVENT_ID> --status declined
|
||||
dws calendar event respond --id <EVENT_ID> --status tentative
|
||||
dws calendar event respond --id <EVENT_ID> --status accepted --calendar-id <SHARED_CALENDAR_ID> # 响应其他日历本下的日程
|
||||
Flags:
|
||||
--id string 日程 ID (必填)
|
||||
--status string 响应状态: needsAction(未操作)|accepted(接受)|declined(拒绝)|tentative(暂定) (必填)
|
||||
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程无参会人,因此不可响应)
|
||||
```
|
||||
|
||||
> **说明**:作为日程参会人,设置自己的响应状态(接受、拒绝、暂定)。`--status` 可选值:`needsAction`(未操作,默认值)、`accepted`(接受)、`declined`(拒绝)、`tentative`(暂定)。
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"日程/会议/约会/日历":
|
||||
- 查看 → `event list`
|
||||
- 详情 → `event get`
|
||||
- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`,自定义提醒加 `--remind-minutes`)
|
||||
- 修改/改时间/改描述 → `event update`(支持修改标题、时间、描述、时区、循环规则)
|
||||
- 取消/删除 → `event delete`
|
||||
- 推荐时间/什么时候有空/协调时间 → `event suggest`
|
||||
- 接受/拒绝/暂定日程 → `event respond`
|
||||
- 查询循环日程/重复日程的每次实例/展开循环日程 → `event instances`
|
||||
- 分享日程/把日程发给别人/获取日程分享信息或入会信息 → `event share-info`
|
||||
|
||||
用户说"参会人/与会者":
|
||||
- 查看 → `attendee list`
|
||||
- 邀请/添加 → `attendee add --attendees <USER_ID>`(可选参会人加 `--optional`)
|
||||
- 移除 → `attendee delete --attendees <USER_ID>`
|
||||
|
||||
用户说"会议室/订会议室":
|
||||
- 哪个空闲 → `room search`(默认查当前时间起 1 小时内可用会议室)
|
||||
- 按名找会议室(如「永澄亭」「永澄亭会议室」「约西湖厅」,未提时间段)→ 先在模型层精简名称(剔除「会议室」等通用后缀),再用 `room search --room-name <核心专名>`(按名称搜索,不检查可用性)
|
||||
- 按名找可用会议室(如「永澄亭下午 2 点有空吗」)→ `room search --room-name <核心专名> --start <开始时间> --end <结束时间>`(按名称+时间段搜索可用会议室)
|
||||
- 预订
|
||||
- 给已有日程订会议室 → `room add --event <已有EVENT_ID> --rooms <ROOM_ID>`
|
||||
- 创建新日程并订会议室 → `event create --rooms`(仅当日程尚不存在时)
|
||||
- 取消预定 → `room delete`
|
||||
- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`(需配合 `--start`/`--end` 按时间段搜索;可再叠加 `--room-name` 在分组内过滤)
|
||||
|
||||
用户说"有空吗/忙不忙/闲忙":
|
||||
- 查询用户闲忙 → `busy search --users <USER_ID>`
|
||||
- 查询会议室闲忙 → `busy search --rooms <ROOM_ID>`
|
||||
- 用户 + 会议室一起查 → `busy search --users <USER_ID> --rooms <ROOM_ID>`
|
||||
|
||||
用户说"日程附件/给会议加文件/上传日程材料":
|
||||
- 添加 → `attachment add`(先用钉盘上传得 fileId,再 `attachment add --files <fileId>:<name>`)
|
||||
|
||||
用户说"我有几个日历/查所有日历/别人共享给我的日历/他人共享给我的日历本":
|
||||
- 列表 → `book list`(返回用户拥有和订阅的所有日历本,包括他人共享给自己的;主日历 id 固定为 `primary`)
|
||||
- 查指定日历本 → `book get --id <CALENDAR_ID>`
|
||||
- 按名称搜索日历本 → `book search --query "关键词"`
|
||||
- 修改日历本名称/描述 → `book update --id <CALENDAR_ID> --summary "新名"`
|
||||
|
||||
用户说"我的日历共享给了谁/谁能看我日历/日历权限/取消共享/把日历分享给xxx":
|
||||
- 查看我共享出去的情况(即谁有权访问我的日历) → `acl list`
|
||||
- 把我的日历共享给他人 → `acl add --user <USER_ID> --privilege reader`
|
||||
- 取消我的日历对某人的共享 → `acl delete --acl-id <ACL_ID>`(aclId 来自 `acl list`)
|
||||
|
||||
> **易混淆辨析**:`book list` 查的是"我能看哪些日历本"(包含别人共享**给我**的);`acl list` 查的是"我的日历共享**给了谁**"(我的主日历的访问控制列表)。两者方向相反,不可混用。
|
||||
|
||||
用户说"查下xxx的日程安排":
|
||||
- 查询是否有共享关系 -> `book list`
|
||||
- 场景1: 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id <xxx的日历本id> `可查到xxx完整的日程安排
|
||||
- 场景2: 共享日历本中没有来自 xxx 的。那么通过 `busy search -- <USER_ID>`,查询xxx的忙闲安排
|
||||
|
||||
## 核心工作流
|
||||
|
||||
### 创建会议 + 邀请参会人 + 预订会议室
|
||||
|
||||
`event create` 支持 `--attendees` 在创建时直接指定参会人,**自 calendar MCP v2 起**也支持 `--rooms` 在创建时一并预定会议室;旧流程的「先创建日程再 `room add`」依然有效。
|
||||
|
||||
**关键区分**:`event create --rooms` 仅在**日程尚不存在**时使用;若日程已存在(同一会话刚创建、或用户指向已有日程),必须走「给已有日程订会议室」流程(见下方),**禁止**重复 `event create`。
|
||||
|
||||
**方式一:创建时一步完成(仅当日程尚不存在时推荐)**
|
||||
|
||||
```bash
|
||||
# Step 1: 搜索空闲会议室,记下 roomId
|
||||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json
|
||||
# 若返回错误(会议室超100条),先查分组再按分组搜索:
|
||||
# dws calendar room list-groups --format json
|
||||
# dws calendar room search --start ... --end ... --group-id <GROUP_ID> --format json
|
||||
|
||||
# Step 2: 创建日程时直接指定参会人 + 会议室
|
||||
dws calendar event create --title "Q1 复盘会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||||
--attendees userId1,userId2 \
|
||||
--rooms <ROOM_ID_FROM_STEP1> --format json
|
||||
```
|
||||
|
||||
**方式二:先创建日程,再单独添加参会人 / 会议室**
|
||||
|
||||
```bash
|
||||
# Step 1: 创建日程 — 从 result.id 提取日程 ID
|
||||
dws calendar event create --title "Q1 复盘会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json
|
||||
|
||||
# Step 2: 添加参会人(必须用 Step 1 返回的 result.id)
|
||||
dws calendar attendee add --event <EVENT_ID> --attendees userId1,userId2 --format json
|
||||
|
||||
# Step 3: 搜索空闲会议室
|
||||
dws calendar room search --start ... --end ... --format json
|
||||
|
||||
# Step 4: 预定会议室
|
||||
dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID> --format json
|
||||
```
|
||||
|
||||
### 给已存在的日程加附件
|
||||
|
||||
```bash
|
||||
# Step 1: 用钉盘上传文件,得到 fileId(参见 dws drive 系列命令)
|
||||
# Step 2: 把附件挂到指定日程
|
||||
dws calendar attachment add --event <EVENT_ID> --files <FILE_ID>:report.pdf,<FILE_ID2>:slides.pptx --format json
|
||||
```
|
||||
|
||||
### 查看日程列表
|
||||
|
||||
```bash
|
||||
dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `event create` | `result.id` | attendee/room/attachment 操作的 --event |
|
||||
| `event list` | `result.events[].id`, `nextCursor` | event get/update/delete/respond 的 --id;下一页 --cursor |
|
||||
| `event suggest` | 推荐的时间段 | event create 的 --start/--end |
|
||||
| `event respond` | 响应结果 | — |
|
||||
| `event instances` | `result.events[].id`, `nextCursor` | event get/update/delete/respond 的 --id;下一页 --cursor |
|
||||
| `event share-info` | 日程分享信息(主题、组织人、地点、入会信息等) | 分享给他人(如 chat/mail 发送) |
|
||||
| `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms |
|
||||
| `room list-groups` | `groups[].groupId` | room search 的 --group-id |
|
||||
| `book list` | `id`(如 `primary`) | event list/get 的 --calendar-id, book get/update 的 --id |
|
||||
| `book get` | 日历详细信息 | — |
|
||||
| `book search` | 匹配的日历列表 | book get/update 的 --id |
|
||||
| `acl list` | `aclId` | acl delete 的 --acl-id |
|
||||
| `acl add` | 新增的权限记录 | — |
|
||||
| 钉盘上传 | 文件 `fileId` | attachment add 的 --files `<fileId>:<name>` |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 时间格式: `event create/update`、`event list`、`busy search` 和 `event suggest` 用 ISO-8601
|
||||
- 时区: `event create/update` 和 `event suggest` 支持 `--timezone` 指定 IANA 时区(如 `Asia/Shanghai`、`America/New_York`),不传默认 `Asia/Shanghai`
|
||||
- 创建日程时可通过 `--attendees` 直接指定参会人(最多500人),也可创建后用 `attendee add --attendees ...` 单独添加
|
||||
- `--attendees` 和 `--open-dingtalk-ids` 至少传一个(如果需要指定参会人)
|
||||
- 添加参会人时可通过 `--optional` 设为可选参会人(默认必选)
|
||||
- `event suggest` 根据参会人闲忙自动推荐合适时间,适合会议时间未确定时使用
|
||||
- 创建日程**支持**通过 `--rooms` 一步预定会议室(`event create --rooms roomId1,roomId2`);若创建后再加,仍可用 `room add`
|
||||
- `room search` 不带 `--group-id` 时查根目录;企业会议室超过 100 条会报错,此时需先 `room list-groups` 获取分组,再按分组逐一查询
|
||||
- `room list-groups` 支持 `--limit` / `--page` 分页(schema 类型为字符串)
|
||||
- **`event create --rooms` / `room add --rooms` 的唯一合法来源**:最近一次(同一会话、同一时段窗口)`room search` 返回体中的 `roomId`;禁止把用户自然语言会议室名当 `roomId` 传入(否则会 `roomId invalid` 等错误)
|
||||
- **搜房无结果**:在符合早停/用户限定范围内,`room search`(含按分组逐组查)全部返回空或无空闲 → 应**直接向用户报错/说明失败**并结束订房;**禁止**假设 roomId、禁止无合法 `roomId` 时调用 `room add` / `event create --rooms` 试探、禁止用 `event get` 等绕路推断 roomId
|
||||
- **自动化校验**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId`
|
||||
- **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `<fileId>:<name>` 元素逗号分隔
|
||||
- **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历
|
||||
- **分页查询**:`event list` / `event instances` 均支持 `--limit`(控制每页条数,默认/最大 100)和 `--cursor`(翻页游标);**首次查询无需传 `--cursor`**,仅当返回体中包含 `nextCursor` 时,将其作为 `--cursor` 传入可获取下一页
|
||||
- **循环日程实例**:`event instances` 用于按时间范围展开重复日程(SeriesMaster)的每一个实例;**普通非循环日程调用该命令将查不到任何实例信息**
|
||||
- **日程分享**:`event share-info` 获取日程的分享信息(主题、组织人、地点、入会信息等);`--language` 控制文案语言(中文场景传 zh-CN)
|
||||
- **日程提醒**:`event create` 支持 `--remind-minutes` 设置开始前提醒,逗号分隔多个分钟数(如 `--remind-minutes 5,10,15`),不传则默认15分钟提醒
|
||||
- **会议室分页**:`room search` 支持 `--limit`(每页条数,默认100,最大100)和 `--page`(分页起始位置,默认0),与 `room list-groups` 分页风格一致
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [calendar_today_agenda.py](../scripts/calendar_today_agenda.py) | 查看今天/明天/本周日程安排 | `python calendar_today_agenda.py today` |
|
||||
| [calendar_schedule_meeting.py](../scripts/calendar_schedule_meeting.py) | 一键创建日程+添加参会人+预定会议室;搜房失败时输出明确原因并返回非零退出码 | `python calendar_schedule_meeting.py --title "复盘会" --start "2026-03-15T14:00" --end "2026-03-15T15:00" --users userId1 --book-room` |
|
||||
| [calendar_free_slot_finder.py](../scripts/calendar_free_slot_finder.py) | 查询多人共同空闲时段 | `python calendar_free_slot_finder.py --users userId1,userId2 --date 2026-03-15` |
|
||||
|
||||
## 相关产品
|
||||
|
||||
- conference(视频会议预约) — 仅视频会议预约(返回入会链接),不含参会人/会议室管理
|
||||
- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事 userId,用于 attendee add --attendees
|
||||
@@ -0,0 +1,9 @@
|
||||
# calendar 局部意图消歧
|
||||
|
||||
本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。
|
||||
|
||||
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|
||||
|---|---|---|---|---|
|
||||
| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 |
|
||||
| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议(conference)当前 CLI 不支持 |
|
||||
| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder |
|
||||
@@ -0,0 +1,24 @@
|
||||
# calendar Lite Recipe
|
||||
|
||||
本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。
|
||||
|
||||
## #3 会议日程
|
||||
|
||||
### list-today-meetings
|
||||
|
||||
**优先**:`python scripts/calendar_today_agenda.py [today|tomorrow|week]`
|
||||
备选:`dws calendar event list --start "<今日起始ISO>" --end "<今日结束ISO>"`(须加 `--format json`)
|
||||
|
||||
### check-users-busy
|
||||
|
||||
查询多人在某时段内的闲忙(**busy**,不是用 `event list` 扫日程):
|
||||
|
||||
1. 解析用户:对每个姓名执行 `aisearch person --query "<姓名>" --dimension name` → `userId`;多人将 `userId` 用英文逗号拼接(无空格或按 [calendar.md](./calendar.md) `busy search` 要求)。
|
||||
2. 确认时段:用户须给出或可收敛为明确的 `--start` / `--end`(ISO-8601);若未给出,**先追问**起止时间,禁止用任意默认全天窗口代替用户意图。
|
||||
3. 执行:`dws calendar busy search --users <userId1,userId2,...> --start "<ISO>" --end "<ISO>" --format json`
|
||||
|
||||
详见 [calendar.md](./calendar.md) 中「查询用户闲忙状态」。
|
||||
|
||||
### start-conference
|
||||
|
||||
> 当前 CLI 不提供视频会议(conference)发起/入会/会中控制能力。触发「发起会议」「开个会」「创建会议」且**没有给出具体时间**时,不要构造 `conference` 命令;直接告知用户请在钉钉客户端操作。
|
||||
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查询多人共同空闲时段,推荐最佳会议时间
|
||||
|
||||
用法:
|
||||
python calendar_free_slot_finder.py \
|
||||
--users userId1,userId2,userId3 \
|
||||
--date 2026-03-15 \
|
||||
--duration 60
|
||||
|
||||
python calendar_free_slot_finder.py \
|
||||
--users userId1,userId2 \
|
||||
--date 2026-03-15 \
|
||||
--start-hour 9 --end-hour 18 \
|
||||
--duration 30 --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
SLOT_STEP_MIN = 30
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def fmt_iso(dt: datetime) -> str:
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
def parse_busy_intervals(
|
||||
data: Any,
|
||||
) -> List[Tuple[datetime, datetime]]:
|
||||
intervals = []
|
||||
if not data:
|
||||
return intervals
|
||||
items = []
|
||||
if isinstance(data, list):
|
||||
items = data
|
||||
elif isinstance(data, dict):
|
||||
inner = data.get('result', data)
|
||||
if isinstance(inner, list):
|
||||
items = inner
|
||||
elif isinstance(inner, dict):
|
||||
for user_data in inner.values():
|
||||
if isinstance(user_data, list):
|
||||
items.extend(user_data)
|
||||
elif isinstance(user_data, dict):
|
||||
items.extend(
|
||||
user_data.get('busyTimes', [])
|
||||
)
|
||||
for item in items:
|
||||
start_str = item.get('startTime') or item.get('start', '')
|
||||
end_str = item.get('endTime') or item.get('end', '')
|
||||
if not start_str or not end_str:
|
||||
continue
|
||||
for fmt in (
|
||||
'%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S',
|
||||
'%Y-%m-%dT%H:%M%z',
|
||||
):
|
||||
try:
|
||||
s = datetime.strptime(start_str, fmt)
|
||||
e = datetime.strptime(end_str, fmt)
|
||||
if s.tzinfo is None:
|
||||
s = s.replace(tzinfo=TZ)
|
||||
if e.tzinfo is None:
|
||||
e = e.replace(tzinfo=TZ)
|
||||
intervals.append((s, e))
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
return intervals
|
||||
|
||||
|
||||
def find_free_slots(
|
||||
day_start: datetime, day_end: datetime,
|
||||
busy: List[Tuple[datetime, datetime]],
|
||||
duration_min: int,
|
||||
) -> List[Tuple[datetime, datetime]]:
|
||||
busy_sorted = sorted(busy, key=lambda x: x[0])
|
||||
merged: List[Tuple[datetime, datetime]] = []
|
||||
for s, e in busy_sorted:
|
||||
if merged and s <= merged[-1][1]:
|
||||
merged[-1] = (merged[-1][0], max(merged[-1][1], e))
|
||||
else:
|
||||
merged.append((s, e))
|
||||
|
||||
free: List[Tuple[datetime, datetime]] = []
|
||||
cursor = day_start
|
||||
for bs, be in merged:
|
||||
if cursor < bs:
|
||||
gap = (bs - cursor).total_seconds() / 60
|
||||
if gap >= duration_min:
|
||||
free.append((cursor, bs))
|
||||
cursor = max(cursor, be)
|
||||
if cursor < day_end:
|
||||
gap = (day_end - cursor).total_seconds() / 60
|
||||
if gap >= duration_min:
|
||||
free.append((cursor, day_end))
|
||||
return free
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='查询多人共同空闲时段'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--users', required=True, help='用户 ID 列表,逗号分隔'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--date', required=True, help='查询日期 YYYY-MM-DD'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--duration', type=int, default=60,
|
||||
help='会议时长(分钟),默认 60',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--start-hour', type=int, default=9,
|
||||
help='工作日开始小时,默认 9',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--end-hour', type=int, default=18,
|
||||
help='工作日结束小时,默认 18',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run', action='store_true', help='仅显示命令'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
date = datetime.strptime(args.date, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
print('错误:日期格式应为 YYYY-MM-DD')
|
||||
sys.exit(1)
|
||||
|
||||
day_start = date.replace(
|
||||
hour=args.start_hour, tzinfo=TZ
|
||||
)
|
||||
day_end = date.replace(hour=args.end_hour, tzinfo=TZ)
|
||||
|
||||
data = run_dws([
|
||||
'calendar', 'busy', 'search',
|
||||
'--users', args.users,
|
||||
'--start', fmt_iso(day_start),
|
||||
'--end', fmt_iso(day_end),
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
|
||||
if args.dry_run:
|
||||
return
|
||||
|
||||
busy = parse_busy_intervals(data)
|
||||
free = find_free_slots(day_start, day_end, busy, args.duration)
|
||||
|
||||
users_list = args.users.split(',')
|
||||
print(f"\n🕐 空闲时段查询 ({args.date})")
|
||||
print(f" 参与人: {len(users_list)} 人")
|
||||
print(f" 会议时长: {args.duration} 分钟")
|
||||
print(f" 工作时间: {args.start_hour}:00 ~ "
|
||||
f"{args.end_hour}:00")
|
||||
print('=' * 50)
|
||||
|
||||
if not free:
|
||||
print(' ❌ 该日无共同空闲时段')
|
||||
return
|
||||
|
||||
print(f"\n✅ 找到 {len(free)} 个可用时段:\n")
|
||||
for i, (s, e) in enumerate(free, 1):
|
||||
gap_min = int((e - s).total_seconds() / 60)
|
||||
label = '⭐ 推荐' if i == 1 else f' 备选{i-1}'
|
||||
print(f" {label} {s.strftime('%H:%M')} ~ "
|
||||
f"{e.strftime('%H:%M')} ({gap_min}分钟)")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
一键创建日程(可选:带参与者 + 预定空闲会议室)
|
||||
|
||||
流程:
|
||||
1. 若需预定会议室 (--book-room),先搜索空闲会议室;无可用则提前报错退出
|
||||
2. 使用 event create 一次性完成日程创建 + 添加参与者 + 预定会议室
|
||||
|
||||
用法:
|
||||
python calendar_schedule_meeting.py \
|
||||
--title "Q1 复盘会" \
|
||||
--start "2026-03-15T14:00" \
|
||||
--end "2026-03-15T15:00" \
|
||||
--users userId1,userId2 \
|
||||
--book-room
|
||||
|
||||
python calendar_schedule_meeting.py --dry-run \
|
||||
--title "测试" --start "2026-03-15T14:00" --end "2026-03-15T15:00"
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import List, Any, Optional, Tuple
|
||||
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return {'dry_run': True}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f" ✗ 错误:{result.stderr.strip()}")
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f" ✗ 错误:{e}")
|
||||
return None
|
||||
|
||||
|
||||
def normalize_time(time_str: str) -> str:
|
||||
for fmt in ('%Y-%m-%dT%H:%M', '%Y-%m-%d %H:%M',
|
||||
'%Y-%m-%dT%H:%M:%S'):
|
||||
try:
|
||||
dt = datetime.strptime(time_str, fmt)
|
||||
dt = dt.replace(tzinfo=TZ)
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
except ValueError:
|
||||
continue
|
||||
if '+' in time_str or time_str.endswith('Z'):
|
||||
return time_str
|
||||
raise ValueError(f"无法解析时间:{time_str}")
|
||||
|
||||
|
||||
def parse_group_ids(raw: str) -> List[str]:
|
||||
if raw is None:
|
||||
return []
|
||||
return [part.strip() for part in raw.split(',') if part.strip()]
|
||||
|
||||
|
||||
def extract_room_candidates(payload: Any) -> Tuple[List[dict], str]:
|
||||
candidates: Any = []
|
||||
if isinstance(payload, list):
|
||||
candidates = payload
|
||||
elif isinstance(payload, dict):
|
||||
if isinstance(payload.get('rooms'), list):
|
||||
candidates = payload.get('rooms', [])
|
||||
elif isinstance(payload.get('result'), dict):
|
||||
nested = payload.get('result', {})
|
||||
if isinstance(nested.get('rooms'), list):
|
||||
candidates = nested.get('rooms', [])
|
||||
elif isinstance(nested.get('result'), list):
|
||||
candidates = nested.get('result', [])
|
||||
elif isinstance(payload.get('result'), list):
|
||||
candidates = payload.get('result', [])
|
||||
|
||||
if not isinstance(candidates, list):
|
||||
return [], '返回结构中未找到会议室列表'
|
||||
|
||||
valid_rooms: List[dict] = []
|
||||
placeholder_count = 0
|
||||
for item in candidates:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
if item.get('roomId') or item.get('id'):
|
||||
valid_rooms.append(item)
|
||||
continue
|
||||
if item.get('labels') is None and len(item) == 1:
|
||||
placeholder_count += 1
|
||||
|
||||
if valid_rooms:
|
||||
return valid_rooms, f'返回 {len(valid_rooms)} 个有效会议室'
|
||||
if placeholder_count:
|
||||
return [], '仅返回占位结果(如 labels:null),无有效 roomId'
|
||||
if candidates:
|
||||
return [], '返回了对象列表,但均不含有效 roomId'
|
||||
return [], '未返回任何会议室'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='一键创建日程(可选:带参与者 + 预定会议室)'
|
||||
)
|
||||
parser.add_argument('--title', required=True, help='日程标题')
|
||||
parser.add_argument('--start', required=True, help='开始时间')
|
||||
parser.add_argument('--end', required=True, help='结束时间')
|
||||
parser.add_argument('--desc', default='', help='日程描述')
|
||||
parser.add_argument('--users', default='', help='参与者 userId,逗号分隔')
|
||||
parser.add_argument(
|
||||
'--book-room', action='store_true', help='自动搜索并预定空闲会议室'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--room-group-id', default='',
|
||||
help='允许搜索的 groupId;同一地点请只传最相关 group,多个仅用于用户明确允许的多个地点'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run', action='store_true', help='仅显示命令'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
start_iso = normalize_time(args.start)
|
||||
end_iso = normalize_time(args.end)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
sys.exit(1)
|
||||
|
||||
# ── Step 1: 若需预定会议室,先搜索空闲会议室 ──────────────────
|
||||
room_id: Optional[str] = None
|
||||
room_name: Optional[str] = None
|
||||
|
||||
if args.book_room:
|
||||
print('🏢 搜索空闲会议室...')
|
||||
group_ids = parse_group_ids(args.room_group_id)
|
||||
search_scopes = group_ids or [None]
|
||||
selected_room = None
|
||||
failure_reasons: List[str] = []
|
||||
|
||||
for group_id in search_scopes:
|
||||
scope_label = f'group {group_id}' if group_id else '根目录'
|
||||
print(f' - 查询范围: {scope_label}')
|
||||
search_args = [
|
||||
'calendar', 'room', 'search',
|
||||
'--start', start_iso,
|
||||
'--end', end_iso,
|
||||
'--available',
|
||||
'--format', 'json',
|
||||
]
|
||||
if group_id:
|
||||
search_args.extend(['--group-id', group_id])
|
||||
rooms_data = run_dws(search_args, dry_run=args.dry_run)
|
||||
|
||||
if args.dry_run:
|
||||
continue
|
||||
if not rooms_data:
|
||||
failure_reasons.append(f'{scope_label}: room search 执行失败')
|
||||
continue
|
||||
|
||||
rooms, detail = extract_room_candidates(rooms_data)
|
||||
if rooms:
|
||||
selected_room = rooms[0]
|
||||
break
|
||||
failure_reasons.append(f'{scope_label}: {detail}')
|
||||
|
||||
if not args.dry_run:
|
||||
if selected_room:
|
||||
room_id = selected_room.get('roomId') or selected_room.get('id')
|
||||
room_name = selected_room.get('roomName') or selected_room.get('name')
|
||||
print(f' ✓ 找到空闲会议室: {room_name} ({room_id})')
|
||||
else:
|
||||
print(f' ✗ {start_iso} ~ {end_iso} 时段内无可用会议室')
|
||||
for reason in failure_reasons:
|
||||
print(f' - {reason}')
|
||||
print(' 请向用户汇报失败,或询问是否放宽范围/改时间。')
|
||||
sys.exit(2)
|
||||
|
||||
# ── Step 2: 一次性创建日程(含参与者 + 会议室) ────────────────
|
||||
print('\n📅 创建日程...')
|
||||
create_args = [
|
||||
'calendar', 'event', 'create',
|
||||
'--title', args.title,
|
||||
'--start', start_iso,
|
||||
'--end', end_iso,
|
||||
'--format', 'json',
|
||||
]
|
||||
if args.desc:
|
||||
create_args.extend(['--desc', args.desc])
|
||||
if args.users:
|
||||
create_args.extend(['--attendees', args.users])
|
||||
if room_id:
|
||||
create_args.extend(['--rooms', str(room_id)])
|
||||
|
||||
result = run_dws(create_args, dry_run=args.dry_run)
|
||||
if not result:
|
||||
sys.exit(1)
|
||||
|
||||
# 解析响应
|
||||
event_id = None
|
||||
if not args.dry_run and isinstance(result, dict):
|
||||
# MCP 响应通常嵌套在 result 字段内: {"result": {"id": "..."}}
|
||||
inner = result.get('result', result)
|
||||
if isinstance(inner, dict):
|
||||
event_id = inner.get('eventId') or inner.get('id')
|
||||
else:
|
||||
event_id = result.get('eventId') or result.get('id')
|
||||
|
||||
# 输出结果摘要
|
||||
parts = []
|
||||
if event_id:
|
||||
parts.append(f'eventId: {event_id}')
|
||||
if args.users:
|
||||
parts.append(f'参与者: {args.users}')
|
||||
if room_name:
|
||||
parts.append(f'会议室: {room_name}')
|
||||
detail_str = f" ({', '.join(parts)})" if parts else ''
|
||||
print(f' ✓ 日程已创建{detail_str}')
|
||||
|
||||
print('\n✅ 完成!')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查看今天/明天/本周的日程安排
|
||||
|
||||
用法:
|
||||
python calendar_today_agenda.py # 今天
|
||||
python calendar_today_agenda.py today # 今天
|
||||
python calendar_today_agenda.py tomorrow # 明天
|
||||
python calendar_today_agenda.py week # 本周
|
||||
python calendar_today_agenda.py --dry-run # 仅显示命令
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_range(scope: str):
|
||||
now = datetime.now(TZ)
|
||||
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if scope == 'today':
|
||||
return today, today + timedelta(days=1)
|
||||
elif scope == 'tomorrow':
|
||||
t = today + timedelta(days=1)
|
||||
return t, t + timedelta(days=1)
|
||||
elif scope == 'week':
|
||||
ws = today - timedelta(days=today.weekday())
|
||||
return ws, ws + timedelta(days=7)
|
||||
return today, today + timedelta(days=1)
|
||||
|
||||
|
||||
def fmt_iso(dt: datetime) -> str:
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
def fmt_time(iso_str: str) -> str:
|
||||
if not iso_str:
|
||||
return '??:??'
|
||||
try:
|
||||
for fmt in ('%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S'):
|
||||
try:
|
||||
dt = datetime.strptime(iso_str, fmt)
|
||||
return dt.strftime('%H:%M')
|
||||
except ValueError:
|
||||
continue
|
||||
return iso_str[:16]
|
||||
except Exception:
|
||||
return iso_str[:16]
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
args = [a for a in sys.argv[1:] if a != '--dry-run']
|
||||
scope = args[0] if args else 'today'
|
||||
if scope not in ('today', 'tomorrow', 'week'):
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
start, end = get_range(scope)
|
||||
data = run_dws([
|
||||
'calendar', 'event', 'list',
|
||||
'--start', fmt_iso(start),
|
||||
'--end', fmt_iso(end),
|
||||
'--format', 'json',
|
||||
], dry_run=dry_run)
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
events = []
|
||||
if isinstance(data, list):
|
||||
events = data
|
||||
elif isinstance(data, dict):
|
||||
inner = data.get('result', data)
|
||||
if isinstance(inner, dict):
|
||||
events = inner.get('events', [])
|
||||
elif isinstance(inner, list):
|
||||
events = inner
|
||||
|
||||
label = {'today': '今天', 'tomorrow': '明天', 'week': '本周'
|
||||
}.get(scope, scope)
|
||||
print(f"\n📅 {label}日程 ({start.strftime('%m-%d')} ~ "
|
||||
f"{end.strftime('%m-%d')})")
|
||||
print('=' * 50)
|
||||
|
||||
if not events:
|
||||
print(' ✅ 暂无日程,自由安排!')
|
||||
return
|
||||
|
||||
for e in events:
|
||||
if not isinstance(e, dict):
|
||||
print(f" 🕐 {e}")
|
||||
continue
|
||||
title = e.get('summary') or e.get('title', '无标题')
|
||||
s = e.get('start', {})
|
||||
ed = e.get('end', {})
|
||||
start_t = fmt_time(
|
||||
s.get('dateTime', '') if isinstance(s, dict) else str(s)
|
||||
)
|
||||
end_t = fmt_time(
|
||||
ed.get('dateTime', '') if isinstance(ed, dict) else str(ed)
|
||||
)
|
||||
loc = e.get('location', {})
|
||||
loc_str = (loc.get('displayName', '')
|
||||
if isinstance(loc, dict) else str(loc or ''))
|
||||
line = f" 🕐 {start_t}-{end_t} {title}"
|
||||
if loc_str:
|
||||
line += f" 📍{loc_str}"
|
||||
print(line)
|
||||
|
||||
print(f"\n合计: {len(events)} 场日程")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user