feat(skills): align Wukong multi-skill docs

This commit is contained in:
玉澜
2026-07-15 01:17:45 +08:00
parent 4e59f9aa7a
commit 56116bf99e
278 changed files with 14727 additions and 13541 deletions
+16 -18
View File
@@ -1,6 +1,6 @@
---
name: dingtalk-agoal
description: 钉钉 Agoal 目标管理。Use when 用户说 目标管理/Agoal/战略解码/经营合约/计分卡/OKR/目标模板/周月报提交统计/跟催。Distinct from dingtalk-todo(待办任务)、dingtalk-report(日志)。命令前缀:dws agoal。
description: 钉钉 Agoal 目标管理与经营目标跟进。Use when 用户说目标管理/战略解码/经营合约/计分卡/用户目标/OKR拆解/更新目标/查看经营合约/目标进展/目标负责人/周月报统计/周报提交情况/跟催/迟交/未提交。Distinct from dingtalk-todo(个人任务待办)、dingtalk-report(日报周报内容)、dingtalk-oa(审批流)。命令前缀:dws agoal。
cli_version: ">=0.2.14"
metadata:
category: product
@@ -10,34 +10,32 @@ metadata:
- dws
---
# 钉钉 Agoal Skill
# 钉钉目标管理 Skill
> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。
> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH.
## 前置条件 — 执行操作前必读
> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 命令参考:[agoal.md](references/agoal.md)。
## 意图表
| 用户说 | 命令 |
|--------|------|
| "查战略解码 / OGSM" | `dws agoal strategy list --scope-type <DEPT|PERSONAL> --scope-id <ID>` |
| "看战略解码详情" | `dws agoal strategy detail --profile-id <PROFILE_ID>` |
| "更新战略解码" | 先 detail,基于完整数据修改后 `dws agoal strategy update ...` |
| "查经营合约 / KPI合约" | `dws agoal contract list/detail/fields` |
| "更新经营合约" | 先 detail,基于完整 dimensions 修改后 `dws agoal contract update ...` |
| "查计分卡" | `dws agoal scorecard detail --selected-time <ISO> --dept-id <DEPT_ID>` |
| "我的目标 / 个人目标" | `dws agoal user rules` → `dws agoal user objectives` |
| "周月报提交统计 / 跟催 / 迟交 / 未提交" | `dws agoal report list-statistics` → `dws agoal report submit-detail` |
| "目标模板" | `dws agoal obj-template list/create-or-update` |
| "查战略解码 / 战略列表" | `dws agoal strategy list` |
| "查战略详情" | `dws agoal strategy detail --profile-id <id>` |
| "更新战略解码" | 先 `strategy detail` 获取完整内容,再 `strategy update` 覆盖更新 |
| "查经营合约" | `dws agoal contract list` / `contract detail` |
| "更新经营合约" | 先 `contract detail` 获取完整内容,再 `contract update` 覆盖更新 |
| "查计分卡" | `dws agoal scorecard detail` / `scorecard entity-detail` |
| "更新计分卡" | 先查详情,再按 [agoal.md](references/agoal.md) 覆盖更新 |
| "查周月报统计/提交情况/跟催" | `dws agoal report list-statistics` / `report submit-detail` |
## 写操作硬约束
## 硬约束
- `strategy update` / `contract update` / `scorecard update` / `obj-template create-or-update` 都是覆盖式写入。
- 必须先读取现有详情或模板列表,在原数据基础上修改;禁止只传局部字段覆盖。
- 执行前必须向用户展示目标对象、修改字段和最终命令参数摘要,并等待确认。
- `strategy update`、`contract update`、`scorecard update` 都是覆盖式更新,必须先查询详情,在返回数据基础上修改后再提交。
- 所有命令带 `--format json`;涉及写操作时回查确认。
+232 -36
View File
@@ -14,64 +14,260 @@ Agoal 是钉钉目标管理工具,支持战略解码、经营合约、计分
|------|------|----------|------|
| `strategy list` | 获取战略解码列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id |
| `strategy detail` | 获取战略解码详情 | `--profile-id` | 根据战略解码 id 查询 |
| `strategy update` | 更新战略解码 | `--profile-id` `--content` | 覆盖逻辑,必须基于查询返回的老数据修改后再传入;`--content` 为 JSON 数组 |
| `strategy update` | 更新战略解码 | `--profile-id` `--content` | **覆盖逻辑,必须基于查询返回的老数据修改后再传入**;`--content` 为 JSON 数组 |
> **`strategy update` 是覆盖式更新**:一定要先 `strategy detail` 获取完整数据,在原数据基础上修改后再传入,会根据战略解码id进行对应的修改。
#### strategy update --content 实体字段说明
| 字段 | 说明 |
|------|------|
| `id` | 实体 id |
| `title` | 标题对象,如 `{"title":"标题文本"}` |
| `linkEntityId` | 所属实体 ID(查询接口中有值时必须回传) |
| `entityType` | 类型枚举:`OGSM_OBJECTIVE`(目的)、`OGSM_GOAL`(目标)、`OGSM_STRATEGY`(策略)、`OGSM_MEASUREMENT`(衡量标准)、`OGSM_TACTICS`(行动方案) |
| `status` | 状态枚举:`NORMAL`(正常)、`PRE_PUBLISH_THEN_UPDATE`(预发布更新)、`PRE_PUBLISH_THEN_CREATE`(预发布新增)、`PRE_PUBLISH_THEN_DELETE`(预发布删除) |
| `supporters` | 承接人数组 `[{type, dingId, staffId}]`;type: `USER`/`DEPARTMENT`;staffId 在 type=USER 时必填 |
| `indicators` | 关键指标 id 字符串数组 |
| `linkSources` | 资源关联数组 `[{id, linkType, linkId, source, objectId, keyResultId}]`;linkType: project/task/campaign/product/doc/standard;source: teambition |
| `executors` | 人员 dingId 字符串数组 |
| `teams` | 部门 dingId 字符串数组 |
### contract (经营合约管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `contract list` | 获取经营合约列表 | `--scope-type` `--scope-id` | scopeType: DEPT/PERSONAL;scope-id 为 scope-type 对应的部门 id 或用户 id |
| `contract fields` | 获取经营合约字段列表 | - | 获取组织下经营合约的字段配置 |
| `contract fields` | 获取经营合约字段列表 | — | 获取组织下经营合约的字段配置 |
| `contract detail` | 获取经营合约详情 | `--contract-id` | 根据合约 id 查询 |
| `contract update` | 更新经营合约 | `--contract-id` `--dimensions` | 覆盖逻辑;可选 `--audit-config`、`--objective-template` |
| `contract update` | 更新经营合约 | `--contract-id` `--dimensions` | **覆盖逻辑**;可选 `--audit-config`、`--objective-template` |
> **`contract update` 同样是覆盖式更新**:必须基于 `contract detail` 返回的数据修改后再传入。
#### contract update --dimensions 维度字段说明
| 字段 | 说明 |
|------|------|
| `id` | 维度 id |
| `title` | 维度名称 |
| `description` | 维度描述 |
| `weight` | 维度权重 |
| `objectives` | 目标列表 |
| `dimensionConfig` | 维度配置 |
| `children` | 子维度列表 |
#### contract update 可选参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `--audit-config` | 审批配置 JSON | `{"needAudit":true,"processTemplateId":"TPL_ID"}` |
| `--objective-template` | 合约模板 JSON | `{"id":"TPL_ID","title":"模板名称"}` |
### scorecard (计分卡管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `scorecard detail` | 获取计分卡详情 | `--selected-time` `--dept-id` | selectedTime 为 ISO-8601 字符串 |
| `scorecard detail` | 获取计分卡详情 | `--selected-time` `--dept-id` | selectedTime 为 ISO-8601 字符串,如 `"2026-01-01T00:00:00+08:00"` |
| `scorecard entity-detail` | 获取计分卡实体详情 | `--sc-id` `--entity-id` | 根据计分卡 id 和实体 id 查询 |
| `scorecard update` | 更新计分卡 | `--dept-id` `--selected-time` `--id` `--tracking-period-type` `--content` | trackingPeriodType: MONTHLY/QUARTERLY |
### user / report / obj-template
#### selectedTime 时间说明
| 命令 | 用途 | 必填参数 |
|------|------|----------|
| `user rules` | 获取用户规则周期列表 | - |
| `user objectives` | 查询用户目标列表 | `--user-id` `--rule-id` `--period-ids` |
| `report list-statistics` | 获取周月报数据跟催列表 | - |
| `report submit-detail` | 获取周月报规则提交详情 | `--template-id` `--submit-state` |
| `obj-template list` | 获取目标模板列表 | - |
| `obj-template create-or-update` | 新增或更新目标模板 | `--dimensions` |
`--selected-time` 接受 ISO-8601 字符串,传入对应周期起始时刻:
- **2026年** → `"2026-01-01T00:00:00+08:00"`
- **2026年5月** → `"2026-05-01T00:00:00+08:00"`
#### scorecard update --content 维度字段说明
| 字段 | 说明 |
|------|------|
| `id` | 维度 id |
| `title` | 维度名称 |
| `items` | 指标或关键事项列表 |
items 每项包含:
| 字段 | 说明 |
|------|------|
| `id` | 实体 id |
| `title` | 名称 |
| `reference` | 参考信息 |
| `start` | 起始值 |
| `target` | 目标值 |
| `executors` | 负责人列表,每项包含 `openId` |
#### trackingPeriodType 枚举
| 值 | 说明 |
|------|------|
| `MONTHLY` | 月度追踪 |
| `QUARTERLY` | 季度追踪 |
### user (用户目标管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `user rules` | 获取用户规则周期列表 | — | 可选 `--user-id`,不传则默认取操作人自己 |
| `user objectives` | 查询用户目标列表 | `--user-id` `--rule-id` `--period-ids` | `--period-ids` 为逗号分隔的周期 id 列表 |
### obj-template (目标模板管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `obj-template list` | 获取目标模板列表 | — | 可选 `--keyword` 搜索关键词、`--page` 页码、`--page-size` 每页数量 |
| `obj-template create-or-update` | 新增或更新目标模板 | `--dimensions` | **覆盖逻辑**;新增时 `--title` 必填;更新时 `--template-id` 必填,dimensions 必须基于老数据修改 |
> **`obj-template create-or-update` 同样是覆盖式更新**:更新时必须基于 `obj-template list` 返回的数据修改后再传入。新增时建议先参考已存在的模板数据再构建 dimensions。
#### obj-template create-or-update 参数说明
| 参数 | 说明 | 类型 |
|------|------|------|
| `--template-id` | 模板 id(更新时必填) | string |
| `--title` | 模板标题(新增时必填) | string |
| `--objective-weight` | 是否启用目标权重 | bool |
| `--dimension-weight` | 是否启用维度权重 | bool |
| `--compute-by-weight` | 维度是否参与计算 | bool |
| `--dimensions` | 模板关联的维度 JSON 字符串 | string |
### report (周月报管理)
| 命令 | 用途 | 必填参数 | 备注 |
|------|------|----------|------|
| `report list-statistics` | 获取周月报数据跟催列表 | — | 返回各规则的人员提交情况统计(按时/迟交/未提交人数);可选 `--keyword` 搜索规则名称 |
| `report submit-detail` | 获取周月报规则提交详情 | `--template-id` `--submit-state` | submitState: `ON_TIME`(按时)/`LATE`(迟交)/`NOT_SUBMITTED`(未提交);可选 `--query-date`(ISO-8601)、`--page`、`--page-size`、`--keyword`(搜索员工名称) |
#### report submit-detail --submit-state 枚举
| 值 | 说明 |
|------|------|
| `ON_TIME` | 按时提交 |
| `LATE` | 迟交 |
| `NOT_SUBMITTED` | 未提交 |
## 意图判断
- "战略解码/战略目标/OGSM" → `strategy list/detail/update`
- "经营合约/合约/KPI合约" → `contract list/fields/detail/update`
- "计分卡/scorecard/绩效看板" → `scorecard detail/entity-detail/update`
- "目标/OKR/我的目标/个人目标" → `user rules/objectives`
- "目标模板/模板管理" → `obj-template list/create-or-update`
- "周月报/周报统计/提交情况/跟催/迟交/未提交" → `report list-statistics/submit-detail`
用户说"战略解码/战略目标/OGSM":
- 查看/列表 → `strategy list`
- 详情 → `strategy detail`
- 修改/更新 → `strategy update`(先查后改)
## 常用命令
用户说"经营合约/合约/KPI合约":
- 查看/列表 → `contract list`
- 字段配置 → `contract fields`
- 详情 → `contract detail`
- 修改/更新 → `contract update`(先查后改)
用户说"计分卡/scorecard/绩效看板":
- 查看详情 → `scorecard detail`
- 实体详情 → `scorecard entity-detail`
- 修改/更新 → `scorecard update`
用户说"目标/OKR/我的目标/个人目标":
- 规则周期 → `user rules`
- 目标列表 → `user objectives`
用户说"目标模板/模板管理":
- 查看模板列表 → `obj-template list`
- 新增模板 → `obj-template create-or-update --title "模板名称"`
- 更新模板 → `obj-template create-or-update --template-id TPL_ID`
用户说"周月报/周报统计/提交情况/跟催/迟交/未提交":
- 查看提交统计列表 → `report list-statistics`
- 查看某规则的提交详情(按时/迟交/未提交人员明细) → `report submit-detail`
## 核心工作流
```bash
dws agoal strategy list --scope-type DEPT --scope-id DEPT_ID --format json
dws agoal strategy detail --profile-id PROFILE_ID --format json
dws agoal contract list --scope-type PERSONAL --scope-id USER_ID --format json
dws agoal contract fields --format json
dws agoal contract detail --contract-id CONTRACT_ID --format json
dws agoal scorecard detail --selected-time "2026-01-01T00:00:00+08:00" --dept-id DEPT_ID --format json
dws agoal user rules --user-id USER_ID --format json
dws agoal user objectives --user-id USER_ID --rule-id RULE_ID --period-ids "period1,period2" --format json
dws agoal report list-statistics --format json
dws agoal report submit-detail --template-id TPL_ID --submit-state ON_TIME --format json
dws agoal obj-template list --keyword "业绩" --format json
# 1. 查看战略解码列表(按部门)
dws agoal strategy list --scope-type DEPT --scope-id DEPT_ID
# 1.1 查看战略解码列表(按个人)
dws agoal strategy list --scope-type PERSONAL --scope-id USER_ID
# 2. 查看战略解码详情
dws agoal strategy detail --profile-id PROFILE_ID
# 3. 更新战略解码(基于详情返回的数据修改后传入)
dws agoal strategy update --profile-id PROFILE_ID \
--content '[{"id":"entity1","title":{"title":"新目标"},"entityType":"OGSM_OBJECTIVE","status":"NORMAL","executors":["dingId1"]}]'
# 4. 查看经营合约列表(按个人)
dws agoal contract list --scope-type PERSONAL --scope-id USER_ID
# 4.1 查看经营合约列表(按部门)
dws agoal contract list --scope-type DEPT --scope-id DEPT_ID
# 5. 查看经营合约字段列表
dws agoal contract fields
# 6. 查看经营合约详情
dws agoal contract detail --contract-id CONTRACT_ID
# 7 更新经营合约(基于详情返回的数据修改后传入)
dws agoal contract update --contract-id CONTRACT_ID \
--dimensions '[{"id":"dim1","title":"维度名称","objectives":[...]}]'
# 8. 查看计分卡详情
dws agoal scorecard detail --selected-time "2025-01-01T00:00:00+08:00" --dept-id DEPT_ID
# 9. 查看计分卡实体详情
dws agoal scorecard entity-detail --sc-id SC_ID --entity-id ENTITY_ID
# 10. 更新计分卡
dws agoal scorecard update --dept-id DEPT_ID --selected-time "2025-01-01T00:00:00+08:00" --id SC_ID --tracking-period-type MONTHLY --content '[{"id":"dim1","title":"业绩","items":[{"id":"item1","title":"收入","target":"100"}]}]'
# 11. 查看用户规则 → 提取 ruleId 和 periodId
dws agoal user rules --user-id USER_ID
# 12. 查看用户目标
dws agoal user objectives --user-id USER_ID --rule-id RULE_ID --period-ids "period1,period2"
# 13. 查看周月报提交统计列表
dws agoal report list-statistics
# 13.1 按关键词搜索规则
dws agoal report list-statistics --keyword "周报规则"
# 14. 查看某规则的按时提交详情
dws agoal report submit-detail --template-id TPL_ID --submit-state ON_TIME
# 14.1 查看迟交详情(带分页和日期)
dws agoal report submit-detail --template-id TPL_ID --submit-state LATE --query-date "2026-06-18T00:00:00+08:00" --page 1 --page-size 20
# 15. 获取目标模板列表
dws agoal obj-template list
# 15.1 搜索目标模板
dws agoal obj-template list --keyword "业绩"
# 16. 新增目标模板
dws agoal obj-template create-or-update --title "业绩模板" --objective-weight --dimension-weight --dimensions '[...]'
# 16.1 更新目标模板
dws agoal obj-template create-or-update --template-id TPL_ID --title "业绩模板" --dimensions '[...]'
```
## 安全规则
## 上下文传递表
- 所有 update / create-or-update 命令都是覆盖逻辑:必须先用对应 detail/list 查询完整数据,在原数据基础上修改后再传入。
- 写入类命令执行前必须展示变更摘要并等待用户确认。
- `--selected-time` / `--query-date` 接受 ISO-8601 字符串。
- `--period-ids` 为逗号分隔字符串。
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `strategy list` | `profileId` | `strategy detail` / `strategy update` 的 `--profile-id` |
| `strategy detail` | 完整实体数据 | `strategy update` 的 `--content`(基于此修改) |
| `contract list` | `contractId` | `contract detail` / `contract update` 的 `--contract-id` |
| `contract detail` | 完整维度数据 | `contract update` 的 `--dimensions`(基于此修改) |
| `scorecard detail` | `scId`、`entityId` | `scorecard entity-detail` / `scorecard update` 的 `--id` |
| `user rules` | `ruleId`、`periodIds` | `user objectives` 的 `--rule-id` `--period-ids` |
| `report list-statistics` | `templateId`(列表项中) | `report submit-detail` 的 `--template-id` |
| `obj-template list` | `templateId` | `obj-template create-or-update` 的 `--template-id`(更新时) |
## 注意事项
- **所有 update 命令都是覆盖逻辑**:必须先用对应的 detail/list 查询到完整数据,在原数据基础上修改后再传入,否则未传入的数据会被删除
- 所有命令支持可选参数 `--request-id`
- `--scope-type` 仅支持 `DEPT`(按部门)和 `PERSONAL`(按个人)两种
- `--selected-time` 接受 ISO-8601 字符串(如 `"2026-01-01T00:00:00+08:00"`)
- `--period-ids` 为逗号分隔的字符串,如 `"period1,period2"`
- `report submit-detail` 的 `--query-date` 接受 ISO-8601 字符串(如 `"2026-06-18T00:00:00+08:00"`),不传则默认当天
- `report submit-detail` 的 `--page` 和 `--page-size` 默认为 1 和 10,不传时由服务端使用默认值
+45 -7
View File
@@ -1,6 +1,6 @@
---
name: dingtalk-aisearch
description: AI 搜问 - 搜人首选入口(按姓名/部门/职位/职责/上下级/手机号/工号维度)。Use when 用户说 找同事/找人/谁负责XX/XX的负责人是谁/查上级/查下级/团队成员/XX工号是谁/XX手机号。Distinct from dingtalk-contact(精确按 userId 查详情)。命令前缀:dws aisearch。
description: AI 搜问——跨源语义检索/发现(只做找到/定位,不做读写;具体读写各归 doc/report/mail/contact)。Use when 找同事/谁负责/查上下级/查工号手机号、跨文档消息邮件听记的内容检索、"我发过/收到过"行为回溯。模糊找人拿 userId 后精确查走 dingtalk-contact。命令前缀:dws aisearch。
cli_version: ">=0.2.14"
metadata:
category: product
@@ -10,17 +10,16 @@ metadata:
- dws
---
# 钉钉 AI 搜问(搜人)Skill
# 钉钉 AI 搜问 Skill
> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。
> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH.
## 前置条件 — 执行操作前必读
> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 命令参考:[aisearch.md](references/aisearch.md)。
## 意图表
@@ -32,8 +31,44 @@ metadata:
| "张三的上级 / 下级" | `dws aisearch person --keyword "张三" --dimension supervisor`(或 `subordinate`) |
| "X 部门有哪些人" | `dws aisearch person --keyword "<部门>" --dimension department` |
| "工号 12345 是谁 / 138xxxx 手机号是谁" | `dws aisearch person --keyword "<工号>" --dimension jobNumber` / `dws aisearch person --keyword "<手机号>" --dimension phone` |
| "最近 OKR 相关邮件 / 项目相关文档" | `dws aisearch enterprise --queries "<主题>" --types mail/document --time-range "<时间>"` |
| "我发过/创建过/分享过/收到过什么" | `dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向>` |
## 评测高频硬约束
## 标准 SOP(必遵流程)
> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。
### SOP-1 搜人 → 拿 userId(search-person)
**触发**:找人/谁负责/查上下级/部门成员/工号或手机号反查。
1. **定维度(必须)**:姓名→`name`、"谁负责 XX"→`duty`、部门成员→`department`、上级/下级→`supervisor`/`subordinate`、工号→`jobNumber`、手机号→`phone`;不确定→`all`。`--keyword` 必须按用户原文**完整保真**,切勿截断、改昵称、扩同音字。
2. **执行(必须)**:`dws aisearch person --keyword "<完整值>" --dimension <维度> --format json`。
3. **解析(必须)**:从 JSON 取 `userId` / `openDingTalkId`;**多候选必须输出让用户选,禁止默认取第一个、禁止编造**未返回的人员字段。
4. **衔接(必须)**:要邮箱/部门/职位/主管等详情 → 切 `dingtalk-contact` 执行 `dws contact user get --ids <userId> --format json`;发消息 → `dingtalk-chat`;发 DING → `dingtalk-ding`。
5. **失败(必须)**:未命中最多换 1 个维度重试一次(如 `name`→`department`/`jobNumber`/`phone`),仍保留完整目标值;仍无果**必须如实告知**。
**禁止**:用半截姓名扩大搜索、跳过 `--format json`、取首个候选、凭空补全人员信息。
### SOP-2 跨源搜内容(search-content)
**触发**:跨文档/邮件/消息按主题找内容。
1. **执行(必须)**:`dws aisearch enterprise --queries "<主题>" --types <document,mail,...> --time-range "<时间>" --format json`;多主题逗号分隔。
2. **衔接(必须)**:按命中来源切下游读写——文档→`dingtalk-doc`、邮件→`dingtalk-mail`、消息→`dingtalk-chat`。**aisearch 只负责"找到",不做读写。**
**禁止**:把 aisearch 当作读写入口、跳过下游 skill 直接改数据。
### SOP-3 行为回溯(search-behavior)
**触发**:"我发过/收到过/创建过/分享过什么"。
1. **执行(必须)**:`dws aisearch behavior --queries "<主题>" --behavior-type <动作> --direction <方向> --format json`。
2. **衔接(必须)**:按记录类型切对应 skill 操作;aisearch 不做读写。
**禁止**:编造行为结果、跳过 `--format json`。
## 高频硬约束
- 搜索目标必须完整保真:姓名、工号、手机号、部门名按用户原文完整传入 `--keyword`,严禁自行截断、拆字、改昵称或扩展同音字。
- 首次未命中时最多换维度重试一次(如 name → department/jobNumber/phone),仍必须保留完整目标值;不要用半截姓名扩大搜索。
@@ -46,3 +81,6 @@ metadata:
- 拿到 userId 后查详情 / 部门 → 切到 `dingtalk-contact`
- 拿到 userId 发消息 → 切到 `dingtalk-chat`
- 拿到 userId 发 DING → 切到 `dingtalk-ding`
## 局部意图与 Recipe
- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。
@@ -98,7 +98,7 @@ Flags:
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `aisearch person` | `userId`(用户ID,= `meta.staffId`/`meta.jobNumber`)、`author`(真实姓名,= `meta.name`);`title` 是花名/显示名,不一定等于姓名 | 展示搜索结果、后续操作(发消息/建待办等) |
| `aisearch person` | `userId`(用户ID)、`title`(姓名) | 展示搜索结果、后续操作(发消息/建待办等) |
## 重名消歧
@@ -0,0 +1,14 @@
# aisearch 局部意图消歧
本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|---|---|---|---|---|
| "张三在哪个部门/查一下同事工号" | 通讯录精确查询 | #8 `contact` | #5 汇报 / #4 文档 | 需要 userId、手机号、部门 ID 等精确信息时用 contact |
| "找一下张三/搜同事/找人" | AI搜人(首选) | `aisearch person` | `contact user search` | 搜人首选 aisearch,支持姓名/部门/职责/上下级维度;精确查 userId/手机号用 contact |
| "五道的上级是谁/谁负责XX/XX的下属有谁" | AI语义搜人 | `aisearch person` | `contact` | 涉及上下级、职责、负责人等语义维度搜索,用 aisearch |
| "222020这个工号是谁/查工号" | 按工号搜人 | `aisearch person --dimension jobNumber` | `contact` | 工号查人走 aisearch,dimension=jobNumber |
| "13800138000是谁/查手机号" | 按手机号搜人 | `aisearch person --dimension phone` | `contact` | 手机号查人走 aisearch,dimension=phone |
| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` |
| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` |
| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 |
@@ -0,0 +1,30 @@
# aisearch Lite Recipe
本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。
## #8 通讯录
### get-contact-self
`contact user get-self` → 当前用户 userId、部门、主管等
### search-person
**搜人首选入口**。凡是“找人/搜人/找同事/谁负责/上级/下级/负责人/团队成员”均优先用 `aisearch person`:
1. 从用户问题中提取 keyword(人名/业务关键词)和 dimension(维度),规则见 [aisearch.md](./aisearch.md)。
2. `aisearch person --keyword "<关键词>" --dimension <维度>`
3. 结果中提取 `userId` 和 `title`(姓名)展示给用户。
4. 若需要 userId 做后续操作(发消息/建待办),可直接使用结果中的 `userId`。
5. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。
### search-user
仅在以下**精确查询**场景使用,搜人请优先用 `search-person`:
- 需要获取 userId 给其他产品使用(发消息/建待办/约日程)
- 已有 userId 需查完整详情(`contact user get --ids`)
1. `aisearch person --keyword "<姓名>" --dimension name` → `userId`;**多命中须列出候选请用户确认**。
2. **重名消歧**:多人同名时禁止默认选第一个,须追加 `contact user get --ids` 获取部门/职位后请用户确认,详见 [08-directory.md](../../dingtalk-contact/references/08-directory.md)「多命中」。
3. 需详情时:`contact user get --ids <userId>`(多人可 `--ids id1,id2,...`)
+74 -8
View File
@@ -1,6 +1,6 @@
---
name: dingtalk-aitable
description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/建表/查记录/写数据/字段/记录增删改查/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表。Distinct from 主 dws skill 的 dws sheet(电子表格/单元格读写/公式)、dws doc(文档编辑)。命令前缀:dws aitable。
description: 钉钉 AI 表格(多维表)。Use when 用户说 AI表格/多维表/数据表/base/table/建表/查记录/写数据/字段/记录增删改查/筛选/排序/公式/模板搜索/批量导入CSV或JSON/导出/仪表盘/图表/上传附件到表格/按字段类型建表。不做电子表格单元格读写(走 dingtalk-sheet)、文档编辑(走 dingtalk-doc);听记待办入表见配方 minutes-todo-to-aitable。命令前缀:dws aitable。
cli_version: ">=0.2.14"
metadata:
category: product
@@ -14,14 +14,14 @@ metadata:
> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。
> **PREREQUISITE:** Read the root `dws` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH.
## 前置条件 — 执行操作前必读
> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 命令参考:[aitable.md](references/aitable.md);复杂命令按需加载 `references/aitable/*.md`;剧本:[06-data-analytics.md](references/06-data-analytics.md)。
> 高频模块:[Base](references/aitable/aitable-base-index.md) · [Table](references/aitable/aitable-table-index.md) · [Field](references/aitable/aitable-field-index.md) · [Record](references/aitable/aitable-record-index.md) · [View](references/aitable/aitable-view-index.md) · [Form](references/aitable/aitable-form-index.md) · [Workflow](references/aitable/aitable-workflow-index.md)。
## 意图表
@@ -40,13 +40,76 @@ metadata:
| "仪表盘 / 图表" | 先读 `references/aitable/aitable-dashboard-chart.md` |
| "上传附件到记录" | 先读 `references/aitable/aitable-attachment.md`;可用 `python scripts/upload_attachment.py --base-id <id> --file <path>` |
## 标准 SOP(必遵流程)
> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 flag/ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段,不得凭返回结构猜测。`baseId`/`tableId`/`fieldId`/`recordId` 一律先查后用,**禁止默认/编造**。
### SOP-1 定位 Base 与 Table(list / search → table get)
**触发**:找/打开某张 AI 表格、不知 baseId 或 tableId。
1. **选源(必须)**:有名称/关键词 → `dws aitable base search --query "<名称>"`;列最近访问 → `dws aitable base list`。`base list` 仅返回最近访问,不是全部,**禁止**当作全量清单。
2. **执行(必须)**:`dws aitable base search --query "<完整名>" --format json`(或 `dws aitable base list --format json`)。
3. **解析(必须)**:从 JSON 取真实 `baseId`;**多候选必须输出让用户选,禁止默认取第一个**。
4. **取 tableId(必须)**:`dws aitable table get --base-id <baseId> --format json` → 从 `tables[].tableId` 取目标表 ID,连同该表 `fields[]` 的 `fieldId/fieldName/type` 与 `views[]` 的 `viewId` 一并记下。
5. **失败(必须)**:`base list` 为空或不命中 → 换 `base search --query` 关键词重试一次;仍无果**必须如实告知**,禁止臆造 baseId/tableId。
**禁止**:跳过 `table get` 直接用字段名写记录、用模糊名匹配当 baseId、用旧会话里的 ID 不再校验。
### SOP-2 拿字段定义(field get,写记录/改字段前置)
**触发**:建/改/写记录、改字段名或 options、按字段类型拼写入参前。
1. **前置(必须)**:先按 SOP-1 拿到 `baseId` + `tableId`。
2. **执行(必须)**:`dws aitable field get --base-id <baseId> --table-id <tableId> --format json`(仅展开需要的字段时加 `--field-ids fld1,fld2`,单次最多 10 个)。
3. **解析(必须)**:取每个目标字段的 `fieldId`、`type`、`config`(如 singleSelect/multipleSelect 的 `options[].id|name`);写入 cells 的 key **必须用 `fieldId`**,不是字段中文名;select 字段过滤/写入传**选项名称字面量**,不传 option ID。
4. **衔接(必须)**:拿到字段定义 → 进入 SOP-3 写记录、或 `dws aitable field update --field-id <fieldId> --name <新名>|--config <JSON> --format json` 改字段。
5. **失败(必须)**:字段不存在或类型不符 → 重新 `field get` 核对,**禁止**凭旧名称/旧类型继续写入。
**禁止**:用字段中文名当 cells key、跳过 `field get` 直接 `record create/update`、对 select 字段传 option ID 当写入值。
### SOP-3 写/批量写记录(record create)
**触发**:新增记录、批量加数据、CSV/JSON 入表。
1. **前置(必须)**:SOP-1 取 `baseId`/`tableId` + SOP-2 取 `fieldId`/类型。
2. **执行(必须)**:`dws aitable record create --base-id <baseId> --table-id <tableId> --records '[{"cells":{"<fieldId>":<值>}}]' --format json`;单次最多 100 条,超长用 `--records-file ./data.json`。
3. **写入格式(必须)**:按 `record create --help` 类型表严格传值(text→字符串、number→数值、singleSelect→"选项名"、date→RFC3339、url→`{"text","link"}`、group→`{"cid"}` 等);`filterUp`/`lookup` 字段只读不可写。
4. **解析(必须)**:从返回取真实 `recordId`;名称被系统加后缀或字段重命名时,**后续一律用返回的新 ID**,禁止继续按原名称猜。
5. **失败(必须)**:类型/格式错误按返回报错修正后重试,**禁止**降级丢弃字段;不确定格式先 `field get` 复核 config。
**禁止**:编造 fieldId/recordId、跳过 `field get` 凭中文名写、把 URL 字符串直接塞给 url 字段。
### SOP-4 查/筛/排记录(record query)
**触发**:查记录、按条件筛选、排序、取关联记录、定位待改/待删的 recordId。
1. **前置(必须)**:SOP-1 拿 `baseId`/`tableId`。
2. **执行(必须)**:`dws aitable record query --base-id <baseId> --table-id <tableId> --format json`;已知 ID 直取加 `--record-ids rec1,rec2`(忽略 filters/sort,单次≤100)。
3. **筛选/排序(必须)**:`--filters` 最外层必须 `{"operator":"and|or","operands":[...]}`,select 字段值传**选项名字面量**;日期只能用 `date_eq/before/after/not_before/not_after`,范围用 `not_before`+`not_after` 组合,**禁止** `eq`/区间/相对时间。`--sort` 用 `[{"fieldId":"..","direction":"asc|desc"}]`(**必须用 `direction`**)。公式/引用/关联字段默认不返回,需显式 `--field-ids` 指定。
4. **解析(必须)**:取真实 `recordId` 与字段值;分页用 `--cursor`,全表用 `--all --page-limit N`。
5. **衔接(必须)**:拿到 recordId → SOP-5 更新、`record delete --record-ids --yes` 删除(删前确认)。
**禁止**:用字段名做 filter/sort key、对日期用 `eq`、漏掉 `direction` 用旧 `order` 字段、用本地过滤替代服务端 filter。
### SOP-5 更新记录(record update)
**触发**:改记录字段值、批量更新状态、单字段重命名需求之外的记录改动。
1. **前置(必须)**:SOP-1 拿 `baseId`/`tableId`;SOP-2 拿字段类型;SOP-4 拿目标 `recordId`。
2. **执行(必须)**:`dws aitable record update --base-id <baseId> --table-id <tableId> --records '[{"recordId":"recXXX","cells":{"<fieldId>":<新值>}}]' --format json`(每条必含 `recordId`+`cells`,单次≤100;超长用 `--records-file`);只传需改字段,未传保持原值。
3. **解析(必须)**:写入格式同 SOP-3;返回核对真实 recordId 与受影响字段。
4. **失败(必须)**:recordId 不存在或类型不符 → 回 SOP-4 重新定位,**禁止**编造 ID 强写。
**禁止**:省略 `recordId`、用字段中文名当 cells key、凭空猜测 recordId 直接 update。
## 危险操作
`base delete` / `table delete` / `field delete` / `record delete` 不可逆,必须先向用户确认再加 `--yes`。
## 评测高频硬约束
## 高频硬约束
- 创建/改字段/写记录是多轮连续任务时,不能在"让我执行/先获取 ID"后停下;必须实际调用对应 `dws aitable` 命令并验证结果。
- 创建 Base、改字段或写记录时,固定按“获取真实 ID → 执行写命令 → 回读验证”顺序完成,禁止跳过任一步。
- 字段重命名使用 `dws aitable field update --base-id <baseId> --table-id <tableId> --field-id <fieldId> --name "<新名称>" --format json`;先 `field get` 找真实 `fieldId`,不要猜字段名能直接更新。
- 写记录前必须 `field get` 获取 `fieldId` 与类型;`record create/update` 的 `cells` key 用 `fieldId`,不是字段中文名。长 JSON 使用 `--records-file`。
- 表或字段创建返回名称被系统自动加后缀时,后续必须使用返回的真实 `tableId`/`fieldId`,不要继续按原名称猜。
@@ -59,4 +122,7 @@ metadata:
## 跨产品协作
- 单元格 / 工作表 / 公式 → 走主 `dws` skill 的 `sheet` 产品路由(`dws sheet`)
- 单元格 / 工作表 / 公式 → 切到 `dingtalk-sheet`(命令前缀:`dws sheet`)
## 局部意图与 Recipe
- [局部意图消歧](references/intent-guide.md)。
@@ -4,10 +4,8 @@
| Recipe | 行动指南(固定路线) |
|--------|-------------------|
| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable field get --base-id <baseId> --table-id <tableId>` → 取 `fieldId`<br>3. **取记录(按场景分流)**:<br>  • 数据统计/分析/全量汇总 → `aitable record query --base-id <baseId> --table-id <tableId> --all`(自动翻页,**禁止凭单页数据做统计**)<br>  • 大表保险 → 加 `--page-limit 100`(默认 50 页/5000 条,0 = 无限制)<br>  • 单纯预览前几条 → `aitable record query --base-id <baseId> --table-id <tableId> --limit 30`(不加 --all)<br>  • 筛选时 `--filters` 格式见 [aitable-filter-sort.md](../products/aitable/aitable-filter-sort.md)<br>4. **检查输出契约**:`hasMore=true` 时数据被截断,必须用 `--cursor <X>` 续拉;`partial=true` 时表示中途某页失败(保留已拉数据,可重试)<br>5. 总结数据 |
| generate-data-report | 1. 同 read-aitable 步骤 1-3(**必须用 --all 防漏数据**)<br>2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 补充背景<br>3. `doc create --name "<报告名>" --content "<分析报告>"` |
| create-aitable-record | **写入路径分流**(关键决策):<br>  • **追加到已有表**(场景:把这批数据加到「成员表」)→ `python scripts/import_records.py <baseId> <tableId> data.csv\|data.json [batch_size]`(走 `record create`,需要列名匹配字段名)<br>  • **新建表**(场景:把这个 Excel 变成多维表)→ `python scripts/aitable_import_via_task.py <baseId> <file>`(脚本内置 prepare→OSS PUT→import data 全流程和正确的头处理,**禁止自己写 PUT**)。注意 `aitable import upload` 没有 `--file` flag、也不代做 PUT,只准备导入;能一站式完成的是上面的脚本<br>  • **单条/少量手动**:1. `aitable base search` → `baseId`/`tableId`;2. `aitable field get` → `fieldId` 与类型;3. `aitable record create --records '[{"cells":{"<fieldId>":"值"}}]'` |
| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. **取目标 record**:<br>  • 已知少量 recordId → `aitable record query --record-ids <ID1,ID2>`<br>  • 按条件批量改 → `aitable record query --base-id <baseId> --table-id <tableId> --filters '<JSON>' --all`(**用 --all 防止漏改**)<br>3. **先展示让用户确认要改的 record 列表**<br>4. `aitable record update --base-id <baseId> --table-id <tableId> --records '[{"recordId":"<recordId>","cells":{...}}]'`(单次 ≤30 条) |
| read-aitable | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable field get --base-id <baseId> --table-id <tableId>` → 取 `fieldId`<br>3. `aitable record query --base-id <baseId> --table-id <tableId>` → 取记录(分页)<br>  需要筛选时 `--filters` 格式见 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md),根节点必须是 `{"operator":"and\|or","operands":[...]}`<br>4. 总结数据 |
| generate-data-report | 1. 同 read-aitable 步骤 1-3<br>2. 按[「多源并行采集」](_common/conventions.md#多源并行采集公共模式)执行 → 补充背景<br>3. `doc create --name "<报告名>" --content "<分析报告>"` |
| create-aitable-record | **批量导入优先**:`python scripts/import_records.py <baseId> <tableId> data.csv\|data.json [batch_size]`(自动分批创建)<br>单条/少量:1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable field get --base-id <baseId> --table-id <tableId>` → 取 `fieldId` 与类型<br>3. `aitable record create --base-id <baseId> --table-id <tableId> --records '[{"cells":{"<fieldId>":"值"}}]'` |
| update-aitable-record | 1. `aitable base search --query "<表格名>"` → 取 `baseId`/`tableId`<br>2. `aitable record query --base-id <baseId> --table-id <tableId>` → 取 `recordId`,**先展示让用户确认**<br>3. `aitable record update --base-id <baseId> --table-id <tableId> --records '[{"recordId":"<recordId>","cells":{...}}]'` |
| search-aitable-template | 1. `aitable template search --query "<关键词>"` → 取 `templateId`<br>2. 用户选定<br>3. `aitable base create --name "<表格名>" --template-id <templateId>` → 取 `baseId` |
| export-aitable-to-xlsx | 1. `aitable base search --query "<表格名>"` → 取 `baseId`<br>2. **按场景选 scope**:<br>  • 全表+附件 → `aitable export data --base-id <baseId> --scope all --export-format excel_and_attachment --output ./<name>.xlsx`<br>  • 单表(仅 xlsx)→ `--scope table --table-id <tableId> --export-format excel`<br>  • 单视图 → `--scope view --table-id <tableId> --view-id <viewId>`<br>3. CLI 内置渐进式退避轮询 + 自动落盘,**不要自己写 GET downloadUrl**<br>4. 大表超时(默认 5 分钟):加 `--timeout-sec 900` 或拿到 `taskId` 后 `aitable export data --task-id <ID> --output ./<name>.xlsx` 续等<br>5. 与悟空脚本路径并存:复杂场景(多 base 批量 / 按视图组合)请用 `python scripts/aitable_export_via_task.py <baseId> --scope all\|table\|view` |
| primary-doc-from-record | 当 AI 表格用文档作主键字段时,从 record 跳到其主文档:<br>1. `aitable record query` 取 `recordId`<br>2. `aitable base get-primary-doc-id --base-id <baseId> --table-id <tableId> --record-id <recordId>` → 取主键文档 `dentryUuid`<br>3. 凭 `dentryUuid` 调 `doc read --node <UUID>` 拿文档内容 |
@@ -1,6 +1,6 @@
# 业务域通用规范
> 仅服务本仓库已迁入的文档与 AI 表格行动指南。安全门控、危险操作确认、`--format json` 等已在根 skill 中定义,此处不重复。
> 仅服务本 skill 已迁入的行动指南。安全门控、危险操作确认、`--format json` 等已在本 skill 的 `SKILL.md` 中定义,此处不重复。
## 批量查询规范
@@ -9,7 +9,8 @@
| 1 | **并行查详情**:拿到多个 ID 后,用 `&` 合并到同一条 Shell 命令并行执行 + `wait`,**严禁逐条串行** |
| 2 | **翻页**:分页接口须拉全直至无更多 |
| 3 | **优先批量 API**:有批量接口则用批量;无则按 #1 并行 |
| 4 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` |
| 4 | **群消息**:必须先 `chat search --query` 得 `openConversationId`,再 `chat message list --group`;多群同条命令并行 |
| 5 | **列表少轮次**:带条件搜索/列表 → 一次采全详情;**禁止**无新参数时重复同一 `list` / `search` |
## 多源并行采集(公共模式)
@@ -26,10 +27,18 @@
| 字段 | 来源 | 传递给 |
|------|------|--------|
| `nodeId` | `doc search` | `doc read/update/copy/move/rename --node` |
| `nodeId` | `doc list` 中的 folder 类型节点 / `doc folder create` | `doc list --folder`、`doc create --folder`、`doc upload --folder`、`doc copy/move --folder` |
| `taskUuid` | `minutes list` | `minutes get summary/info/batch --id(s)` |
| `userId` | `aisearch person` / `contact user search` / `contact dept list-members` | `contact user get --ids`、`todo --executors`、`calendar --users` |
| `deptId` | `contact dept search` | `contact dept list-members --depts`;多子部门时对每个子部门分别 `dept search` 取 id |
| `nodeId` | `drive search` / `wiki node search` | `doc read/update --node`、`drive copy/move/rename/delete --node` |
| `nodeId` | `wiki node list` 中的 folder 类型节点 / `wiki node create --type folder` | `wiki node list --folder`、`wiki node create --folder`、`drive upload --folder`、`drive copy/move --folder` |
| `eventId` | `calendar event list` | `calendar event get/update --id` |
| `processInstanceId` | `oa approval list-*` | `oa approval detail/approve --instance-id` |
| `openConversationId` | `chat search` | `chat message list/send --group` |
| `todoTaskId` | `todo task list` | `todo task update/done --task-id` |
| `reportId` | `report inbox list` / `report outbox list` | `report entry get/stats --report-id` |
| `baseId` / `tableId` | `aitable base search` | `aitable record query --base-id --table-id` |
| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --file-id`、`drive list/mkdir/upload --parent-id` |
| `workspaceId` | `wiki space search/list/create` | `doc list/search/create --workspace`、`wiki member * --workspace` |
| `dentryUuid` | `drive list` / `drive mkdir` | `drive info/download --node`、`drive list/mkdir/upload --folder` |
| `dentryId` | `drive info` 的数字字段 | 仅用于 `chat message send --dentry-id` |
**ID 边界硬约束**:遇到 `drive --parent-id`、`doc --folder`、`doc --node` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `doc list` / `doc search` 获取正确 ID,不能把该数字直接代入后续命令。
**ID 边界硬约束**:`dentryId` 通常是纯数字,只表示聊天文件消息需要的钉盘条目数字 ID;它不是父目录 ID。遇到 `drive --node/--folder`、`doc --node/--folder`、`wiki node --folder` 时,只能使用 `dentryUuid` / `nodeId` / 文档 URL。若当前上下文只有数字型 `dentryId`,必须先重新 `drive list` / `drive search` / `wiki node list` 获取正确 ID,不能把该数字直接代入后续命令。
@@ -1,282 +1,21 @@
# AI表格 (aitable) 命令参考
# AI 表格 (aitable) 命令参考
> **渐进式文档**:本文件为路由层(索引 + 意图判断),各命令的详细参数、示例和踩坑说明在 [aitable/](./aitable/) 目录下按需加载。
> 渐进式入口:先读命令索引,再只加载命中的模块参考。
## 文档地址 (URI)
## 按需加载
| 资源 | URI 格式 |
|------|----------|
| Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` |
| 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` |
| 任务 | 参考 |
|---|---|
| URL 与 baseId | [URI](./aitable/aitable-uri.md) · [URL 转 baseId](./aitable/aitable-url-to-baseid.md) |
| Base / Table / Field | [Base](./aitable/aitable-base-index.md) · [Table](./aitable/aitable-table-index.md) · [Field](./aitable/aitable-field-index.md) |
| Record / View / Form | [Record](./aitable/aitable-record-index.md) · [View](./aitable/aitable-view-index.md) · [Form](./aitable/aitable-form-index.md) |
| Workflow / Dashboard / Import | [Workflow](./aitable/aitable-workflow-index.md) · [Dashboard](./aitable/aitable-dashboard-index.md) · [导入导出](./aitable/aitable-export-import-index.md) |
| Attachment / Advanced Permission | [附件](./aitable/aitable-attachment-index.md) · [高级权限](./aitable/aitable-advperm-index.md) |
| 完整命令速查 | [命令索引](./aitable/aitable-command-index.md) |
| 复杂操作与路由 | [复杂操作](./aitable/aitable-complex-ops.md) · [意图判断](./aitable/aitable-intent.md) |
| 标准流程与上下文 | [核心工作流](./aitable/aitable-workflow.md) · [上下文传递](./aitable/aitable-context.md) |
| 注意事项、脚本、产品边界 | [注意事项](./aitable/aitable-notes.md) · [自动化脚本](./aitable/aitable-scripts.md) · [相关产品](./aitable/aitable-related.md) |
> **操作后请返回文档 URI**:每次执行 base list/search/create/get 操作后,从返回数据中提取 `baseId`,拼接为 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户。
> 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。
## 创建 AI 表格
## 命令索引表
### base (Base 管理)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` |
| `base search` | 按名称搜索 Base(别名 `aitable search`) | — | `--query` help 标必填但实际可省略:不传时返回最近访问的 Base 列表。`--keyword` 是 `--query` 的隐藏别名,同义 |
| `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID |
| `base copy` | 复制整个 Base 到目标文件夹 | `--base-id` `--target-folder-id` | 默认全量复制;`--only-struct` 仅复制结构不含数据 |
| `base get-primary-doc-id` | 获取某记录的主键文档 ID | `--base-id` `--table-id` `--record-id` | 等价 `record primary-doc-get` 的取 ID 视角 |
| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId |
| `base update` | 更新 Base 名称 | `--base-id` `--name` | — |
| `base delete` | 删除 Base | `--base-id` | 不可逆 |
### table (数据表管理)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 |
| `table list` | 获取数据表(`table get` 的别名) | `--base-id` | 与 `table get` 等价 |
| `table create` | 创建数据表 | `--base-id` `--name` | `--fields` 为 JSON 数组;**可传空数组 `[]`**(默认值即 `[]`),此时服务端自动补一个名为"标题"的 primaryDoc 首列;单次最多 15 个字段 |
| `table update` | 重命名表 | `--base-id` `--table-id` `--name` | — |
| `table delete` | 删除表 | `--base-id` `--table-id` | 不可逆 |
### field (字段管理) → 详见 [aitable-field.md](./aitable/aitable-field.md)、[field-properties](./aitable/aitable-field-properties.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `field get` | 获取字段完整配置 | `--base-id` `--table-id` | 按需展开少量字段 |
| `field list` | 获取字段信息(`field get` 的别名) | `--base-id` `--table-id` | 与 `field get` 等价 |
| `field create` | 创建字段 | `--base-id` `--table-id` + (`--name --type` 或 `--fields`) | 支持单字段/批量模式 |
| `field update` | 更新字段名/配置 | `--base-id` `--table-id` `--field-id` | 不可变更字段类型 |
| `field delete` | 删除字段 | `--base-id` `--table-id` `--field-id` | 不可逆 |
| `field search-options` | 搜索单选/多选字段的选项 | `--base-id` `--table-id` `--field-id` | 仅 singleSelect/multipleSelect;`--keyword` 模糊过滤,不传返回全部 |
### record (记录管理)
| 命令 | 用途 | 必读 reference | 路由提醒 |
|------|------|----------------|----------|
| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable/aitable-record-query.md) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference;`--query`(隐藏别名 `--keyword`)全文搜索 |
| `record list` | 获取记录(`record query` 的别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 与 `record query` 等价 |
| `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable/aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit |
| `record query-empty` | 查询完全没填用户字段的空行 | — | `--base-id` `--table-id`;`--limit` 扫描预算 [1,100],`--cursor` 翻页 |
| `record create` | 新增记录 | [aitable-record-create.md](./aitable/aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 |
| `record update` | 更新记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;**没有** `--record-id` `--cells` flag |
| `record batch-update` | 把同一份 cells 批量应用到多条记录 | [aitable-record-update.md](./aitable/aitable-record-update.md) | `--record-ids`(≤100)+ `--cells` 共享 patch |
| `record upsert` | 批量创建或更新(有 recordId 走更新,无则创建) | [aitable-record-upsert.md](./aitable/aitable-record-upsert.md) | `--records`/`--records-file`;单次最多 100 条 |
| `record delete` | 删除记录 | [aitable-record-delete.md](./aitable/aitable-record-delete.md) | 不可逆,需先 query 确认 |
| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable/aitable-record-share.md) | `--record-ids`(逗号分隔,单次最多 20 条) |
| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable/aitable-record-history.md) | `--record-id` 单条;`--offset`/`--limit`(≤50) |
| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 无文档时返回 `no record` 错误 |
| `record primary-doc-create` | 为记录创建主键文档 | [aitable-primary-doc.md](./aitable/aitable-primary-doc.md) | 幂等;`--field-id` 须 primaryDoc 类型 |
### view (视图管理)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `view get` | 获取视图配置 | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 |
| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner |
| `view update` | 更新视图(**调整字段顺序的入口**) | `--base-id` `--table-id` `--view-id` | `visibleFieldIds` 重排字段顺序 |
| `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 |
> **"移动字段/调整字段顺序"** 在 AI 表格里没有 `field reorder` 命令,必须通过 `view update --config '{"visibleFieldIds":[...]}'` 完成。
> **view update --config 支持的 key 白名单**(传入其他 key 会报错):
> - `visibleFieldIds` — 视图可见字段列表及顺序(首列字段必须保留在第一位)
> - `filter` — 筛选规则列表
> - `sort` — 排序规则列表
> - `group` — 分组规则列表
> - `fieldWidths` — 列宽映射(仅 Grid 视图有效)
>
> 不支持 `formInfo`、`requiredFields`、`conditionalRules` 等 FormDesigner 高级配置,这些 key 会被服务端忽略。
### form (表单管理) → 详见 [aitable-form.md](./aitable/aitable-form.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `form list` | 列出表单视图 | `--base-id` `--table-id` | 每条含 viewId/name;新建表单无 title 且 createdAt=0,改过后才有 |
| `form get` | 按 viewId 取单个表单详情 | `--base-id` `--table-id` `--view-id` | 客户端按 viewId 过滤,`data` 即该表单对象;viewId 不存在报 not found |
| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | 等价 `view create --view-type FormDesigner` |
| `form update` | 更新表单配置 | `--base-id` `--table-id` `--view-id` | title/name/description 至少一项 |
| `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` | 不可逆 |
| `form field list/update/hide` | 表单字段管理 | — | 详情见子文档 |
| `form questions create/delete` | 题目管理(=field create/delete) | — | 详情见子文档 |
| `form share get/update` | 表单分享配置 | — | 详情见子文档 |
> **创建表单**有两种等价方式:`form create --name "..."`(推荐)或 `view create --view-type FormDesigner --name "..."`。
### workflow (自动化工作流) → 详见 [aitable-workflow.md](./aitable/aitable-workflow.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `workflow list` | 列出 Base 下所有工作流 | `--base-id` | 支持 `--limit [1,100]` / `--offset >=0`;list 出参字段叫 `flowId` |
| `workflow get` | 获取单个工作流详情(含 flowSchema) | `--base-id` `--workflow-id` | `--workflow-id` 接受 list 里的 `flowId`(同值) |
| `workflow enable` | 启用工作流 | `--base-id` `--workflow-id` | 返回 `{enabled: true}` 是动作确认;要确认真启用看 list 的 `status` |
| `workflow disable` | 禁用工作流(高危) | `--base-id` `--workflow-id` `--yes` | 影响业务自动化,建议二次确认;status 变 STOP |
> **当前不支持通过 CLI 新建/修改/删除工作流**,请去 AI 表格 Web 端(数据表页面 → 自动化)配置。
### dashboard & chart → 详见 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md)
| 命令 | 用途 |
|------|------|
| `dashboard get/create/update/delete` | 仪表盘管理 |
| `dashboard config-example` | 查看仪表盘配置模板 |
| `chart get/create/update/delete` | 图表管理 |
| `chart widgets-example` | 查看图表 widgets 配置模板 |
### export & import → 详见 [aitable-export-import.md](./aitable/aitable-export-import.md)
| 命令 | 用途 |
|------|------|
| `export data` | 导出数据(异步两阶段轮询) |
| `import upload` | 申请文件导入上传凭证 |
| `import data` | 触发导入 |
### attachment → 详见 [aitable-attachment.md](./aitable/aitable-attachment.md)
| 命令 | 用途 | 路由提醒 |
|------|------|----------|
| `attachment upload` | 准备附件上传凭证 | 不要用钉盘 drive 上传! |
### template (模板搜索)
| 命令 | 用途 | 必填参数 |
|------|------|----------|
| `template search` | 搜索模板 | `--query` |
### advperm (高级权限/自定义角色) → 详见 [aitable-advperm.md](./aitable/aitable-advperm.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `advperm enable` | 开启 Base 高级权限总开关 | `--base-id` | 不开启时角色规则不生效 |
| `advperm disable` | 关闭 Base 高级权限总开关(高危) | `--base-id` `--yes` | 关闭后全员回退默认权限 |
| `advperm role-list` | 列出 Base 下所有角色 | `--base-id` | 同时返回自定义角色和系统角色;`roleType == "custom"` 是自定义,前缀 `system_` 是系统角色 |
| `advperm role-get` | 获取单角色完整配置 | `--base-id` `--role-id` | 含 subRoles 与字段/行级规则 |
| `advperm role-create` | 创建自定义角色 | `--base-id` `--name` | 可选 `--sub-roles` 同时指定子角色权限规则 |
| `advperm role-update` | 增量更新自定义角色(PATCH) | `--base-id` `--role-id` | 未传字段不变;`--sub-roles` 按 (targetId,targetType) 合并 |
| `advperm role-delete` | 删除自定义角色 | `--base-id` `--role-id` `--yes` | 不可逆;系统角色禁删;**调用者必须是该 AI 表格的管理员/Owner**,非管理员会得到 401 AUTH_ERROR |
> 所有写命令(enable/disable/role-create/role-update/role-delete)需要 Base 管理员权限;非管理员只能调 `role-list` / `role-get`(只读)。
> "角色 ↔ 成员"绑定当前 CLI 不支持,仍需在 AI 表格 Web 端 → Base 设置 → 高级权限面板手动完成。
### section (文件夹与节点管理)
用于在 Base 的导航树中组织 table / dashboard / 表单视图 / 文档等节点(类似文件夹)。操作前建议先用 `section list-nodes` 拿到 nodeId / sectionId 与父级关系。
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `section list-nodes` | 列出 Base 下全部节点 | `--base-id` | 返回 `{nodeId, nodeType, parentSectionId, name?}`;是 move-node / reorder 的前置定位命令 |
| `section list-empty` | 列出空文件夹 | `--base-id` | 返回 `{sectionId, name, parentSectionId}`,用于清理导航树 |
| `section create` | 创建文件夹 | `--base-id` `--name` | `--parent-section-id` 不传/空串=根目录;`--index` 指定位置 |
| `section rename` | 重命名文件夹 | `--base-id` `--section-id` `--new-name` | — |
| `section delete` | 删除文件夹 | `--base-id` `--section-id` | 不可逆;建议先 list-empty 确认为空 |
| `section reorder` | 调整文件夹顺序 | `--base-id` `--section-id` `--target-index` | 仅当前父级下调序;跨父级用 move-node |
| `section move-node` | 移动节点(跨父级) | `--base-id` `--node-id` `--new-parent-section-id` | `--new-parent-section-id ""`=移到根;`--target-index` 调全局位置 |
## 意图判断
用户说"表格/多维表/AI表格":
- 查看/查找/列表 → `base search`(优先)或 `base list`(仅浏览最近访问)
- 详情 → `base get`
- 创建 → `base create`
- 修改 → `base update`
- 删除 → `base delete`
用户说"数据表/子表/table":
- 查看 → `table get`
- 创建 → `table create`
- 重命名 → `table update`
- 删除 → `table delete`
用户说"字段/列/column":
- 查看 → `field get`
- 添加 → `field create`(读 [aitable-field.md](./aitable/aitable-field.md))
- 修改 → `field update`
- 删除 → `field delete`
用户说"记录/行/数据/row":
- 查看/搜索 → `record query`(读 [aitable-record-query.md](./aitable/aitable-record-query.md))
- 已知 recordId 反查字段值 → `record get`(按 ID 取专用,等价 `record query --record-ids`)
- 添加/写入 → `record create`(读 [aitable-record-create.md](./aitable/aitable-record-create.md))
- 修改/更新 → `record update`(读 [aitable-record-update.md](./aitable/aitable-record-update.md))
- 删除 → `record delete`
用户说"表单/问卷/form/收集信息" → 读 [aitable-form.md](./aitable/aitable-form.md)
用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable/aitable-filter-sort.md)
用户说"统计/分析/聚合/TOP N/全量" → 读 [aitable-data-analysis-sop.md](./aitable/aitable-data-analysis-sop.md)
用户说"公式/formula/计算字段/派生指标" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md)
用户说"查找引用/lookup/filterUp/跨表" → 读 [aitable-formula-guide.md](./aitable/aitable-formula-guide.md)(§5.4 跨表引用)
用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable/aitable-dashboard-chart.md)
用户说"附件/上传文件" → 读 [aitable-attachment.md](./aitable/aitable-attachment.md)
用户说"导入/导出/import/export" → 读 [aitable-export-import.md](./aitable/aitable-export-import.md)
用户说"模板" → `template search`
命令报错/操作失败 → 读 [aitable-error-recovery.md](./aitable/aitable-error-recovery.md)
**关键区分**: base=表格文件, table=数据表, field=列, record=行
## 核心工作流
```bash
# 1. 搜索/列出 Base — 提取 baseId
dws aitable base search --query "项目" --format json
# 2. 获取 Base 信息 — 提取 tableId
dws aitable base get --base-id <BASE_ID> --format json
# 3. 获取表结构 — 提取 fieldId
dws aitable table get --base-id <BASE_ID> --table-id <TABLE_ID> --format json
# 4. 查询记录
dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --format json
# 5. 新增记录 (cells 用 fieldId 作 key)
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
--records '[{"cells":{"fldXXX":"值"}}]' --format json
```
## 上下文传递表
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI |
| `base create` | `baseId` | 后续命令 + 文档 URI |
| `base get` | `tables[].tableId` | --table-id |
| `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete |
| `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` |
| `template search` | `templateId` | base create --template-id,拼接模板预览 URI |
## URL → baseId 提取
用户提供 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 链接时:
1. 提取 `/nodes/` 后的路径段作为 `baseId`
2. 去掉尾部的查询参数(`?` 及其后内容)
3. 传入 `--base-id` 参数
> 如果该 URL 来自 `dws aitable` 返回或已在当前链路 probe 过,可直接复用;
> 如果是用户直接提供的原始 `alidocs` URL,则先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认 `extension=able` 后再继续。
## 注意事项
- 所有操作使用 ID(baseId/tableId/fieldId/recordId),不使用名称
- records 的 cells key 是 fieldId,不是字段名称
- cells 写入/读取格式见 [aitable-cell-value.md](./aitable/aitable-cell-value.md)
- 最佳实践见 [aitable-best-practices.md](./aitable/aitable-best-practices.md)
## 自动化脚本
| 脚本 | 场景 |
|------|------|
| [bulk_add_fields.py](../../scripts/bulk_add_fields.py) | 批量添加字段 |
| [import_records.py](../../scripts/import_records.py) | 从 JSON/CSV 批量导入记录 |
| [aitable_export_via_task.py](../../scripts/aitable_export_via_task.py) | 文件导出(export_data 轮询 + 下载) |
| [upload_attachment.py](../../scripts/upload_attachment.py) | 上传附件到 AI 表格记录 |
## 相关产品
- [doc](./doc.md) — 富文本文档编辑,不是结构化数据表格
创建 Base 先读 [Base 索引](./aitable/aitable-base-index.md);创建数据表再读 [Table 索引](./aitable/aitable-table-index.md)。
@@ -0,0 +1,6 @@
# 高级权限与自定义角色
| 命令 | 用途 | 详见 |
|---|---|---|
| `advperm enable/disable` | 高级权限总开关 | [`aitable-advperm.md`](./aitable-advperm.md) |
| `advperm role-list/get/create/update/delete` | 自定义角色管理 | [`aitable-advperm.md`](./aitable-advperm.md) |
@@ -0,0 +1,5 @@
# 附件
| 命令 | 用途 | 详见 |
|---|---|---|
| `attachment upload` | 准备附件上传凭证 | [`aitable-attachment.md`](./aitable-attachment.md) |
@@ -1,6 +1,8 @@
# attachment — 附件上传
> **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。
>
> **STOP — 严禁在 record create/update 的 cells 里直接传图片 URL!** 直传 `{"url":"https://..."}` 会导致服务端同步下载图片,批量写入时触发 TIMEOUT_ERROR。正确做法:先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。
## 准备附件上传
@@ -38,10 +40,8 @@ dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
dws aitable attachment upload --base-id <BASE_ID> --file-name report.pdf --size 204800 --format json
# → 返回 uploadUrl、fileToken
# 2. PUT 上传(Content-Type 必须与文件类型一致,不能留空)
# 留空或用 curl 默认的 application/x-www-form-urlencoded 都会被 OSS 拒为 403
# 2. PUT 上传(Content-Type 必须是文件的具体 MIME type)
curl -X PUT "<uploadUrl>" -H "Content-Type: application/pdf" --data-binary @report.pdf
# 例:.txt → text/plain,.png → image/png,.xlsx → application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
# 3. 写入记录
dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> \
@@ -0,0 +1,10 @@
# Base 管理
| 命令 | 用途 | 必读参数 |
|---|---|---|
| `base list` | 列出最近访问的 Base | — |
| `base search` | 按名称搜索 Base | `--query` |
| `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` |
| `base create` | 创建 Base | `--name` |
| `base update` | 更新 Base 名称 | `--base-id` `--name` |
| `base delete` | 删除 Base | `--base-id` |
@@ -26,7 +26,7 @@
| 全量拉取/统计 | `record query --all` | 不要手动循环 cursor |
| 全量导出为文件 | `export data` | 不要 `--all` 拉全量再写文件 |
| 批量写入 | `record create`(分批 100 条) | 不要一次传超过 100 条 |
| 附件上传 | `attachment upload` + `record update` | 不要在 cells 里伪造附件值 |
| 附件/图片上传 | `attachment upload` 获取 fileToken → `record create/update` 用 fileToken 写入 | **严禁直接传图片 URL 到附件字段**(服务端同步下载会超时) |
| 文件级导入 | `import upload` + `import data` | 不要手动解析 xlsx 再逐条写入 |
## 4. 创建/修改后回读确认
@@ -86,11 +86,16 @@
{"fldDateId": "2026-03-15T09:00+08:00"}
```
**读取**:RFC3339 字符串
**读取**:RFC3339 字符串(带时区)
```json
{"fldDateId": "2026-03-15T09:00:00+08:00"}
```
**过滤**(`record query --filters`):日期字段**只能用日期专用操作符** `date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`,比较值用日期字符串(如 `"2026-03-15"`)。
- ❌ 通用 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对日期字段无效,会静默返回 0 条;
- ❌ 不支持区间 `date_between` 与相对 `from_now`(CLI 会直接拒绝),范围查询用 `not_before` + `not_after` 组合。
- 详见 [aitable-filter-sort.md](./aitable-filter-sort.md) §日期字段过滤。
---
### currency(货币)
@@ -236,15 +241,14 @@
### attachment(附件)
**写入**:对象数组,支持 `fileToken` 或 `url` 形式
**写入**:对象数组,**必须使用 `fileToken`**
```json
{"fldAttachId": [{"fileToken": "ft_xxx"}]}
{"fldAttachId": [{"url": "https://example.com/file.pdf"}]}
```
> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 获取 `fileToken`**。
> URL 形式是 best-effort 异步转存,不保证立即可用。
> ⚠️ **必须先通过 [attachment upload 流程](./aitable-attachment.md) 上传文件获取 `fileToken`,再将 `fileToken` 写入 cells。**
> ❌ **严禁直接传 `{"url": "https://..."}` 形式写入附件/图片字段** — 服务端会同步下载图片,10 条记录即触发 TIMEOUT_ERROR 超时。
> 写入会**整体覆盖**原附件列表,不是追加。
**读取**:对象数组(含下载链接、文件名、大小)
@@ -335,7 +339,7 @@
|------|----------|
| cells key 用字段名称 `"课程名称"` | 用 fieldId `"fldXXX"` |
| progress 写入 `75` | 写入 `0.75`(范围 0~1) |
| attachment 直接传文件路径 | 必须先 upload 获取 fileToken |
| attachment 直接传文件路径或图片 URL | 必须先 `attachment upload` 获取 fileToken,再用 fileToken 写入(直传 URL 会超时) |
| user 字段传用户名字符串 | 传对象数组 `[{"userId":"...", "corpId":"..."}]` |
| group 字段用 `openConversationId` | 用 `cid` |
| singleSelect 传 option id 字符串 | 传 name 字符串或 `{"id":"...", "name":"..."}` 对象 |
@@ -0,0 +1,273 @@
# 命令索引表
## 命令索引表
### base (Base 管理) → 详见 [aitable-base-index.md](./aitable-base-index.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `base list` | 列出最近访问的 Base | — | 仅返回最近访问过的,优先用 `base search` |
| `base search` | 按名称搜索 Base | `--query` | 关键词 ≥2 字符 |
| `base get` | 获取 Base 信息(含 tables 列表) | `--base-id` | 用户给 URL 时提取末尾 ID |
| `base create` | 创建 Base | `--name` | 创建后直接用返回的 baseId;**默认新建的 base 自带一个空白「数据表」(含 3 行空记录)和一个空白仪表盘**,如需干净的空 base,传 `--template-id 1743` |
| `base update` | 更新 Base 名称 | `--base-id` `--name` | — |
| `base delete` | 删除 Base | `--base-id` | 不可逆 |
### table (数据表管理) → 详见 [aitable-table-index.md](./aitable-table-index.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `table get` | 获取表结构(字段+视图目录) | `--base-id` | 不传 `--table-ids` 返回全部表 |
| `table create` | 创建数据表 | `--base-id` `--name` `--fields` | fields 为 JSON 数组,至少 1 个 |
| `table update` | 修改表名 / 备注 / 行命名规则 | `--base-id` `--table-id` + 三选一(`--name` / `--description` / `--record-name-key`) | `--record-name-key` 是固定枚举(如 task/project/event/customer/ji_lu 等),非字段 ID |
| `table delete` | 删除表 | `--base-id` `--table-id` | 不可逆 |
### field (字段管理) → 详见 [aitable-field-index.md](./aitable-field-index.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `field get` | 获取字段完整配置 | `--base-id` `--table-id` | 按需展开少量字段 |
| `field create` | 创建字段 | `--base-id` `--table-id` + (`--name --type` 或 `--fields`) | 支持单字段/批量模式 |
| `field update` | 更新字段名/配置 | `--base-id` `--table-id` `--field-id` | 不可变更字段类型 |
| `field delete` | 删除字段 | `--base-id` `--table-id` `--field-id` | 不可逆 |
#### 搜索字段选项
```
Usage:
dws aitable field search-options [flags]
Example:
dws aitable field search-options --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID>
dws aitable field search-options --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID> --keyword 已完成
dws aitable field search-options --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID> --limit 100
Flags:
--base-id string Base ID (必填)
--field-id string 目标字段 ID,必须是 singleSelect / multipleSelect 类型 (必填)
--keyword string 模糊搜索关键词,大小写不敏感、contains 匹配 option name;不传返回全部
--limit int 返回的最大 option 数量,默认 3000(全量),最大 3000
--table-id string Table ID (必填)
```
仅适用于 **singleSelect / multipleSelect** 字段。其他类型(text/number/date/...)调用会返回错误。
适用场景:
- options 较多,只想要含某关键词的子集(避免 `field get` 拉取整个字段配置带回所有 options)。
- 写入 record 前预览选项 id ↔ name 的映射,确认要使用的选项确实存在。
> **写 record 时**:`record create / update` 对 singleSelect/multipleSelect 可直接传 option **name**,不需要用本命令。本命令主要用于 **filter** 写法(filters 优先用 option **id**)或选项较多需要精确定位时。
### record (记录管理) → 详见 [aitable-record-index.md](./aitable-record-index.md)
| 命令 | 用途 | 必读 reference | 路由提醒 |
|------|------|----------------|----------|
| `record query` | 查询/搜索记录 | [aitable-record-query.md](./aitable-record-query.md) | 先 `table get` 拿 fieldId;`--all` 自动翻页;filters 结构见 reference |
| `record get` | 按 ID 取记录(`record query --record-ids` 的窄别名) | [aitable-record-query.md](./aitable-record-query.md) | 已知 recordId 时首选;必填 `--record-ids`(单次最多 100 条);未暴露 filters/sort/query/cursor/limit |
| `record create` | 新增记录 | [aitable-record-create.md](./aitable-record-create.md) | cells key 必须是 fieldId 不是字段名;单次最多 100 条 |
| `record update` | 更新记录(每条独立 cells) | [aitable-record-update.md](./aitable-record-update.md) | 需先 query 拿 recordId;只传需改字段;`--records` 是 `[{recordId,cells},...]` 数组 |
| `record batch-update` | 批量更新(同一 cells 应用到多条 recordId) | [aitable-record-update.md](./aitable-record-update.md)、[aitable-cell-value.md](./aitable-cell-value.md) | 适合"统一标记完成/统一改负责人"等共享 patch 场景;`--cells` 是 JSON object(key=fieldId,value 按字段类型见 cell-value.md),与 record update 的单条 cells 结构完全一致;必填 `--record-ids` `--cells`;单次最多 100 条 |
| `record delete` | 删除记录 | [aitable-record-delete.md](./aitable-record-delete.md) | 不可逆,需先 query 确认 |
| `record history-list` | 查询单条记录的变更历史 | [aitable-record-history.md](./aitable-record-history.md) | 必填 `--record-id`;分页 `--offset --limit`,limit 范围 [1,50] 默认 20 |
| `record query-empty` | 查询完全没填用户字段的空行 | [aitable-record-query.md](./aitable-record-query.md) | 一页扫描 `--limit` [1,100] 默认 100;扫完前需用 `--cursor` 翻页(nextCursor 为空才表扫完) |
| `record share-url` | 批量获取记录分享链接 | [aitable-record-share.md](./aitable-record-share.md) | 必填 `--record-ids`(CSV,单次最多 20 条);可选 `--view-id` 带视图上下文 |
| `record upsert` | 批量创建或更新(按 recordId 是否存在自动拆分) | [aitable-record-upsert.md](./aitable-record-upsert.md) | --records 同 record update 格式;带 recordId 走 update,不带走 create;单次最多 100 |
| `record primary-doc-get` | 查询记录的主键文档 nodeId | [aitable-primary-doc.md](./aitable-primary-doc.md) | 返回的 nodeId 可直接用于 `dws doc read/update --node` |
| `record primary-doc-create` | 为记录创建主键文档(幂等) | [aitable-primary-doc.md](./aitable-primary-doc.md) | fieldId 必须是 primaryDoc 类型;已存在则返回已有 nodeId |
### view (视图管理) → 详见 [aitable-view-index.md](./aitable-view-index.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `view get` | 获取视图配置(不传子命令) | `--base-id` `--table-id` | 不传 `--view-ids` 返回全部视图 |
| `view get <attr>` | 获取视图某个属性 | `--view-id` | 12 个:card/timebar/aggregate/filter/sort/group/visible-fields/field-widths(详见 [aitable-view-config.md](./aitable-view-config.md))+ lock/frozen-cols/row-height/fill-color-rule(详见 [aitable-view-extras.md](./aitable-view-extras.md)) |
| `view list` | 列出全部视图(`view get` 的别名) | `--base-id` `--table-id` | 与 `view get` 完全等价 |
| `view create` | 创建视图 | `--base-id` `--table-id` `--view-type` | 类型: Grid/Kanban/Gantt/Calendar/Gallery/FormDesigner;**Gantt 创建后必须 `view update timebar` 绑定日期字段** |
| `view update` | 整体更新视图 / 多属性合并更新 | `--base-id` `--table-id` `--view-id` | 可传 `--name --desc --config '{...}'`,**`--config` 路径继续保留** |
| `view update <attr>` | 按属性局部更新(推荐)| `--view-id` + typed flag / `--json` | 12 个:card/timebar/aggregate/field-widths/visible-fields/filter/sort/group/name + frozen-cols/row-height/fill-color-rule |
| `view lock [--off]` | 锁定/解锁视图 | `--base-id` `--table-id` `--view-id` | 默认锁定;`--off` 解锁。详见 [aitable-view-extras.md](./aitable-view-extras.md) |
| `view duplicate` | 复制视图 | `--base-id` `--table-id` `--view-id` | 可选 `--new-name`;保留源视图全部配置。详见 [aitable-view-extras.md](./aitable-view-extras.md) |
| `view delete` | 删除视图 | `--base-id` `--table-id` `--view-id` | 不可删最后一个/锁定视图 |
> **优先用 `view get <attr>` / `view update <attr>` 子命令**:每个属性独立命令,typed flag 友好,agent 不必拼 JSON。**`view update --config '{...}'` 仍可用**,适合一次性多属性更新或脚本场景。
> **属性按 attr 分类,决定该读哪份子文档**:
> - card / timebar / aggregate / filter / sort / group / visible-fields / field-widths → [aitable-view-config.md](./aitable-view-config.md)
> - lock / frozen-cols / row-height / fill-color-rule / duplicate → [aitable-view-extras.md](./aitable-view-extras.md)
> 后一类**不能**塞进 `view update --config '{...}'`,必须用各自专属子命令;如果错传 `flags` / `frozenColCount` / `cellHeight` / `conditionalFormats` 等 key 进 `--config`,CLI 会在 stderr 提示应改用的命令。
> **`view update --config` 支持的 9 个 key**:
> `visibleFieldIds` / `filter` / `sort` / `group` / `fieldWidths`(Grid) / `aggregate`(Grid) / `kanbanCard`(Kanban) / `ganttTimebar`(Gantt) / `galleryCard`(Gallery)。
> filter/sort/group 必须传**数组**格式(与 `record query --filters` 的对象格式不同;CLI 会自动容错)。其他 key 会被服务端忽略并打 warning。
### form (表单管理) → 详见 [aitable-form-index.md](./aitable-form-index.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `form list` | 列出表单视图 | `--base-id` `--table-id` | 详情见 [aitable-form.md](./aitable-form.md) |
| `form get` | 按 viewId 取单个表单详情 | `--base-id` `--table-id` `--view-id` | — |
| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | — |
| `form update` | 更新表单配置 | `--base-id` `--table-id` `--view-id` | title/name/description 至少一项 |
| `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` | 不可逆 |
| `form field list/update/hide` | 表单字段管理 | — | 详情见子文档 |
| `form questions create/delete` | 题目管理(=field create/delete) | — | 详情见子文档 |
| `form share get/update` | 表单分享配置 | — | 详情见子文档 |
> **创建表单**有两种等价方式:`form create --name "..."`(推荐)或 `view create --view-type FormDesigner --name "..."`。
### workflow (自动化工作流) → 详见 [aitable-workflow-index.md](./aitable-workflow-index.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `workflow list` | 列出 Base 下所有工作流 | `--base-id` | 支持 `--limit [1,100]` / `--offset >=0`;list 出参字段叫 `flowId` |
| `workflow get` | 获取单个工作流详情(含 flowSchema) | `--base-id` `--workflow-id` | `--workflow-id` 接受 list 里的 `flowId`(同值) |
| `workflow enable` | 启用工作流 | `--base-id` `--workflow-id` | 返回 `{enabled: true}` 是动作确认;要确认真启用看 list 的 `status` |
| `workflow disable` | 禁用工作流(高危) | `--base-id` `--workflow-id` `--yes` | 影响业务自动化,建议二次确认;status 变 STOP |
> **当前不支持通过 CLI 新建/修改/删除工作流**,请去 AI 表格 Web 端(数据表页面 → 自动化)配置。
### dashboard & chart → 详见 [aitable-dashboard-index.md](./aitable-dashboard-index.md)
| 命令 | 用途 |
|------|------|
| `dashboard get/create/update/delete` | 仪表盘管理 |
| `dashboard config-example` | 查看仪表盘配置模板 |
| `dashboard arrange` | 自动重排仪表盘图表布局(智能填满网格,避免空缺) |
| `chart get/create/update/delete` | 图表管理 |
| `chart widgets-example` | 查看图表 widgets 配置模板 |
### export & import → 详见 [aitable-export-import-index.md](./aitable-export-import-index.md)
| 命令 | 用途 |
|------|------|
| `export data` | 导出数据(异步两阶段轮询) |
| `import upload` | 申请文件导入上传凭证 |
| `import data` | 触发导入 |
### attachment → 详见 [aitable-attachment-index.md](./aitable-attachment-index.md)
| 命令 | 用途 | 路由提醒 |
|------|------|----------|
| `attachment upload` | 准备附件上传凭证 | 不要用钉盘 drive 上传! |
### template (模板搜索)
| 命令 | 用途 | 必填参数 |
|------|------|----------|
| `template search` | 搜索模板 | `--query` |
### advperm (高级权限/自定义角色) → 详见 [aitable-advperm-index.md](./aitable-advperm-index.md)
| 命令 | 用途 | 必填参数 | 路由提醒 |
|------|------|----------|----------|
| `advperm enable` | 开启 Base 高级权限总开关 | `--base-id` | 不开启时角色规则不生效 |
| `advperm disable` | 关闭 Base 高级权限总开关(高危) | `--base-id` `--yes` | 关闭后全员回退默认权限 |
| `advperm role-list` | 列出 Base 下所有角色 | `--base-id` | 同时返回自定义角色和系统角色;`roleType == "custom"` 是自定义,前缀 `system_` 是系统角色 |
| `advperm role-get` | 获取单角色完整配置 | `--base-id` `--role-id` | 含 subRoles 与字段/行级规则 |
| `advperm role-create` | 创建自定义角色 | `--base-id` `--name` | 可选 `--sub-roles` 同时指定子角色权限规则 |
| `advperm role-update` | 增量更新自定义角色(PATCH) | `--base-id` `--role-id` | 未传字段不变;`--sub-roles` 按 (targetId,targetType) 合并 |
| `advperm role-delete` | 删除自定义角色 | `--base-id` `--role-id` `--yes` | 不可逆;系统角色禁删;**调用者必须是该 AI 表格的管理员/Owner**,非管理员会得到 401 AUTH_ERROR |
> **角色 CRUD 已全支持**:create/get/list/update/delete 都可走 CLI。
> 所有写命令(enable/disable/role-create/role-update/role-delete)需要 Base 管理员权限;非管理员只能调 `role-list` / `role-get`(只读)。
> "角色 ↔ 成员"绑定当前 CLI 不支持,仍需在 AI 表格 Web 端 → Base 设置 → 高级权限面板手动完成。
### section (文件夹与节点管理)
> 用于在 Base 的导航树中组织 table / dashboard / 表单视图 / 文档等节点(类似文件夹)。
> 操作前建议先用 `section list-nodes` 拿到 nodeId / sectionId 与父级关系。
#### 创建文件夹
```
Usage:
dws aitable section create [flags]
Example:
dws aitable section create --base-id <BASE_ID> --name 我的文件夹
dws aitable section create --base-id <BASE_ID> --name 子文件夹 --parent-section-id <SECTION_ID> --index 0
Flags:
--base-id string Base ID (必填)
--name string 文件夹名称 (必填)
--parent-section-id string 父文件夹 ID;不传或空字符串表示创建在 Base 根目录下
--index int 在父文件夹下的目标位置(0-based);不传则追加到末尾
```
返回 `data.sectionId` 与 `data.name`。
#### 重命名文件夹
```
Usage:
dws aitable section rename [flags]
Example:
dws aitable section rename --base-id <BASE_ID> --section-id <SECTION_ID> --new-name 新名称
Flags:
--base-id string Base ID (必填)
--section-id string 目标文件夹 ID (必填)
--new-name string 新的文件夹名称 (必填)
```
#### 删除文件夹
```
Usage:
dws aitable section delete [flags]
Example:
dws aitable section delete --base-id <BASE_ID> --section-id <SECTION_ID>
Flags:
--base-id string Base ID (必填)
--section-id string 目标文件夹 ID (必填)
```
> **注意**:删除不可逆;删除前可先用 `section list-empty` 确认是否为空文件夹。
#### 调整文件夹顺序
```
Usage:
dws aitable section reorder [flags]
Example:
dws aitable section reorder --base-id <BASE_ID> --section-id <SECTION_ID> --target-index 0
Flags:
--base-id string Base ID (必填)
--section-id string 目标文件夹 ID (必填)
--target-index int 目标位置(0-based)(必填)
```
> 在**当前父文件夹下**调整展示顺序。跨父级移动请用 `section move-node`。
#### 列出空文件夹
```
Usage:
dws aitable section list-empty [flags]
Example:
dws aitable section list-empty --base-id <BASE_ID>
Flags:
--base-id string Base ID (必填)
```
返回 `data.items: [{sectionId, name, parentSectionId}]` 与 `data.total`,用于清理或诊断导航树(parentSectionId 为空串表示在根目录下)。
#### 列出全部节点
```
Usage:
dws aitable section list-nodes [flags]
Example:
dws aitable section list-nodes --base-id <BASE_ID>
Flags:
--base-id string Base ID (必填)
```
返回 `data.items: [{nodeId, nodeType, parentSectionId, name?}]` 与 `data.total`,涵盖文件夹 / AI 表格 / 表单视图 / 仪表盘 / 文档 / 查询视图。
> **与其他命令的关联**:是 `section move-node` / `section reorder` 的前置定位命令——先用它拿到 nodeId 与 parentSectionId。
#### 移动节点
```
Usage:
dws aitable section move-node [flags]
Example:
dws aitable section move-node --base-id <BASE_ID> --node-id <NODE_ID> --new-parent-section-id <SECTION_ID>
dws aitable section move-node --base-id <BASE_ID> --node-id <NODE_ID> --new-parent-section-id "" --target-index 0
Flags:
--base-id string Base ID (必填)
--node-id string 要移动的节点 ID(文件夹/AI表格/表单视图/仪表盘/文档/查询视图)(必填)
--new-parent-section-id string 目标父文件夹 ID;空字符串表示移到 Base 根目录 (必填)
--target-index int Base 内节点的全局位置(0-based);不传则不调整
```
> 服务端自动识别节点类型,无需区分文件夹与非文件夹。返回 `data.nodeId / newParentSectionId / nodeType`。
> 对文件夹节点带 `--target-index` 时会先 move 再 reorder,中间失败会返回 `MOVE_OK_REORDER_FAILED`,可用 `section reorder` 重试。
@@ -0,0 +1,39 @@
# 复杂操作
## 复杂操作
### 仪表盘 / 图表(建议顺序)
```bash
# 1) 先看配置模板(JSONC)
dws aitable dashboard config-example --format json
dws aitable chart widgets-example --format json
# 2) 先拿 dashboard,再拿 chart 详情
dws aitable dashboard get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --format json
dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --format json
```
要点:
- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用。
- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错。
- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断。
### 导出数据(两阶段轮询)
`export data` 常见为异步任务:首次调用可能只返回 `taskId`,需要继续轮询。
```bash
# 第一步:创建任务(按 scope 传必要参数)
dws aitable export data --base-id <BASE_ID> --scope table --table-id <TABLE_ID> --format excel --timeout-ms 1000
# 第二步:拿 taskId 继续轮询,直到返回 downloadUrl
dws aitable export data --base-id <BASE_ID> --task-id <TASK_ID> --timeout-ms 3000
```
参数约束
- `scope=all`:只需 `base-id`
- `scope=table`:必须 `table-id`
- `scope=view`:必须同时 `table-id + view-id`
@@ -0,0 +1,13 @@
# 上下文传递表
## 上下文传递表
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `base list/search` | `baseId` | 所有后续命令的 --base-id,拼接文档 URI |
| `base create` | `baseId` | 后续命令 + 文档 URI |
| `base get` | `tables[].tableId` | --table-id,拼接指定数据表 URI |
| `table create` | `tableId` | 后续命令 + 拼接指定数据表 URI |
| `table get` | `fields[].fieldId` | record 操作的 cells key, field get/update/delete |
| `record query` | `recordId` | record update/delete;按 ID 反查字段值用 `record get` |
| `template search` | `templateId` | base create --template-id,拼接模板预览 URI |
@@ -15,10 +15,8 @@ dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-
## 要点
- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用
- `dashboard share get` 可能返回 `404`(`retryable:true`)——从未分享、甚至刚开启分享后立即查都可能 404;`data` 为 `{}`。按可重试错误处理,不要误判为参数拼错。它有时也会返回 `success + data`(如关闭分享之后),行为不稳定,别用它当"是否已分享"的唯一判据。
- `chart share get` 稳定返回 `success + data`(含 `enabled` 等),可用于分享状态判断;从未分享时 `enabled=false`,不会 404。
- ⚠️ **`dashboard share update` 开启 ORG 分享后返回的 `shareType` 是 `"[1]"`(未映射回 `ORG`,服务端已知问题)**;`chart share update --share-type ORG` 则正确返回 `shareType="ORG"`。判断 dashboard 是否 ORG 分享时对 `"[1]"` 做兼容。
- `chart share update` / `dashboard share update` 的 `--enabled` 是字符串 flag:`--enabled false`(空格)和 `--enabled=false` 都能正确关闭分享。
- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错
- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断
## dashboard 子命令
@@ -27,25 +25,19 @@ dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-
| `dashboard get` | 获取仪表盘详情(含 charts 列表) | `--base-id` `--dashboard-id` | — |
| `dashboard create` | 创建仪表盘 | `--base-id` + (`--config` 或 `--name`) | `--name` 简化版创建空看板;`--config` 传完整 JSON |
| `dashboard update` | 更新仪表盘 | `--base-id` `--dashboard-id` + (`--config` 或 `--name`) | `--name` 仅改名;`--config` 更新完整配置 |
| `dashboard arrange` | 自动重排仪表盘图表布局 | `--base-id` `--dashboard-id` | 让服务端重新排布 charts 位置 |
| `dashboard delete` | 删除仪表盘 | `--base-id` `--dashboard-id` `--yes` | — |
| `dashboard config-example` | 查看仪表盘配置模板 | 无 | 创建前先调此命令了解 config 结构 |
| `dashboard share get` | 获取仪表盘分享配置 | `--base-id` `--dashboard-id` | 可能 404,见上方要点 |
| `dashboard share update` | 更新仪表盘分享配置 | `--base-id` `--dashboard-id` `--enabled` | `--enabled true` 开启,`--share-type` 用 `ORG`(ORG 回显 `shareType="[1]"`);部分组织禁用了 `PUBLIC` 公开分享;若报 `Illegal argument`,改用 `ORG`(组织内分享)。`--enabled false` 关闭 |
| `dashboard arrange` | 自动重排图表布局 | `--base-id` `--dashboard-id` | 把图表按行铺满网格,避免某行只占半幅、留下大片空白;返回 `{totalColumns, layout, alignedChartCount}` |
## chart 子命令
| 命令 | 用途 | 必填参数 |
|------|------|----------|
| `chart get` | 获取图表详情 | `--base-id` `--dashboard-id` `--chart-id` |
| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` `--layout` |
| `chart create` | 创建图表 | `--base-id` `--dashboard-id` `--config` |
| `chart update` | 更新图表配置 | `--base-id` `--dashboard-id` `--chart-id` `--config` |
| `chart delete` | 删除图表 | `--base-id` `--dashboard-id` `--chart-id` `--yes` |
| `chart widgets-example` | 查看图表 widgets 配置模板 | 无 |
| `chart share get` | 获取图表分享配置 | `--base-id` `--dashboard-id` `--chart-id` |
| `chart share update` | 更新图表分享配置(`--enabled true/false`,开启配 `--share-type`,用 `ORG`;部分组织禁用了 `PUBLIC` 公开分享;报 `Illegal argument` 时改用 `ORG`) | `--base-id` `--dashboard-id` `--chart-id` `--enabled` |
> `chart create` 的 `--layout` 是**必填**(12 列网格布局,如 `{"x":0,"y":0,"w":6,"h":4}`);不传本地校验直接拒。`chart update` 的 `--config` 也**必填**——即便只想改 layout,也要带完整 config,否则服务端拒绝。
## 配置获取流程
@@ -0,0 +1,6 @@
# 仪表盘与图表
| 命令 | 用途 | 详见 |
|---|---|---|
| `dashboard get/create/update/delete/arrange/config-example` | 仪表盘管理 | [`aitable-dashboard-chart.md`](./aitable-dashboard-chart.md) |
| `chart get/create/update/delete/widgets-example` | 图表管理 | [`aitable-dashboard-chart.md`](./aitable-dashboard-chart.md) |
@@ -0,0 +1,6 @@
# 导入导出
| 命令 | 用途 | 详见 |
|---|---|---|
| `export data` | 导出数据 | [`aitable-export-import.md`](./aitable-export-import.md) |
| `import upload/data` | 触发导入 | [`aitable-export-import.md`](./aitable-export-import.md) |
@@ -113,32 +113,3 @@ dws aitable import data --import-id <importId> --table-id <TABLE_ID> --format js
> **导入数据无法整体撤销**:文件一旦导入成功,数据即写入表中,没有"撤销导入"操作。如需清理导入的测试数据,只能手动通过 `record delete` 逐条或批量删除记录;如果是新建表模式导入的,可以直接 `table delete` 删除整张表。因此:
> - 测试/验证场景建议导入到**独立的测试表或测试 Base**,用完后整体删除
> - 如果用户明确表示不想导入测试数据或要求先预览内容再决定,应先解析文件内容展示给用户确认,而非直接导入
---
## 导入数据:两条链路怎么选
用户给文件让导入 AI 表格时,路径选择决定成败:
> ⚠️ **`import upload` 没有 `--file` flag**(传了会报 unknown flag)。它只申请上传凭证,必填 `--file-name` + `--file-size`,拿到 `uploadUrl` 后要**自己 curl PUT 上传文件**(Content-Type 留空,见上文三步流程),再 `import data`。想省事直接用 `aitable_import_via_task.py` 脚本,它把这三步包好了。
| 用户原话 | 链路 | 命令 / 脚本 | 行为 |
|---------|------|------------|------|
| "把这个 Excel 导入到 AI 表格"(无指定目标表) | **文件导入任务** | `python scripts/aitable_import_via_task.py <baseId> <file>`(推荐)或手动三步 `import upload --file-name x.xlsx --file-size <字节>` → curl PUT → `import data --import-id <ID>` | 服务端解析文件,**新建数据表**,自动识别表头 |
| "把这个 Excel 导入新表 / 自动建表" | 同上 | 同上 | 同上 |
| "把这批记录追加到已有的『成员表』里" | **记录批量写入** | `python scripts/import_records.py <baseId> <tableId> <file>` | 走 `record create`,**写入已有 tableId**,需要字段名匹配 |
| "Excel 列名和表字段对不上但要追加" | 文件导入 + 追加 + 字段映射 | 三步导入后 `import data --import-id <ID> --table-id <TBL> --field-mapping '{"目标":"源"}'` | 服务端按映射追加 |
## 大表 / 长任务超时续等
单次等待窗口很短:`export data` 只有 `--timeout-ms`(默认且**上限 30000 = 30 秒**,没有 `--timeout-sec`,传了会报 unknown flag);`import data` 用 `--timeout`(秒,默认且推荐最大值 30)。窗口内没跑完,命令会返回 `taskId` / `importId`,用同命令带 ID 反复续等即可:
```bash
# 续等导出:拿到 downloadUrl 后再 curl 下载(见下方警告)
dws aitable export data --base-id <B> --task-id <ID> --timeout-ms 30000
# 续等导入
dws aitable import data --import-id <ID>
```
> ⚠️ **`--output` 不会保存导出的 xlsx**:`--output` 是隐藏的全局 flag,作用是把命令的 **JSON 输出**写到文件,实测只生成一个 0 字节文件,不会下载导出内容。正确做法是从 `export data` 返回里取 `downloadUrl`,再 `curl -L "<downloadUrl>" -o out.xlsx` 下载。想省事直接用 `aitable_export_via_task.py` 脚本(它负责轮询 + 下载)。
@@ -0,0 +1,9 @@
# 字段管理
| 命令 | 用途 | 详见 |
|---|---|---|
| `field get` | 获取字段完整配置 | [`aitable-field.md`](./aitable-field.md) |
| `field create` | 创建字段 | [`aitable-field.md`](./aitable-field.md) |
| `field update` | 更新字段名/配置 | [`aitable-field.md`](./aitable-field.md) |
| `field delete` | 删除字段 | [`aitable-field.md`](./aitable-field.md) |
| `field search-options` | 搜索字段选项 | [`aitable-field-properties.md`](./aitable-field-properties.md) |
@@ -1,5 +1,7 @@
# filters & sort — 筛选排序语法参考
> 视图(view)配置的 filter/sort/group **整体写入**请优先用 `view update filter` / `view update sort` / `view update group` 子命令,详见 [aitable-view-config.md](./aitable-view-config.md)。本文件聚焦于 `record query --filters` 与 view config filter 的语法和差异。
## filters 结构规范
### 强制规则
@@ -49,11 +51,34 @@ CLI 同时兼容两种子条件写法(推荐格式 A):
| `exist` / `un_exist` | 有值 / 为空 | `["fieldId"]`(无需第二项) |
| `any_of` / `none_of` / `all_of` | 包含任一 / 不包含任一 / 全包含(多选字段) | `["fieldId", "optionName"]` |
| `date_eq` / `before` / `after` | 日期等于 / 早于 / 晚于 | `["fieldId", "dateStr"]` |
| `not_before` / `not_after` | 不早于 / 不晚于 | `["fieldId", "dateStr"]` |
| `from_now` | 从现在起 N 天内 | `["fieldId", "天数"]` |
| `date_between` | 日期区间 | `["fieldId", "[startTs, endTs]"]` |
| `not_before` / `not_after` | 不早于(≥) / 不晚于(≤) | `["fieldId", "2026-05-22"]` |
> **操作符拼写必须严格匹配上表**,CLI 会在调用前校验,错误拼写会被拒绝。
>
> **没有 `date_between`(区间)操作符**,也**不支持 `from_now`**——date 字段不支持区间/相对过滤,传了会被 CLI 拒绝。范围查询用 `not_before` + `not_after` 组合,见下方专节。
### 日期字段过滤(date / 创建时间 / 修改时间)
日期类字段的过滤规则与其它字段**不同**,是线上反馈最高频的踩坑点。**经集成测试实测**确认的规则:
1. **只能用日期专用操作符**:`date_eq` / `before` / `after` / `not_before` / `not_after` / `exist` / `un_exist`(与前端筛选 UI 的「等于 / 早于 / 晚于 / 早于或等于 / 晚于或等于 / 不为空 / 为空」一一对应)。
2. **比较值用日期字符串**,如 `"2026-05-22"`(也接受 RFC3339 / 毫秒时间戳,内部统一转成毫秒比较)。读取返回的是带时区 RFC3339(如 `"2026-05-22T00:00:00+08:00"`)。
3. **通用操作符 `eq` / `ne` / `gt` / `gte` / `lt` / `lte` / `contain` 对 date 字段无效**——无论传 ISO 字符串还是毫秒时间戳,都会**静默返回 0 条**。这是后端 date 字段的比较规则,不是 bug,CLI 也无法在本地拦截(不知道字段类型),务必用对操作符。
4. **没有区间操作符 `date_between`**,也**不支持 `from_now`(相对天数)**——均会静默返回 0 条,CLI 已直接拒绝。范围查询用 `not_before`(≥起点)+ `not_after`(≤终点)两个条件 `and` 组合。
| 需求 | 操作符 | 示例 operands |
|------|--------|--------------|
| 等于某天 | `date_eq` | `["fldDate", "2026-05-22"]` |
| 早于 / 晚于(不含当天) | `before` / `after` | `["fldDate", "2026-05-22"]` |
| 不早于(≥) / 不晚于(≤) | `not_before` / `not_after` | `["fldDate", "2026-05-22"]` |
| 有值 / 为空 | `exist` / `un_exist` | `["fldDate"]` |
**日期区间查询(替代 between)**——查 `2026-05-01 ~ 2026-05-31`(含端点):
```bash
dws aitable record query --base-id X --table-id Y \
--filters '{"operator":"and","operands":[{"operator":"not_before","operands":["fldDate","2026-05-01"]},{"operator":"not_after","operands":["fldDate","2026-05-31"]}]}'
```
### 常见错误拼写(CLI 会自动提示纠正)
@@ -104,3 +129,41 @@ dws aitable record query --base-id X --table-id Y \
```bash
--sort '[{"fieldId":"fldPriority","direction":"desc"},{"fieldId":"fldCreatedAt","direction":"asc"}]'
```
---
## view update --config 中的 filter / sort 格式
> **重要区分**:`record query --filters` 和 `view update --config` 中的 filter **格式不同**!
| 场景 | filter 格式 | 说明 |
|------|-------------|------|
| `record query --filters` | **对象**:`{"operator":"and","operands":[...]}` | 直接传最外层逻辑对象 |
| `view update --config` 的 filter | **数组**:`[{"operator":"and","operands":[...]}]` | 外面多一层数组包裹 |
| `view update --config` 的 sort | **数组**:`[{"fieldId":"X","direction":"asc"}]` | 与 record query --sort 一致 |
### 正确示例
```bash
# view update 设置筛选(filter 是数组)
dws aitable view update --base-id X --table-id Y --view-id Z \
--config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","待处理"]}]}]}'
# view update 设置排序(sort 是数组)
dws aitable view update --base-id X --table-id Y --view-id Z \
--config '{"sort":[{"fieldId":"fldPriority","direction":"desc"}]}'
# 同时设置 filter + sort + visibleFieldIds
dws aitable view update --base-id X --table-id Y --view-id Z \
--config '{"filter":[{"operator":"and","operands":[{"operator":"eq","operands":["fldStatus","进行中"]}]}],"sort":[{"fieldId":"fldDate","direction":"asc"}],"visibleFieldIds":["fld1","fld2","fld3"]}'
```
### CLI 自动容错
CLI 会自动修正以下常见错误格式(不会报错,但建议直接使用正确格式):
| 错误写法 | CLI 自动修正为 |
|----------|---------------|
| `"filter":{"operator":"and",...}` (对象) | `"filter":[{"operator":"and",...}]` (数组) |
| `"sort":{"fieldId":"X","direction":"asc"}` (对象) | `"sort":[{"fieldId":"X","direction":"asc"}]` (数组) |
| 子条件用 MCP 简写 `{"fieldId":"X","operator":"eq","value":"Y"}` | 自动转为 `{"operator":"eq","operands":["X","Y"]}` |
@@ -0,0 +1,8 @@
# 表单管理
| 命令 | 用途 | 详见 |
|---|---|---|
| `form list/get/create/update/delete` | 表单视图管理 | [`aitable-form.md`](./aitable-form.md) |
| `form field list/update/hide` | 表单字段管理 | [`aitable-form.md`](./aitable-form.md) |
| `form questions create/delete` | 题目管理 | [`aitable-form.md`](./aitable-form.md) |
| `form share get/update` | 表单分享配置 | [`aitable-form.md`](./aitable-form.md) |
@@ -50,8 +50,8 @@ dws aitable form share get --base-id BASE_ID --table-id TABLE_ID --view-id VIEW_
| 命令 | 用途 | 必填参数 | 说明 |
|------|------|----------|------|
| `form list` | 列出表单视图 | `--base-id` `--table-id` | 每条返回 viewId/name;新建表单**无 title 且 createdAt=0**,改过(form update)后才出现 title 和真实 createdAt |
| `form get` | 按 viewId 取单个表单 | `--base-id` `--table-id` `--view-id` | 客户端按 viewId 过滤后 `data` **即该表单对象**(不是 formViews 数组);viewId 不存在返回 `form view ... not found` 错误 |
| `form list` | 列出表单视图 | `--base-id` `--table-id` | 返回 viewId/name/title/createdAt |
| `form get` | 按 viewId 取单个表单 | `--base-id` `--table-id` `--view-id` | 内部基于 list_form_views 过滤 |
| `form create` | 创建表单视图 | `--base-id` `--table-id` `--name` | viewType=FormDesigner |
| `form update` | 更新表单 | `--base-id` `--table-id` `--view-id` | `--title`/`--name`(等价)和 `--description` 至少传一项;同时传 title/name 时 title 优先 |
| `form delete` | 删除表单 | `--base-id` `--table-id` `--view-id` `--yes` | 不可逆 |
@@ -115,6 +115,6 @@ dws aitable form share update --base-id BASE_ID --table-id TABLE_ID --view-id VI
## 返回结构补充
- `form list` 返回 `data.formViews[]`,**每条含** `viewId/name`(+ `createdAt`);**新建表单没有 `title` 字段且 `createdAt=0`**,只有在 `form update` 碰过之后才会出现 `title` 和真实 `createdAt`。`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。
- `form get` 的 `data` **就是命中的那一条表单对象**(如 `{viewId, name, createdAt, title?, shareFormUuid?}`),不是 `formViews` 数组。Agent 直接读 `data.viewId` / `data.name` 即可,**不要**再取 `data.formViews[0]`。服务端的 viewIds 过滤参数当前不生效,CLI 在客户端按 viewId 精确筛出单条;传了不存在的 viewId 会返回 `form view <id> not found in table` 错误。
- `form list` 返回 `data.formViews[]`,**每条仅含** `viewId/name/title/createdAt`;`shareFormUuid` 不在此返回,请用 `form share get` 单独获取。
- `form get` 返回结构与 `form list` 完全一致(`data.formViews[]`),仅含一条记录(与请求 viewId 一致)。Agent 提取时仍走 `data.formViews[0]`。
- `form field list` 仅返回**未隐藏**的字段;`hidden=true` 的字段不在此返回,如需查看全部字段请用 `field get`。
@@ -0,0 +1,93 @@
# 意图判断
## 意图判断
用户说"表格/多维表/AI表格":
- 查看/查找/列表 → `base search`(优先)或 `base list`(仅浏览最近访问)
- 详情 → `base get`
- 创建 → `base create`
- 修改 → `base update`
- 删除 → `base delete`
用户说"数据表/子表/table":
- 查看 → `table get`
- 创建 → `table create`
- 重命名 / 改备注 / 改行命名规则 → `table update`(三选一:`--name` / `--description` / `--record-name-key`)
- 用户说"行命名规则/记录别名/卡片显示成 task/project/event 这种" → `table update --record-name-key <枚举键>`,**中文 → 枚举键**对照见 [aitable-record-name-key.md](./aitable-record-name-key.md)
- 删除 → `table delete`
用户说"字段/列/column":
- 查看 → `field get`
- 添加 → `field create`(读 [aitable-field.md](./aitable-field.md))
- 修改 → `field update`
- 删除 → `field delete`
用户说"记录/行/数据/row":
- 查看/搜索 → `record query`(读 [aitable-record-query.md](./aitable-record-query.md))
- 找空行 / 没填东西的行 → `record query-empty`(读 [aitable-record-query.md](./aitable-record-query.md))
- 已知 recordId 反查字段值 → `record get`(按 ID 取专用,等价 `record query --record-ids`)
- 添加/写入 → `record create`(读 [aitable-record-create.md](./aitable-record-create.md))
- 修改/更新(每条独立 cells) → `record update`(读 [aitable-record-update.md](./aitable-record-update.md))
- **批量更新同一字段值**(统一标记/统一改值) → `record batch-update --record-ids ... --cells '{...}'`
- 删除 → `record delete`
- **查记录的字段变更历史 / 操作审计** → `record history-list`(读 [aitable-record-history.md](./aitable-record-history.md))
- **取记录分享链接 / 把这行发给同事** → `record share-url`(读 [aitable-record-share.md](./aitable-record-share.md))
- **不知道有没有 → 有就改、没有就建** → `record upsert`(读 [aitable-record-upsert.md](./aitable-record-upsert.md))
用户说"视图/view":
- 列出/查看全部视图 → `view list`(或 `view get` 不传 --view-ids,二者等价)
- 看某个视图详情 → `view get --view-ids <ID>`
- 创建 → `view create`
- 修改(含"调整字段顺序/隐藏字段") → `view update --config '{"visibleFieldIds":[...]}'`
- 修改某一项配置(filter/sort/group/card/timebar/aggregate 等)→ `view update <attr>`(读 [aitable-view-config.md](./aitable-view-config.md))
- 锁定 / 冻结列 / 行高 / 数据高亮规则 / 复制视图 → 读 [aitable-view-extras.md](./aitable-view-extras.md)
- 删除 → `view delete`
用户说"锁定视图/解锁视图/lock view" → `view lock` / `view lock --off`,详见 [aitable-view-extras.md](./aitable-view-extras.md)
用户说"冻结列/冻结首列/frozen columns" → `view update frozen-cols --count N`,详见 [aitable-view-extras.md](./aitable-view-extras.md)
用户说"行高/单元格高度/紧凑模式/cell height" → `view update row-height --cell-height N`(合法档位 32/56/88/128),详见 [aitable-view-extras.md](./aitable-view-extras.md)
用户说"数据高亮/条件格式/单元格上色/fill color rule" → `view update fill-color-rule --json '[...]'`,详见 [aitable-view-extras.md](./aitable-view-extras.md)
用户说"复制视图/duplicate view" → `view duplicate --view-id ... [--new-name ...]`,详见 [aitable-view-extras.md](./aitable-view-extras.md)
用户说"筛选/过滤/filter" → 读 [aitable-filter-sort.md](./aitable-filter-sort.md)
用户说"统计/分析/聚合/TOP N/全量" → 读 [aitable-data-analysis-sop.md](./aitable-data-analysis-sop.md)
用户说"公式/formula/计算字段/派生指标" → 读 [aitable-formula-guide.md](./aitable-formula-guide.md)
用户说"查找引用/lookup/filterUp/跨表" → 读 [aitable-formula-guide.md](./aitable-formula-guide.md)(§5.4 跨表引用)
用户说"表单/form/收集表/问卷/催办填写" → 读 [aitable-form.md](./aitable-form.md)
用户说"自动化/工作流/流程/触发/automation/workflow" → 读 [aitable-workflow.md](./aitable-workflow.md)
- 看 Base 里有哪些流程 / 哪些在跑 → `workflow list`(看 `recordCount` / `runningCount`)
- 看某个流程具体配置(触发条件、动作步骤) → `workflow get`
- 启用流程 → `workflow enable`
- 临时停掉流程(调试 / 数据迁移)→ `workflow disable --yes`
- **新建 / 修改 / 删除流程**:当前不支持,引导用户到 AI 表格 Web 端 → 数据表 → 自动化 面板手动完成
用户说"仪表盘/图表/chart" → 读 [aitable-dashboard-chart.md](./aitable-dashboard-chart.md)
用户说"仪表盘排版乱了/图表对不齐/重新排布/自动布局/美化仪表盘" → `dashboard arrange`(读 [aitable-dashboard-chart.md](./aitable-dashboard-chart.md))
用户说"附件/上传文件" → 读 [aitable-attachment.md](./aitable-attachment.md)
用户说"导入/导出/import/export" → 读 [aitable-export-import.md](./aitable-export-import.md)
用户说"模板" → `template search`
用户说"高级权限/角色/权限控制/谁能看/谁能改" → 读 [aitable-advperm.md](./aitable-advperm.md)
- 开/关高级权限 → `advperm enable` / `advperm disable --yes`
- 看角色配置 → `advperm role-list` 或 `advperm role-get`
- 建角色(可同时指定子角色权限) → `advperm role-create --name ... --sub-roles '[...]'`
- 改角色名 / 改子角色权限(PATCH 语义,未传字段不变) → `advperm role-update --role-id ... [--name ...] [--sub-roles '[...]']`
- 删角色 → `advperm role-delete --yes`
- **角色 ↔ 成员绑定**:当前 CLI 不支持,仍需在 AI 表格 Web 端面板手动完成
命令报错/操作失败 → 读 [aitable-error-recovery.md](./aitable-error-recovery.md)
**关键区分**: base=表格文件, table=数据表, field=列, record=行
@@ -0,0 +1,8 @@
# 注意事项
## 注意事项
- 所有操作使用 ID(baseId/tableId/fieldId/recordId),不使用名称
- records 的 cells key 是 fieldId,不是字段名称
- cells 写入/读取格式见 [aitable-cell-value.md](./aitable-cell-value.md)
- 最佳实践见 [aitable-best-practices.md](./aitable-best-practices.md)
@@ -17,9 +17,7 @@ dws aitable record primary-doc-get --base-id BASE_ID --table-id TABLE_ID --recor
- `--table-id`(必填):Table ID
- `--record-id`(必填):Record ID
**返回:** `data.nodeId` — 主键文档的 nodeId,可直接传给 `dws doc read/update` 的 `--node` 参数。
> **该记录尚未创建主键文档时**:不会返回 `nodeId: null`,而是 `status=error`、`data={}`,`error={code:"-1", message:"no record", type:"SYSTEM_ERROR", retryable:true}`。要判断"有没有主键文档",看是否命中这个 `no record` 错误,而不是判断 `nodeId` 是否为 null。需要文档时改用 `primary-doc-create`(幂等,已存在则直接返回)。
**返回:** `data.nodeId` — 主键文档的 nodeId,可直接传给 `dws doc read/update` 的 `--node` 参数。若该记录尚未创建主键文档,`nodeId` 为 null。
### 创建主键文档
@@ -27,6 +27,7 @@ Flags:
| cells key 用字段名 | ❌ cells key 必须是 fieldId(如 `fldXXX`),不是字段名称(如 `"课程名称"`) |
| 不先获取 fieldId | ❌ 必须先 `table get` 获取 fieldId,再写入记录 |
| 单次超 100 条 | ❌ 单次最多 100 条,超过需分批 |
| 附件/图片字段直传 URL | ❌ 严禁 `{"url":"https://..."}` — 会触发 TIMEOUT_ERROR。必须先 `attachment upload` 获取 `fileToken`,再用 `{"fileToken":"ft_xxx"}` 写入。详见 [aitable-attachment.md](./aitable-attachment.md) |
## 正确流程
@@ -25,11 +25,12 @@ dws aitable record history-list \
"data": {
"histories": [
{
"type": "row", // 变更类型,实测均为 "row"(行级变更)
"action": "updateRecords", // 操作动作:appendRow(新增行) / updateRecords(更新记录)
"newValue": "{\"fldX\":{\"dataType\":\"STRING\",\"value\":\"改后值\"}}", // 变更后的值(JSON 字符串,按 fieldId 组织)
"oldValue": "{\"fldX\":{\"dataType\":\"STRING\",\"value\":\"改前值\"}}", // 变更前的值(appendRow 新增行时无此字段)
"type": "field_change", // 变更类型
"action": "update", // 操作动作: create / update / delete
"newValue": "{\"...\":\"...\"}", // 变更后的值(JSON 字符串)
"oldValue": "{\"...\":\"...\"}", // 变更前的值(JSON 字符串)
"operateTime": 1733123456789, // 操作时间(毫秒级时间戳)
"typeChangedFields": "{...}", // 类型变更的字段信息(JSON 字符串)
"version": 7 // 版本号(单调递增)
}
]
@@ -37,14 +38,14 @@ dws aitable record history-list \
}
```
`newValue` / `oldValue` 是 JSON 字符串(不是 JSON 对象),需要二次 `JSON.parse` 才能拿到结构化值;解析后是 `{fieldId: {dataType, value}}` 结构。`appendRow`(新增行)事件没有 `oldValue`。实测返回里**没有** `typeChangedFields` 字段。
`newValue` / `oldValue` / `typeChangedFields` 是 JSON 字符串(不是 JSON 对象),需要二次 `JSON.parse` 才能拿到结构化值。
## 字段含义速查
| 字段 | 用途 |
|------|------|
| `type` | 实测均为 `row`(行级变更),服务端未按 `record_create` / `field_change` 细分。 |
| `action` | 底层操作名:`appendRow`(新增行)/ `updateRecords`(更新记录)。按"动作"统计时以这两个值为准。 |
| `type` | 高层分类:`record_create` / `field_change` / `record_delete` 等。先按 type 过滤大类。 |
| `action` | 三态:`create` / `update` / `delete`。比 type 粗,但便于按"动作"统计。 |
| `version` | 单调递增整数;同一 record 越新值越大。**用作"上一条 vs 这一条"的稳定排序键**。 |
| `operateTime` | 毫秒时间戳;可格式化成可读时间。多条同 version 的极端场景用 operateTime 兜底排序。 |
@@ -73,22 +74,20 @@ dws aitable record history-list --base-id BASE --table-id TBL --record-id REC --
```bash
dws aitable record history-list --base-id BASE --table-id TBL --record-id REC --limit 50 --format json \
| jq '[.data.histories[] | select(.action == "updateRecords")][0].oldValue'
| jq '[.data.histories[] | select(.action == "update")][0].oldValue'
```
### 4. 只看更新事件(排除新增行)
### 4. 找出删除事件(如果存在 delete history)
```bash
dws aitable record history-list --base-id BASE --table-id TBL --record-id REC --format json \
| jq '.data.histories[] | select(.action == "updateRecords") | {version, operateTime}'
| jq '.data.histories[] | select(.action == "delete") | {version, operateTime}'
```
> 记录被 `record delete` 删除后,其历史不再返回(`histories` 为空数组),无法通过本命令回溯删除事件。
## 注意事项
- 一次只能查一条 record;如需批量审计多条记录请循环调用。
- 仅返回**新增行**(appendRow)与**字段值变更**(updateRecords);记录被删除后其历史不再可查(返回空)。视图、字段定义、表结构变更不在此 history 里。
- 仅返回**字段值变更**与**记录生命周期事件**;视图、字段定义、表结构变更不在此 history 里。
- 历史保留时长由 server 决定,过老的记录可能不再返回。
## 与其他 record 命令的关系
@@ -0,0 +1,13 @@
# 记录管理
| 命令 | 用途 | 详见 |
|---|---|---|
| `record query` | 查询/搜索记录 | [`aitable-record-query.md`](./aitable-record-query.md) |
| `record get` | 按 ID 取记录 | [`aitable-record-query.md`](./aitable-record-query.md) |
| `record create` | 新增记录 | [`aitable-record-create.md`](./aitable-record-create.md) |
| `record update` | 更新记录 | [`aitable-record-update.md`](./aitable-record-update.md) |
| `record batch-update` | 批量更新 | [`aitable-record-update.md`](./aitable-record-update.md) |
| `record delete` | 删除记录 | [`aitable-record-delete.md`](./aitable-record-delete.md) |
| `record history-list` | 查询单条记录的变更历史 | [`aitable-record-history.md`](./aitable-record-history.md) |
| `record share-url` | 批量获取记录分享链接 | [`aitable-record-share.md`](./aitable-record-share.md) |
| `record upsert` | 批量创建或更新 | [`aitable-record-upsert.md`](./aitable-record-upsert.md) |
@@ -72,3 +72,49 @@ dws aitable record query --base-id X --table-id Y --all --cursor "上次返回
- `--sort` 用 `"order":"desc"` → 必须用 `"direction":"desc"`
- 不加 `--field-ids` 拉全字段 → 大表响应体积过大
- 全量拉取后在 context 里手动统计 → 应优先用 `--filters` 服务端过滤
## record query-empty — 找空行
`record query-empty` 是与 `record query` 平行的独立子命令,专门按表内顺序扫描出"完全没填用户字段"的空行。
```bash
dws aitable record query-empty --base-id BASE_ID --table-id TABLE_ID
```
| flag | 说明 |
|------|------|
| `--base-id` / `--base` | 必填 |
| `--table-id` | 必填 |
| `--limit` | 单次**扫描预算**(不是返回数);范围 [1, 100],默认 100 |
| `--cursor` | 分页游标。响应中 `nextCursor` 非空 → 用它翻页继续扫;nextCursor 为空(或不存在)→ 已扫完整表 |
返回结构:
```jsonc
{ "data": { "records": [...], "nextCursor": "..." } }
```
### 关键语义
1. **`--limit` 是扫描预算不是返回数**:可能扫了 100 条但全部非空,本页 `records: []`。
2. **本页空 records ≠ 全表无空行**:必须看 `nextCursor`,nextCursor 还在就要继续翻。
3. **空行定义**:除系统字段(recordId / 创建人 / 创建时间 / 修改人 / 修改时间)外,所有 cell 都是 null、空字符串、空集合或空 Map。一般是用户在 UI 上"插入空行"产生的。
### 典型用法
```bash
# 扫一页,看本页有没有空行
dws aitable record query-empty --base-id BASE --table-id TBL
# 翻页
dws aitable record query-empty --base-id BASE --table-id TBL --cursor <上次的nextCursor>
# 把整表扫完(手动循环 cursor)
NC=""
while : ; do
R=$(dws aitable record query-empty --base-id BASE --table-id TBL ${NC:+--cursor "$NC"} --format json)
echo "$R" | jq '.data.records[] | .recordId'
NC=$(echo "$R" | jq -r '.data.nextCursor // empty')
[ -z "$NC" ] && break
done
```
@@ -0,0 +1,5 @@
# 相关产品
## 相关产品
- [doc](../../../dingtalk-doc/references/doc.md) — 富文本文档编辑,不是结构化数据表格
@@ -0,0 +1,10 @@
# 自动化脚本
## 自动化脚本
| 脚本 | 场景 |
|------|------|
| [bulk_add_fields.py](../../scripts/bulk_add_fields.py) | 批量添加字段 |
| [import_records.py](../../scripts/import_records.py) | 从 JSON/CSV 批量导入记录 |
| [aitable_export_via_task.py](../../scripts/aitable_export_via_task.py) | 文件导出(export_data 轮询 + 下载) |
| [upload_attachment.py](../../scripts/upload_attachment.py) | 上传附件到 AI 表格记录 |
@@ -0,0 +1,8 @@
# 数据表管理
| 命令 | 用途 | 必读参数 |
|---|---|---|
| `table get` | 获取表结构(字段+视图目录) | `--base-id` |
| `table create` | 创建数据表 | `--base-id` `--name` `--fields` |
| `table update` | 修改表名 / 备注 / 行命名规则 | `--base-id` `--table-id` |
| `table delete` | 删除表 | `--base-id` `--table-id` |
@@ -0,0 +1,17 @@
# 文档地址 (URI)
## 文档地址 (URI)
| 资源 | URI 格式 |
|------|----------|
| Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` |
| 指定数据表 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` |
| 指定数据表+视图 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` |
| 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` |
> **操作后请返回文档 URI**:返回链接时必须带上当前操作的数据表 tableId,让用户点击后直接看到目标数据表,而不是落在空白的默认表。
> - 已知 tableId + viewId 时(view create 返回、view get 中提取):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}`
> - 已知 tableId 时(table create 返回、base get 中提取、record 操作所用的 tableId):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}`
> - 仅有 baseId、无明确 tableId 时(如 base list/search):拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}`
>
> 补充:如果 URL 不是来自 `aitable` 命令返回,而是用户直接贴的原始 `alidocs` URL,先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认是 `able` 后再按 AI 表格处理。
@@ -0,0 +1,11 @@
# URL → baseId 提取
## URL → baseId 提取
用户提供 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 链接时:
1. 提取 `/nodes/` 后的路径段作为 `baseId`
2. 去掉尾部的查询参数(`?` 及其后内容)
3. 传入 `--base-id` 参数
> 如果该 URL 来自 `dws aitable` 返回或已在当前链路 probe 过,可直接复用;
> 如果是用户直接提供的原始 `alidocs` URL,则先按 [链接规范](../url-patterns.md#alidocs-url-类型探测流程) probe,确认 `extension=able` 后再继续。
@@ -79,21 +79,21 @@ dws aitable view update timebar --view-id GANTT_ID --official-holiday=true
### view update aggregate(仅 Grid)
值是 `map[fieldId]→AggregateAction string`。**设置**聚合可用;**清除**聚合当前无效(见下方警告)。
值是 `map[fieldId]→AggregateAction string`;传 null 清除某个字段聚合。
| flag | 类型 | 说明 |
|------|------|------|
| `--field-id` | string | 配合 `--action` 设置**单字段**聚合 |
| `--action` | string | `SUM`/`AVG`/`MAX`/`MIN`/`MEDIAN`/`RANGE`/`TOTAL`/`DISTINCT`/`EXIST`/`UN_EXIST`/`CHECKED`/`EARLIEST_DATE` 等(按字段类型可用) |
| `--clear-field-id` | string (CSV) | 一/多个字段 ID,本意是清除其聚合,但**当前服务端不支持清除,静默无效**(见下方警告) |
| `--clear-field-id` | string (CSV) | 一/多个字段 ID,清除其聚合 |
| `--json` | JSON | 完整 aggregate map |
```bash
dws aitable view update aggregate --view-id GRID_ID --field-id fldX --action SUM
dws aitable view update aggregate --view-id GRID_ID --clear-field-id fldA,fldB
dws aitable view update aggregate --view-id GRID_ID --json '{"fldX":"AVG","fldY":null}'
```
> ⚠️ **当前无法清除已设置的聚合(服务端限制)**:`--clear-field-id fldA,fldB` 与 `--json '{"fldX":null}'` 两种清除写法都返回 `success`,但用 `view get aggregate` 复核会发现聚合**原样不动**——是静默无效,不是真的清掉。这是服务端没有清除语义所致,直至服务端修复前不要依赖它。改聚合方式可行(重新 `--action` 覆盖成别的),只是无法回到"无聚合"。
### view update field-widths(仅 Grid)
| flag | 类型 |
@@ -108,11 +108,9 @@ dws aitable view update field-widths --view-id GRID_ID --json '{"fldA":120,"fldB
### view update visible-fields(通用)
整组替换可见字段列表与顺序,同时兼作**隐藏/显示**入口。首列字段(primaryDoc)必须保留在数组第一位。
整组替换可见字段列表与顺序。首列字段(primaryDoc)必须保留在数组第一位。
> **传入的列表既定顺序又定可见性**:传一个比当前 columns 短的列表,缺失的字段会被真正隐藏(该字段在 `view list` 的 `custom.hiddenFields` 里变 `true`);再传回全量列表即可解除隐藏(`hiddenFields` 变 `false`)。
>
> ⚠️ **查隐藏状态别看这里**:`view get visible-fields` 返回的数组**包含已隐藏字段**(列的完整顺序),看不出谁被隐藏。要确认隐藏状态,读 `view get`(view list)里该视图的 `custom.hiddenFields`(`{fieldId: true|false}`)。
> ⚠️ 注意:服务端**只接受 reorder,不接受真"隐藏字段"**——如果传入的列表比当前 columns 短,缺失的字段不会被隐藏。需要真正隐藏字段请到 AI 表格 Web UI。
| flag | 类型 |
|------|------|
@@ -120,9 +118,7 @@ dws aitable view update field-widths --view-id GRID_ID --json '{"fldA":120,"fldB
| `--json` | string 数组 JSON(与 `--field-ids` 同传时 `--json` 优先) |
```bash
# 只保留首列和 fldA,其余字段被隐藏
dws aitable view update visible-fields --view-id VIEW_ID --field-ids fldPrimary,fldA
# 传回全量列表解除隐藏
dws aitable view update visible-fields --view-id VIEW_ID --field-ids fldPrimary,fldA,fldB
dws aitable view update visible-fields --view-id VIEW_ID --json '["fldPrimary","fldA","fldB"]'
```
@@ -54,7 +54,7 @@ dws aitable view update frozen-cols --view-id VIEW_ID --count 0
# 查询当前冻结列数
dws aitable view get frozen-cols --view-id VIEW_ID --format json
# → {"data": {..., "count": 1}} 未显式设置时整个 count 键缺失(不是返回 null)
# → {"data": {..., "count": 1}} count 为 null 表示视图未显式设置
```
`--count` 必须 ≥ 0;负数会被拒绝。
@@ -69,7 +69,7 @@ dws aitable view update row-height --view-id VIEW_ID --cell-height 56
# 查询当前行高
dws aitable view get row-height --view-id VIEW_ID --format json
# → {"data": {..., "cellHeight": 56}} 未显式设置时整个 cellHeight 键缺失(不是返回 null;前端按 32 渲染)
# → {"data": {..., "cellHeight": 56}} cellHeight 为 null 表示视图未显式设置(前端按 32 渲染)
```
## 数据高亮规则(条件填色,仅 Grid)
@@ -0,0 +1,6 @@
# 视图管理
| 命令 | 用途 | 详见 |
|---|---|---|
| `view get/list/create/update/delete/duplicate/lock` | 视图本体与基本属性 | [`aitable-view-config.md`](./aitable-view-config.md) |
| `view update <attr>` | 按属性局部更新 | [`aitable-view-config.md`](./aitable-view-config.md) / [`aitable-view-extras.md`](./aitable-view-extras.md) |
@@ -0,0 +1,5 @@
# 自动化工作流
| 命令 | 用途 | 详见 |
|---|---|---|
| `workflow list/get/enable/disable` | 工作流管理 | [`aitable-workflow.md`](./aitable-workflow.md) |
@@ -1,180 +1,21 @@
# workflow — 自动化工作流管理
# 核心工作流
启停 / 查看 / 列出 Base 下的自动化工作流(在 AI 表格 Web 端配置的 "当 X 时自动 Y" 流程)。
适用场景:用户问 "停掉这个流程"、"看下都有哪些自动化流程"、"流程 X 的配置是什么"。
## 命令一览
| 命令 | 用途 |
|------|------|
| `workflow list` | 列出 Base 下所有工作流(含状态/创建人/最后修改时间),支持分页 |
| `workflow get` | 获取单个工作流详情(含 flowSchema 完整节点定义) |
| `workflow enable` | 启用指定工作流(按配置的触发条件自动执行) |
| `workflow disable` | 禁用指定工作流(高危,建议 `--yes` 二次确认) |
> 所有子命令的 `--base-id` 必填(可用隐藏别名 `--base`)。
> 当前**不支持通过 CLI 新建工作流**,请在 AI 表格 Web 端配置好后用 `workflow list` 拿到 ID 再启停。
## 命令详情
### workflow list — 列出工作流
## 核心工作流
```bash
dws aitable workflow list --base-id BASE_ID --format json
dws aitable workflow list --base-id BASE_ID --limit 50 --offset 100
# 1. 搜索/列出 Base — 提取 baseId
dws aitable base search --query "项目" --format json
# 2. 获取 Base 信息 — 提取 tableId
dws aitable base get --base-id <BASE_ID> --format json
# 3. 获取表结构 — 提取 fieldId
dws aitable table get --base-id <BASE_ID> --table-id <TABLE_ID> --format json
# 4. 查询记录
dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --format json
# 5. 新增记录 (cells 用 fieldId 作 key)
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
--records '[{"cells":{"fldXXX":"值"}}]' --format json
```
| flag | 说明 |
|------|------|
| `--base-id` | 必填 |
| `--limit` | 可选,分页大小 `[1, 100]`,不传走服务端默认 20 |
| `--offset` | 可选,分页偏移量 `>= 0`,不传走服务端默认 0 |
返回结构:
```json
{
"data": {
"list": [
{
"flowId": "G-FLOW-XXXXXX", // ★ 注意字段名是 flowId
"name": "流程1",
"description": "当创建记录时,就更新记录",
"status": "RUNNING", // RUNNING / STOP
"creatorStaffId": "281493",
"lastModifier": { "name": "李普阳", "staffId": "281493" },
"gmtModified": 1780318540000,
"versionId": "G-FLOW-VER-XXXXXX",
"icons": ["..."], // 触发器+动作的图标
"isSubFlow": false,
"opPermissions": { "canEdit": true }
}
],
"recordCount": 1, // Base 下总数
"runningCount": 1 // RUNNING 状态的数量
}
}
```
**注意**:
- 标识字段服务端在 `list` 里叫 **`flowId`**,但在 `enable` / `disable` 出参里叫 **`workflowId`**。CLI `--workflow-id` 传任一即可(同值)。
- `status` 是字符串枚举:`RUNNING`(启用中)/ `STOP`(已禁用),**不是** boolean。
- `runningCount` 是当前 Base 下 status=RUNNING 的工作流数,方便快速判断「有几个流程在跑」。
### workflow get — 获取单个工作流详情
```bash
dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json
```
| flag | 说明 |
|------|------|
| `--base-id` | 必填 |
| `--workflow-id` | 必填,对应 list 出参里的 `flowId` |
返回完整工作流配置:
```json
{
"data": {
"name": "流程1",
"namespace": "...",
"status": "RUNNING",
"versionId": "G-FLOW-VER-XXXXXX",
"versionNo": 14,
"versionStatus": "...",
"accessor": {...}, // 访问者信息
"corpId": "...",
"flowAttribute": {...}, // 流程顶层属性
"flowSchema": {...}, // ★ 流程节点定义(触发器/动作/分支等)
"gmtCreate": 1780317804000,
"gmtModified": 1780318540000
}
}
```
`flowSchema` 是完整的节点 DAG,结构因流程而异(条件触发器 vs 定时触发器、单分支 vs 多分支等)。agent 应按需读取关心字段,不要试图建静态 schema。
### workflow enable — 启用工作流
```bash
dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json
```
返回 `{workflowId, enabled: true}` —— **`enabled: true` 是动作确认,不是当前状态查询**。要确认真启用了,必须再 `workflow list` 看 `status` 是否变成 `"RUNNING"` 或 `runningCount` 是否加 1。
### workflow disable — 禁用工作流(高危)
```bash
dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes --format json
```
返回 `{workflowId, disabled: true}` —— 同样是动作确认。禁用后该工作流不再自动触发。
**风险**:直接影响业务自动化(如停掉「记录创建后自动发通知」会让通知断流)。建议:
- 操作前先 `workflow get` 留底当前配置
- 脚本场景显式传 `--yes`;交互场景让用户在 prompt 中再次确认
## 能力边界
| 能力 | 状态 |
|------|------|
| 列出工作流 | ✅ |
| 看工作流详情(含 flowSchema) | ✅ |
| 启用/禁用 | ✅ |
| 新建工作流 | ❌ 当前不支持,请去 AI 表格 Web 端 → 数据表 → 自动化 创建 |
| 修改工作流配置 | ❌ 同上,需 Web UI 编辑 |
| 删除工作流 | ❌ 同上 |
| 查看运行历史/执行日志 | ❌ 暂未开放 |
| 手动触发/单次运行 | ❌ 暂未开放 |
## 错误码速查
| 场景 | code | type | 备注 |
|------|------|------|------|
| `workflow-id` 不存在调 get | `GET_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 可能为 null,先 `workflow list` 核对 ID |
| `workflow-id` 不存在调 enable | `ENABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | message 含 "场域中不存在该 namespace" |
| `workflow-id` 不存在调 disable | `DISABLE_WORKFLOW_ERROR` | `SYSTEM_ERROR` | 同上 |
| `--limit` < 1 或 > 100 | (CLI 层拦截) | — | `--limit 必须在 [1, 100] 范围内,got N` |
| `--offset` < 0 | (CLI 层拦截) | — | `--offset 必须 >= 0,got N` |
> 拿到 `*_WORKFLOW_ERROR / SYSTEM_ERROR` 时,先 `workflow list` 自查目标 ID 是否还存在、是否在当前 Base 下。
## 典型工作流
### 看看 Base 里有哪些自动化在跑
```bash
dws aitable workflow list --base-id BASE_ID --format json | jq '.data | {total: .recordCount, running: .runningCount, items: .list | map({name, status, flowId})}'
```
### 临时停掉某个流程做调试
```bash
# 1. 留底当前状态
dws aitable workflow get --base-id BASE_ID --workflow-id WORKFLOW_ID --format json > /tmp/wf-backup.json
# 2. 禁用
dws aitable workflow disable --base-id BASE_ID --workflow-id WORKFLOW_ID --yes --format json
# 3. 调试做完后重启
dws aitable workflow enable --base-id BASE_ID --workflow-id WORKFLOW_ID --format json
# 4. 确认 status=RUNNING
dws aitable workflow list --base-id BASE_ID --format json | jq '.data.list[] | select(.flowId == "WORKFLOW_ID") | .status'
```
### 批量关掉某个 Base 下所有 workflow(调试 / 迁移前清场)
```bash
for WF in $(dws aitable workflow list --base-id BASE_ID --limit 100 --format json | jq -r '.data.list[] | select(.status == "RUNNING") | .flowId'); do
dws aitable workflow disable --base-id BASE_ID --workflow-id "$WF" --yes --format json | jq .status
done
```
## 注意事项
- `--workflow-id` 接受的就是 `list` 返回里的 `flowId`(同值,CLI 屏蔽了服务端字段名差异)。
- enable / disable 出参里的 `enabled` / `disabled` 是 **动作确认 flag**,不是当前状态字段。要确认真生效请走 `workflow list` 查 `status`。
- `workflow get` 的 `flowSchema` 结构随触发器/动作类型变化,不要假设固定字段。
- 新建/修改/删除工作流目前必须在 AI 表格 Web 端(数据表页面 → 自动化)完成。
@@ -83,7 +83,7 @@ dws aitable table create --base-id <BASE_ID> --name "产品图片" \
## 记录写入格式(record create / record update)
> 各字段类型的完整写入/读取格式规范请参考:[aitable-cell-value.md](./products/aitable/aitable-cell-value.md)
> 各字段类型的完整写入/读取格式规范请参考:[aitable-cell-value.md](./aitable/aitable-cell-value.md)
>
> 该文件是 cellValue 格式的 **source of truth**,包含所有字段类型的详细示例和注意事项。
@@ -0,0 +1,10 @@
# aitable 局部意图消歧
本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|---|---|---|---|---|
| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `doc` / `sheet` | 涉及结构化数据/行列操作,不是富文本文档或电子表格 |
| "帮我写个项目周报" | 创建钉钉文档 | `doc` | `aitable` | 富文本内容创作,不是数据表 |
| "创建一个电子表格" | 创建表格文档 | `sheet` | `aitable` | Excel 式表格/单元格操作,不是多维表记录 |
| "帮我读一下表格 A1:D10 的数据" | 读取单元格数据 | `sheet` | `aitable` | 按单元格区域读写,不是按记录查询 |
@@ -0,0 +1,129 @@
# URL 格式与处理规范
## alidocs URL 分流决策(必须首先执行)
收到 `alidocs.dingtalk.com` URL 时,**必须按以下顺序判断,禁止跳过**:
1. URL 路径含 `/i/p/` → **分享短链**,禁止调用 `dws doc` 任何子命令 → 按下方 [分享短链处理](#分享短链处理) 执行
2. URL 路径含 `/i/nodes/` → **节点链接**,需探测类型 → 按下方 [alidocs URL 类型探测流程](#alidocs-url-类型探测流程) 执行
3. URL 路径含 `/spreadsheetv2/` → **电子表格直链**,直接路由到 `sheet`,将完整 URL 原样传给 `--node` 参数
4. URL 路径含 `/document/edit` 或 `/document/preview` 且 query 参数包含 `dentryKey` → **文档链接**,直接路由到 `doc`,将完整 URL 原样传给 `--node` 参数(URL 中不一定有 `type=d`,只需匹配路径和 `dentryKey` 参数即可)
5. 其他 alidocs URL 格式 → 告知用户当前暂不支持该链接格式
---
## 已知 URL 格式
需要自行拼接链接时,只能使用以下模板:
| 产品 | 用途 | URL 格式 | ID 来源 |
|------|------|----------|---------|
| `aitable` | AI表格 Base 链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` | `base list/search/create/get` 返回的 `baseId` |
| `aitable` | AI表格指定数据表链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}` | `baseId` + `table create/get` 或 `base get` 返回的 `tableId` |
| `aitable` | AI表格指定数据表+视图链接 | `https://alidocs.dingtalk.com/i/nodes/{baseId}?iframeQuery=sheetId%3D{tableId}%26viewId%3D{viewId}` | `baseId` + `tableId` + `view create/get` 返回的 `viewId` |
| `aitable` | AI表格模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` | `template search` 返回的 `templateId` |
| `doc` | 文档链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `doc` 命令返回的 `dentryUuid` |
| `sheet` | 电子表格链接 | `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}` | `sheet create` 返回的 `dentryUuid` |
| `sheet` | 电子表格直链 | `https://alidocs.dingtalk.com/spreadsheetv2/{key}/...?dentryKey={key}&type=s` | 用户提供的完整 URL,直接传给 `--node` |
| `doc` | 文档链接(edit/preview) | `https://alidocs.dingtalk.com/document/{edit\|preview}?...&dentryKey={key}` | 用户提供的完整 URL,直接传给 `--node` |
| `minutes` | 听记链接 | `https://shanji.dingtalk.com/app/transcribes/{taskUuid}` | `list mine/shared` 返回的 `taskUuid` |
不在此表中的产品,禁止自行拼接 URL。命令返回中包含完整链接时直接使用,否则告知用户无法提供。
## 分享短链处理
`alidocs.dingtalk.com/i/p/{shortKey}` 是钉钉文档的**对外分享短链**,`dws doc` 命令无法解析此格式。
### 识别规则
URL 路径中包含 `/i/p/` 即为分享短链(无论后面是否还有子路径),例如:
- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2`
- `https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7`
- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234`
- `https://alidocs.dingtalk.com/i/p/AbCdEfGh1234/sheets/XYZ789`
> **关键**:只要 URL 中出现 `/i/p/`,无论后面跟什么子路径(`/docs/...`、`/sheets/...` 等),都属于分享短链,一律禁止调用 `dws doc`。
### 处理方式
**不要调用 `dws doc` 任何子命令**(包括 `doc info`、`doc read` 等),`dws` 无法解析此格式。
- **需要获取文档内容时**:使用 `read_url` 工具直接读取该链接
- **其他操作(如移动、复制、权限管理等)**:告知用户此链接为分享短链,无法直接执行复制、移动、权限管理等操作。如需保存该文档内容,建议用户在钉钉客户端中打开该页面,手动复制文本内容,然后可通过 `dws doc create` 创建一篇新文档并将内容写入
```
# 需要读取文档内容时(无论 /i/p/ 后面有没有子路径,都用 read_url)
read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2")
read_url("https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7")
# 禁止(以下全部会失败,dws 无法解析任何含 /i/p/ 的 URL)
dws doc info --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2" --format json
dws doc read --node "https://alidocs.dingtalk.com/i/p/Y7kmbokZp3pgGLq2/docs/AY39rGpMPmeVNpXZevZm8OZkXKnaoNQ7" --format json
```
### 当 `read_url` 返回内容不完整时
钉钉文档分享页是动态渲染的,`read_url` 可能只能获取到页面标题等有限信息,无法获取文档正文。此时**禁止猜测原因**(如"权限不足""文档为空""文档已删除"等),**禁止建议用户"提供 `/i/nodes/` 格式链接"**(分享短链和节点链接是不同体系,普通用户无法自行转换)。应直接告知用户:
> 这个链接是钉钉文档的分享短链,由于页面是动态渲染的,我无法通过该链接直接获取文档的完整正文内容。
>
> 你可以:
> 1. 在钉钉客户端中打开该文档,将正文内容复制粘贴给我
> 2. 如果文档已保存在你的文档空间中,可以告诉我文档名称,我通过 `dws drive search` 搜索后再读取
---
## alidocs URL 类型探测流程
`alidocs.dingtalk.com/i/nodes/{id}` 是钉钉文档空间的统一 URL,可能指向**文档、电子表格、多维表、文件、文件夹**等不同类型。**禁止仅凭 URL 就假定为文档**,必须先探测类型再路由到正确的产品。
### 探测步骤
```
Step 1 → dws doc info --node "<URL>" --format json
Step 2 → 从返回中提取 contentType、extension、nodeType 字段
Step 3 → 按下方路由规则映射到对应产品
```
### 路由映射表
| 条件 | 路由到产品 | 后续操作 |
|------|-----------|---------|
| `contentType=ALIDOC`, `extension=adoc` | `doc` | 加载 `dingtalk-doc` 操作内容 |
| `contentType=ALIDOC`, `extension=axls` | `sheet` | 加载 `dingtalk-sheet` 操作(仅 `axls` 在线电子表格) |
| `contentType=ALIDOC`, `extension=able` | `aitable` | 将 nodeId 作为 baseId,加载 `dingtalk-aitable` 操作 |
| `contentType=DOCUMENT`, `extension=xlsx` / `xls` / `xlsm` / `csv` | `drive` | 必须用 `dws drive download` 下载到本地处理,禁止走 `sheet`(非在线表格,sheet 命令无法操作) |
| `contentType≠ALIDOC`, `nodeType=file` | `drive` | 调用 `dws drive download` 下载,返回文件下载链接 |
| `nodeType=folder` | `drive` / `wiki` | 调用 `dws drive list --workspace <WS_ID>` 或 `dws wiki node list` 列出子节点 |
| 以上均不匹配 | — | 告知用户当前暂不支持该类型 |
> axls vs xlsx 关键区分:
> - `axls`(钉钉在线电子表格,`contentType=ALIDOC`)→ 走 `sheet` 产品线(读/写/筛选/导出等服务端原子操作)
> - `xlsx` / `xls` / `xlsm` / `csv`(上传到文档空间的本地表格文件,`contentType=DOCUMENT`)→ 必须走 `dws drive download` 下载到本地后再解析处理,严禁错误路由到 `sheet` 产品线(sheet 命令只支持在线表格,调用 xlsx 节点会直接报错)
> - 用户想把在线表格导出为 xlsx 文件 → 用 `dws sheet export`(输入是 `axls`,输出是 xlsx,这是 axls → xlsx 的格式转换,不属于 xlsx 读取场景)
### 示例
```bash
# 用户传入: https://alidocs.dingtalk.com/i/nodes/abc123
dws doc info --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json
# 返回 contentType=ALIDOC, extension=axls → 在线电子表格,路由到 sheet
dws sheet list --node "https://alidocs.dingtalk.com/i/nodes/abc123" --format json
# 返回 contentType≠ALIDOC, extension=xlsx/xls/csv → 本地表格文件,必须下载处理(禁止走 sheet)
dws drive download --node "https://alidocs.dingtalk.com/i/nodes/xlsx456"
# 返回 contentType≠ALIDOC, nodeType=file → 普通文件,下载
dws drive download --node "https://alidocs.dingtalk.com/i/nodes/def456"
# 返回 nodeType=folder → 文件夹,列出子节点
dws drive list --workspace <WS_ID> --format json
```
### 何时可跳过探测
当用户指令中已明确指定产品(如"帮我读这个文档"、"看下这个表格的数据"),可结合用户意图**跳过探测**直接路由。仅在以下情况**必须执行探测**:
- 用户只粘贴 URL,无其他上下文
- 用户指令与 URL 实际类型可能不一致(如说"文档"但实际是表格)
- 用户直接粘贴的是原始 `alidocs` URL,且没有上游命令返回来确认类型
@@ -90,10 +90,8 @@ def build_start_args(args: argparse.Namespace) -> list[str]:
args.scope,
"--format",
args.export_format,
# CLI 的 --timeout-ms 是单次等待上限(毫秒,最大 30000);脚本自身的
# --timeout-sec 用于整体轮询/子进程超时,二者语义不同,不能混用。
"--timeout-ms",
"30000",
str(args.timeout_ms),
]
if args.table_id:
cmd.extend(["--table-id", args.table_id])
@@ -109,8 +107,9 @@ def main() -> None:
parser.add_argument("--table-id", help="scope=table/view 时必填")
parser.add_argument("--view-id", help="scope=view 时必填")
parser.add_argument("--export-format", default="excel", choices=sorted(ALLOWED_FORMATS), help="导出格式")
parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)")
parser.add_argument("--max-polls", type=int, default=10, help="(兼容旧参数,开源 CLI 内置轮询不再使用)")
parser.add_argument("--timeout-ms", type=int, default=1000, help="单次等待毫秒数,默认 1000")
parser.add_argument("--poll-timeout-ms", type=int, default=3000, help="轮询等待毫秒数,默认 3000")
parser.add_argument("--max-polls", type=int, default=10, help="最大轮询次数,默认 10")
parser.add_argument("--output", help="本地保存路径(不传则按 fileName 保存到当前目录)")
parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws")
parser.add_argument("--no-download", action="store_true", help="仅返回 downloadUrl,不下载文件")
@@ -155,9 +154,9 @@ def main() -> None:
"--task-id",
task_id,
"--timeout-ms",
"30000",
str(args.poll_timeout_ms),
],
timeout_sec=max(120, args.timeout_sec + 60),
timeout_sec=max(120, int(args.poll_timeout_ms / 1000) + 60),
)
if rc2 != 0:
fail(f"export_data 轮询失败: {err2 or out2}", rc2)
@@ -77,7 +77,7 @@ def main() -> None:
parser = argparse.ArgumentParser(description="通过文件导入任务导入 AI 表格")
parser.add_argument("base_id", help="目标 AI 表格 baseId")
parser.add_argument("file_path", help="待导入文件路径(.csv/.xlsx/.xls)")
parser.add_argument("--timeout-sec", type=int, default=300, help="CLI 内置轮询整体超时(秒),默认 300(5 分钟)")
parser.add_argument("--timeout", type=int, default=30, help="import_data 等待秒数,默认 30")
parser.add_argument("--dws", default="dws", help="dws 可执行文件路径,默认 dws")
args = parser.parse_args()
@@ -140,14 +140,12 @@ def main() -> None:
"data",
"--import-id",
import_id,
# import data 的 --timeout 单位是秒、最大 30;脚本的 --timeout-sec
# 是整体子进程预算,不能直接透传,这里用 CLI 允许的最大值。
"--timeout",
"30",
str(args.timeout),
"--format",
"json",
],
timeout_sec=max(120, args.timeout_sec + 30),
timeout_sec=max(120, args.timeout + 30),
)
if rc2 != 0:
fail(f"import_data 调用失败: {err2 or out2}", rc2)
@@ -29,14 +29,14 @@ JsonData = Union[List[Any], Dict[str, Any]]
MAX_FILE_SIZE = 10 * 1024 * 1024
ALLOWED_FILE_EXTENSIONS = ['.json']
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{6,128}$')
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$')
ALLOWED_FIELD_TYPES = {
'text', 'number', 'singleSelect', 'multipleSelect', 'date', 'currency',
'user', 'department', 'group', 'progress', 'rating', 'checkbox',
'attachment', 'url', 'richText', 'telephone', 'email', 'idCard',
'barcode', 'geolocation', 'primaryDoc', 'formula', 'unidirectionalLink',
'bidirectionalLink', 'creator', 'lastModifier', 'createdTime',
'lastModifiedTime',
'barcode', 'geolocation', 'address', 'primaryDoc', 'formula',
'unidirectionalLink', 'bidirectionalLink', 'lookup', 'filterUp',
'creator', 'lastModifier', 'createdTime', 'lastModifiedTime',
}
FIELD_TYPE_ALIASES = {
'phone': 'telephone',
@@ -121,9 +121,32 @@ def validate_field_config(field: Dict[str, Any]) -> Tuple[bool, str]:
)
if field_type in {'unidirectionalLink', 'bidirectionalLink'}:
linked_sheet_id = (config or {}).get('linkedSheetId')
if not linked_sheet_id or not validate_resource_id(linked_sheet_id):
return False, '关联字段必须提供合法的 config.linkedSheetId'
linked_table_id = (config or {}).get('linkedTableId')
if not linked_table_id or not validate_resource_id(linked_table_id):
return False, (
'关联字段必须提供合法的 config.linkedTableId(目标 Table ID)'
)
if field_type == 'lookup':
cfg = config or {}
if not cfg.get('associateField'):
return False, 'lookup 必须提供 config.associateField(本表关联字段的 fieldId)'
if not cfg.get('valuesField'):
return False, 'lookup 必须提供 config.valuesField(关联目标表中要取值的字段 fieldId)'
if not cfg.get('aggregator'):
return False, 'lookup 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)'
if field_type == 'filterUp':
cfg = config or {}
if not cfg.get('targetSheet'):
return False, 'filterUp 必须提供 config.targetSheet(目标 Table ID)'
filters = cfg.get('filters')
if not filters or not isinstance(filters, list):
return False, 'filterUp 必须提供 config.filters(至少一条筛选规则)'
if not cfg.get('valuesField'):
return False, 'filterUp 必须提供 config.valuesField(目标表中要取值的字段 fieldId)'
if not cfg.get('aggregator'):
return False, 'filterUp 必须提供 config.aggregator(SUM/AVERAGE/COUNT/MAX/MIN/CONCATENATE)'
return True, ''
@@ -11,6 +11,13 @@
- JSON 支持两种格式:
1. [{"cells": {"fldxxx": "value"}}, ...]
2. [{"fldxxx": "value"}, ...] # 会自动包装成 cells
⚠️ CSV 自动类型转换风险:
CSV 读入的所有 cell 都是 string,本脚本会尝试自动识别 'true'/'false'/数字
并转成对应类型(避免 text 字段塞入纯文本数字)。但当 fieldId 对应的字段是
text / telephone / idCard / barcode 这类"字符串形数字"字段时,自动转 int / float
会让 server 拒绝(字段类型不匹配)。这种情况建议改用 JSON 格式(自己显式控制类型),
或在 CSV 写入前给字段值前缀加引号 / 改为非纯数字。
"""
import sys
@@ -28,7 +35,7 @@ RecordDict = Dict[str, str]
MAX_FILE_SIZE = 50 * 1024 * 1024
ALLOWED_CSV_EXTENSIONS = ['.csv']
ALLOWED_JSON_EXTENSIONS = ['.json']
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{6,128}$')
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$')
MAX_RECORDS_PER_BATCH = 100
DEFAULT_BATCH_SIZE = 50
@@ -131,9 +138,7 @@ def normalize_record(record: Dict[str, Any]) -> Dict[str, Any]:
return {'cells': normalized}
def validate_record(
record: Dict[str, Any], headers: List[str]
) -> Tuple[bool, str]:
def validate_record(record: Dict[str, Any]) -> Tuple[bool, str]:
if not isinstance(record, dict):
return False, '记录必须是对象'
normalized = normalize_record(record)
@@ -238,7 +243,7 @@ def import_from_json(
return False
for i, record in enumerate(records):
valid, error = validate_record(record, [])
valid, error = validate_record(record)
if not valid:
print(f"错误:记录 #{i+1} 格式无效:{error}")
return False
+21 -19
View File
@@ -1,6 +1,6 @@
---
name: dingtalk-attendance
description: 钉钉考勤。Use when 用户说 考勤/打卡记录/查打卡/查班次/考勤汇总/考勤规则/出勤情况/请假加班补卡/排班/考勤组/假期余额。命令前缀:dws attendance。支持查询与写操作(班次、排班、考勤组、个人规则设置、假期规则/余额等),写操作需二次确认后再加 --yes。
description: 钉钉考勤、打卡、排班与考勤报表。Use when 用户说考勤/打卡记录/查打卡/排班/班次/考勤报表/导出考勤/出勤汇总/考勤明细/迟到早退统计/全员考勤数据/某月考勤统计/考勤表格/考勤Excel。不做日程会议(走 dingtalk-calendar)、日报周报(走 dingtalk-report)、审批请假流程(走 dingtalk-oa)。命令前缀:dws attendance。
cli_version: ">=0.2.14"
metadata:
category: product
@@ -14,38 +14,40 @@ metadata:
> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。
> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH.
## 前置条件 — 执行操作前必读
> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 渐进式参考:[attendance-index.md](references/attendance-index.md)。日期范围或“签到”场景必须先读 [attendance-routing.md](references/attendance-routing.md);报表导出再读 [attendance-report.md](references/attendance-report.md)。
> 命令参考:[attendance.md](references/attendance.md)。
> 旧路径兼容入口:[attendance.md](references/attendance.md)。
## 意图表
| 用户说 | 命令 |
|--------|------|
| "查我 / 某人 某天的打卡" | `dws attendance record get --user <userId> --date <YYYY-MM-DD>` |
| "查考勤组 / 考勤规则 / 打卡范围" | `dws attendance rules --date <YYYY-MM-DD>` |
| "查班次 / 排班 / 谁今天上什么班" | `dws attendance shift list --users <userId1,userId2> --start <YYYY-MM-DD> --end <YYYY-MM-DD>` |
| "考勤统计 / 周月汇总 / 出勤天数" | `dws attendance summary --user <userId> --date <YYYY-MM-DD> --stats-type week\|month` |
| "请假 / 加班 / 补卡 / 出差 记录或提交链接" | `dws attendance approve list` / `dws attendance approve templates --type <类型>` |
| "班次 / 补卡规则 / 加班规则 / 考勤组 查询与修改" | `class` / `adjustment` / `overtime` / `group` 子命令 |
| "排班 / 假期规则 / 假期余额 / 签到 / 报表" | `schedule` / `vacation` / `checkin` / `report` 子命令 |
| "查我自己的打卡 / 某天考勤" | `python scripts/attendance_my_record.py 2026-03-08` 或 `dws attendance record get --user <userId> --date <YYYY-MM-DD>` |
| "查团队排班" | `python scripts/attendance_team_shift.py --users <userId1,userId2> --from <YYYY-MM-DD> --to <YYYY-MM-DD>` |
| "导出考勤报表 / 月度汇总 / 考勤明细 / 每日统计" | **必须先读 [attendance-report.md](references/attendance-report.md)** 强制门禁后选择 `attendance_report_{detail,monthly,daily}.py` |
| "创建班次 / 设置班次" | 先读 [attendance.md](references/attendance-commands.md) 的 `class create`,确认后执行 |
| "导入排班 / 安排排班" | 先读 [attendance.md](references/attendance-commands.md) 的 `schedule import`,确认后执行 |
| "加入/移出考勤组 / 更新考勤组成员" | `dws attendance group update-members ...`(需确认) |
> 完整命令集(含写操作与参数)见 [references/attendance.md](references/attendance.md)。
## 高频硬约束
## 评测高频硬约束
- 当前 dws 已注册全部考勤子命令组(`record` / `check` / `approve` / `shift` / `schedule` / `class` / `adjustment` / `overtime` / `group` / `summary` / `rules` / `selfsetting` / `globalsetting` / `vacation` / `checkin` / `report` / `boss-check`),查询与写操作大多可直接调用后端。**不要再以"开源版只读/不支持写操作"为由拒答。** 创建班次、导入排班、加人入考勤组、保存个人规则设置、设置假期余额等写操作可执行,但必须先展示参数摘要并二次确认,再追加 `--yes`(或 `--user-say-yes`)执行;不要在未确认时直接写、也不要伪装成功。个别命令受权限/数据影响返回空或权限错误(如 `report` 系列仅管理员),如实说明即可。
- 不要在读完 [attendance.md](references/attendance-commands.md) 前判断"CLI 不支持"。`class create`、`schedule import`、`group update-members`、`group update` 都是已支持写操作,但必须先展示摘要并等用户确认。
- 查询迟到/缺勤名单时,空打卡结果不等于"没人迟到"。必须结合排班、`NotSigned`、`Absenteeism`、无记录人员分别说明;数据缺失要标为"无记录/无法判断",不要归为正常。
- 做部门 Top N 排名时,用户要求前 N 名就必须输出 N 个部门;无打卡记录或无可计算数据的部门按 0 或"无数据"保留在排名中,不能只输出有数据的少数部门。
- `summary` 必须同时传 `--user`、`--date`、`--stats-type`(week/month),缺一返回 C0002。
- `shift list` 时间跨度不超过 7 天,最多 50 人;`record get` 一次只查一个用户一天。
- 所有 dws 命令带 `--format json`,时间/日期按命令要求分别使用 `YYYY-MM-DD` 或 `yyyy-MM-dd HH:mm:ss`。
- 处理请假/补卡/加班审批时,先用考勤审批模板或 OA 查询确认可操作范围;没有直接提交接口时返回可点击提交链接并说明无法代填提交,不要假装已提交。
- 更新考勤组成员固定按 `aisearch/contact` 解析 userId → `group search` 解析 groupId → 展示变更摘要并确认 → `group update-members` → `group filtered-get --member` 回查。
- 所有 dws 命令带 `--format json`,时间/日期按命令要求分别使用 `YYYY-MM-DD` 或 reference 指定格式。
## 跨产品协作
- 拿到 userId 前先用 `dingtalk-aisearch` 解析人名
- 报表导出涉及多人 / 多月 → 脚本内部自动分批 + 切片,输出 xlsx
## 局部意图与 Recipe
- [局部意图消歧](references/intent-guide.md)。
@@ -0,0 +1,998 @@
# 命令总览
## 命令总览
### 查询打卡结果
```
Usage:
dws attendance check result [flags]
Example:
dws attendance check result --users userId1,userId2 --start 2026-04-01 --end 2026-04-30 --limit 50
Flags:
--start string 起始日期, 格式 YYYY-MM-DD (必填)
--end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填)
--limit int 分页大小, 默认 100, 范围 1-1000 (可选)
--offset int 分页偏移量, 默认 0 (可选)
--users string 用户 ID 列表, 逗号分隔, 最多 100 个 (必填)
```
返回每条记录含:用户 ID、工作日期、时间结果(Normal/Late/Early/Absenteeism/NotSigned)、位置结果、计划打卡时间、实际打卡时间、打卡流水 ID。时间跨度不超过 1 个月,最多 100 人。
### 查询打卡流水
```
Usage:
dws attendance check record [flags]
Example:
dws attendance check record --users userId1 --start 2026-04-01 --end 2026-04-30
Flags:
--start string 起始日期, 格式 YYYY-MM-DD (必填)
--end string 结束日期, 格式 YYYY-MM-DD, 不超过 1 个月 (必填)
--users string 用户 ID 列表, 逗号分隔 (必填)
```
返回每条记录含:用户 ID、实际打卡时间、打卡地址、打卡经纬度、打卡类型(OnDuty/OffDuty)、定位方式(Map/Wifi/etc)。时间跨度不超过 1 个月。
### 查询审批单(补卡/加班/请假/出差外出)
```
Usage:
dws attendance approve list [flags]
Example:
dws attendance approve list --users userId1 --types overtime,leave --start 2026-04-01 --end 2026-04-30
dws attendance approve list --users userId1 --types trip --start 2026-04-01 --end 2026-04-30 # 同时返回出差与外出
dws attendance approve list --users userId1 --types 加班,请假,补卡 --start 2026-04-01 --end 2026-04-30
Flags:
--start string 起始日期, 格式 YYYY-MM-DD (必填)
--end string 结束日期, 格式 YYYY-MM-DD (必填)
--types string 审批类型, 逗号分隔: overtime/加班、trip/travel/business_trip/出差/外出、leave/请假、patch/repair-check/补卡 (必填)
--users string 用户 ID 列表, 逗号分隔 (必填)
```
审批类型映射(关键词 → bizType):
- `overtime` / `加班` → `1`
- `trip` / `travel` / `business_trip` / `business-trip` / `出差` / `外出` → `2`(**服务端查询接口 bizType=2 同时覆盖出差与外出,两者合并为同一类、不再细分**;传入任一别名都会返回这两类记录)
- `leave` / `请假` → `3`
- `patch` / `repair-check` / `repair_check` / `补卡` → `4`
> 查询不区分外出与出差,如果需要在提交入口区分外出(`TRAVEL`)与出差(`OUT`),请改用 `dws attendance approve templates --type travel|out`。
返回每条记录含:用户 ID、审批标签、审批子类型、审批类型、生效时间、时长、时长单位、流程实例 ID。
### 查询补卡/请假/加班/外出/出差审批提交链接 (必须走引导流程)
```
Usage:
dws attendance approve templates [flags]
Example:
dws attendance approve templates --type leave
dws attendance approve templates --type REPAIR_CHECK
dws attendance approve templates --type 加班
dws attendance approve templates --type travel # 外出,等价 --type TRAVEL
dws attendance approve templates --type 出差 # 出差,等价 --type OUT
Flags:
--type string 审批类型:repair-check/patch/补卡、leave/请假、overtime/加班、travel/外出、out/trip/出差,或 REPAIR_CHECK/LEAVE/OVERTIME/TRAVEL/OUT(必填)
```
当用户提到需要提交补卡、请假、加班、外出或出差时,优先使用该命令查询考勤审批表单模板提交链接,并引导用户点击返回的 `submitUrl` 提交。
`corpId` 和 `opUserId` 由系统参数自动注入,无需通过命令参数传入。
审批类型映射:补卡=`REPAIR_CHECK`,请假=`LEAVE`,加班=`OVERTIME`,外出=`TRAVEL`,出差=`OUT`(`trip` / `business_trip` / `business-trip` 亦映射为 `OUT`)。返回结果为列表,每条记录包含 `approveType`、`formName`、`processCode`、`submitUrl`。
#### 引导用户自主选择合适的表单模板流程
如果返回多个表单模板,必须将多个可用模板都返回给用户,并引导用户根据实际场景自主选择合适的模板提交:
- 请假场景:可根据 `formName` 将与用户请假类型更匹配的模板放在前面展示。例如用户明确说年假、事假、病假、调休时,将名称中包含对应假期类型的模板靠前;如果用户只泛化表达“请假”,将名称最通用的请假模板靠前,例如“请假”“员工请假”“通用请假”等,避免把专项或特殊场景模板放在最前。
- 补卡/加班场景:可将名称与“补卡”或“加班”最直接匹配的模板放在前面展示。
- 回复用户时不要直接裸露任何 `submitUrl`,所有返回的表单模板都必须使用 Markdown 可点击链接格式展示:`[formName](submitUrl)`,例如 `[员工请假](https://...)`。如存在更匹配的模板,可以放在列表前面,但不要只返回推荐模板,必须同时返回其它可用模板供用户选择,且每个模板都应是用户可直接点击的 Markdown 链接。
### 导入排班记录(排班 = 为员工安排工作日期和班次, 写场景接口,必须走二次确认流程)
```
Usage:
dws attendance schedule import [flags]
Example:
dws attendance schedule import --group-id 123456 \
--schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \
--yes
Flags:
--group-id string 考勤组(必填,传入考勤组ID)
--schedules string 排班记录 JSON 数组(必填)
--yes 跳过确认提示
```
为排班制考勤组导入排班记录。`--schedules` 为 JSON 数组,每条记录包含:
- `userId`: 员工ID
- `classId`: 班次ID
- `workDate`: 工作日期(YYYY-MM-DD),如 2026-04-22
- `checkBeginTime`: 开始打卡时间
- `checkEndTime`: 结束打卡时间
- `isRest`: 是否休息日 Y/N(可选)
#### AI 调用 `schedule import` 的二次确认流程
`schedule import` 是写操作,会为考勤组导入或变更员工排班。AI 调用时必须按以下流程执行,不得在未确认的情况下直接导入:
1. **识别写操作**:用户表达“导入排班 / 设置排班 / 安排排班 / 给员工排班 / 批量排班”等意图时,命中 `schedule import`。
2. **收集必要参数**:必须明确 `--group-id` 和 `--schedules`,并确认排班记录中的 `userId`、`classId`、`workDate`、`checkBeginTime`、`checkEndTime`、`isRest` 等字段。
3. **展示导入摘要并反问确认**:向用户展示考勤组 ID、导入员工数量、涉及日期范围、班次 ID 列表,以及排班记录明细摘要,并询问是否确认执行导入。
4. **用户确认后再执行导入**:只有用户明确确认后,才可以执行 `dws attendance schedule import ... --format json`。
确认话术示例:
```text
即将导入排班记录,请确认:
- 考勤组 ID:<GROUP_ID>
- 员工数量:<USER_COUNT>
- 日期范围:<START_DATE> ~ <END_DATE>
- 班次 ID:<CLASS_IDS>
- 排班明细:
- <USER_ID>:<WORK_DATE> <CHECK_BEGIN_TIME>-<CHECK_END_TIME>,班次 <CLASS_ID>
是否确认执行导入?
```
如用户明确要求跳过确认,或命令中明确包含全局 `--yes`,可跳过二次确认。
### 获取排班记录
**禁止直接调用 `dws attendance schedule get`。必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行。**
- 任何"查询排班"、"查看排班"、"XX考勤组的排班"、"X月份的排班"场景,**一律走 attendance-schedule.md 工作流**,由脚本 `attendance_schedule_export.py` 统一处理
- 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出
- 违反后果:人数多时接口超时/报错、输出裸 userId 和 classId 用户看不懂、无排班表格式
- **此处不提供 `schedule get` 的 Usage/Flags,防止绕过工作流直接拼命令。完整参数由 attendance-schedule.md 工作流中的脚本内部使用。**
### 查询当前用户可管理的所有班次详情
```
Usage:
dws attendance class search [flags]
Example:
dws attendance class search
dws attendance class search --query "早班" --filter-type MINE_OWN
dws attendance class search --page 1 --limit 50
Flags:
--filter-type string 班次类型: ALL 全部班次 / MINE_OWN 我负责的 (可选)
--query string 班次名称关键字, 模糊搜索 (可选)
--page int 页码, 从 1 开始 (可选, 默认 1)
--limit int 每页条数, 最大 200 (可选, 默认 20)
```
### 查询班次详情
```
Usage:
dws attendance class get [flags]
Example:
dws attendance class get --class-id 1170996821
Flags:
--class-id int 班次 ID (必填)
```
根据班次 ID 查询该班次的完整详细信息。班次 ID 可从 `class search` 返回结果中提取,也有可能来源于用户手动输入。
### 创建班次 (写场景接口,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要,包括班次名称、上下班时间、休息时段等
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传全局 `--yes` 执行命令
**禁止未经用户确认直接执行或自动添加 `--yes`。**
```
Usage:
dws attendance class create [flags]
Example:
dws attendance class create --name "早班" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:00","across":0},{"checkType":"OffDuty","checkTime":"17:00","across":0}]}]}' --timeout 10
# 带休息时段(12:00-13:00 午休)
dws attendance class create --name "测试CLI" --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10
Flags:
--name string 班次名称 (必填)
--owner string 班次负责人 userId (可选)
--class-vo string 完整 TopAtClassVO JSON 字符串, 包含 sections 等复杂子对象 (必填)
```
创建一个新班次。`--name` 和 `--class-vo`(包含 `sections`)必填。`sections` 定义班次的上下班时间段,支持多段上下班,每段包含 `times` 数组(有且只能有两个对象:上班+下班)。由于保存班次耗时较久,建议加 `--timeout 10`。
`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。
`--class-vo` 支持字段:
- `name`(string, 必填) `owner`(string, 可选)
- `sections`([]object, 必填): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty, 必填) `checkTime`("HH:mm", 必填) `across`(0/1, 必填) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制)
- `setting`(object, 可选): `seriousLateMinutes`(严重迟到分钟) `absenteeismLateMinutes`(旷工迟到分钟) `attendDays`(出勤天数) `topRestTimeList`([]object, 仅单段上下班时可用,最多3段: checkType/checkTime("HH:mm")/across)
### 更新班次 (写场景接口,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要,包括班次 ID、要修改的字段含义及新值
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传全局 `--yes` 执行命令
**禁止未经用户确认直接执行或自动添加 `--yes`。**
```
Usage:
dws attendance class update [flags]
Example:
dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10
dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10
# 带休息时段(12:00-13:00 午休)
dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"09:00","across":0},{"checkType":"OffDuty","checkTime":"18:00","across":0}]}],"setting":{"topRestTimeList":[{"checkType":"OnDuty","checkTime":"12:00","across":0},{"checkType":"OffDuty","checkTime":"13:00","across":0}]}}' --timeout 10
Flags:
--class-id int 班次 ID (必填)
--name string 班次名称 (可选,不传则保持原值)
--owner string 班次负责人 userId (可选,不传则保持原值)
--class-vo string 完整 TopAtClassVO JSON 字符串,用于修改复杂子对象 (可选)
```
更新班次配置。`--class-id` 必填,其余均可选,仅需对要修改的字段进行赋值,未传字段会自动从已有配置补充。小改用单字段 flag(如 `--name`);修改上下班时间、休息时段等复杂子对象时用 `--class-vo` 传入完整 JSON。`--class-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。
`checkTime` 字段统一使用 "HH:mm" 格式(如 "09:00"、"17:30"),CLI 自动转换为服务端所需格式。
`--class-vo` 支持字段(均可选,只需包含要修改的字段):
- `name`(string) `owner`(string)
- `sections`([]object): 每个对象含 `times`([]object),每个 time 含 `checkType`(OnDuty/OffDuty) `checkTime`("HH:mm") `across`(0/1) `freeCheck`(bool) `beginMin`(number, -1不限制) `endMin`(number, -1不限制)
- `setting`(object): `seriousLateMinutes` `absenteeismLateMinutes` `attendDays` `topRestTimeList`([]object: checkType/checkTime("HH:mm")/across)
由于保存班次耗时较久,建议加 `--timeout 10`。
### 分页查询补卡规则,支持按名称搜素
```
Usage:
dws attendance adjustment search [flags]
Example:
dws attendance adjustment search --page 1 --limit 20
dws attendance adjustment search --query "标准" --page 1 --limit 50
Flags:
--page int 页码, 从 1 开始 (必填, 默认 1)
--query string 补卡规则名称关键字, 模糊搜索 (可选)
--limit int 每页条数, 200 以内 (必填, 默认 20)
```
### 查询补卡规则详情
```
Usage:
dws attendance adjustment get [flags]
Example:
dws attendance adjustment get --adjustment-id 12345
Flags:
--adjustment-id int 补卡规则主键 ID (必填)
```
根据补卡规则主键 ID 查询对应的补卡规则详情。主键 ID 可从 `adjustment search` 返回结果中提取,也有可能来源于用户手动输入。**注意:已被删除或被更新覆盖的补卡规则无法查询到。**
### 分页查询加班规则,支持按名称搜素
```
Usage:
dws attendance overtime search [flags]
Example:
dws attendance overtime search --page 1 --limit 20
dws attendance overtime search --query "节假日" --page 1 --limit 50
Flags:
--page int 页码, 从 1 开始 (必填, 默认 1)
--query string 加班规则名称关键字, 模糊搜索 (可选)
--limit int 每页条数, 200 以内 (必填, 默认 20)
```
### 查询加班规则详情
```
Usage:
dws attendance overtime get [flags]
Example:
dws attendance overtime get --overtime-id 12345
Flags:
--overtime-id int 加班规则主键 ID (必填)
```
根据加班规则主键 ID 查询对应的加班规则详情。主键 ID 可从 `overtime search` 返回结果中提取,也有可能来源于用户手动输入。**已被删除或更新覆盖的加班规则也可以查到。**
### 查询考勤组列表
```
Usage:
dws attendance group search [flags]
Example:
dws attendance group search --query "研发"
dws attendance group search --type FIXED --limit 50
dws attendance group search --page 1 --limit 20
Flags:
--query string 考勤组名称关键字, 模糊搜索 (可选)
--page int 页码, 从 1 开始 (必填, 默认 1)
--limit int 每页条数, 200 以内 (必填, 默认 20)
--query-ble 是否查询蓝牙设备列表 (可选, 默认 false)
--query-position 是否查询地理定位和 Wifi 名称 (可选, 默认 false)
--type string 考勤组类型: FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选)
```
### 查询考勤组全量信息
```
Usage:
dws attendance group get [flags]
Example:
dws attendance group get --group-id 123456
Flags:
--group-id int 考勤组 ID (必填)
```
根据考勤组 ID 查询该考勤组的全量信息。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。如果只需查询成员、打卡地址、蓝牙、Wifi 子集,请使用 `group filtered-get` 以节省查询成本。
返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids <userId1>,<userId2>,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId
### 按需查询考勤组部分信息
```
Usage:
dws attendance group filtered-get [flags]
Example:
dws attendance group filtered-get --group-id 123456 --member
dws attendance group filtered-get --group-id 123456 --position --wifi
Flags:
--group-id int 考勤组 ID (必填)
--member 是否查询考勤组成员信息 (可选, 默认 false)
--position 是否查询打卡地址 (可选, 默认 false)
--wifi 是否查询打卡 Wifi (可选, 默认 false)
--bles 是否查询打卡蓝牙 (可选, 默认 false)
```
强烈建议在仅需查询成员、打卡地址、蓝牙、Wifi 时调用该命令,避免全量查询带来的性能开销。考勤组 ID 可从 `group search` 返回结果中提取,也有可能来源于用户手动输入。
返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids <userId1>,<userId2>,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId
### 更新考勤组成员 (写场景接口,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要添加/移除的成员列表
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传全局 `--yes` 执行命令
**禁止未经用户确认直接执行或自动添加 `--yes`。**
```
Usage:
dws attendance group update-members [flags]
Example:
dws attendance group update-members --group-id 123456 --add-users userId1,userId2
dws attendance group update-members --group-id 123456 --remove-users userId1
dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2
Flags:
--group-id int 考勤组 ID (必填)
--add-users string 添加考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选)
--remove-users string 删除考勤人员 userId 列表, 逗号分隔, 最多 20 个 (可选)
--add-extra-users string 添加无需考勤的人员 userId 列表, 逗号分隔, 最多 20 个 (可选)
--remove-extra-users string 删除无需考勤的成员 userId 列表, 逗号分隔, 最多 20 个 (可选)
--add-depts string 添加考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选),若要添加全公司,根部门id为-1
--remove-depts string 删除考勤部门 ID 列表, 逗号分隔, 最多 20 个 (可选),全公司根部门id为-1
```
对指定考勤组的成员进行增删操作。--group-id 必填,其余参数均为可选,但至少需要传入一个变更项,否则命令拒绝执行。每次调用各参数最多传 20 个 ID。"无需考勤"人员指考勤组内豁免打卡的成员(如高管)。
### 更新考勤组配置 (写场景接口,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要,包括考勤组 ID、要修改的字段含义及新值
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传全局 `--yes` 执行命令
**禁止未经用户确认直接执行或自动添加 `--yes`。**
```
Usage:
dws attendance group update [flags]
Example:
dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10
dws attendance group update --group-id 123456 --owner userId1 --timeout 10
dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10
dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市...","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10
Flags:
--group-id int 考勤组 ID (必填)
--name string 考勤组名称 (可选)
--type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (可选)
--owner string 考勤组主负责人 userId (可选)
--enable-outside-check 是否允许外勤打卡 true/false (可选)
--classIds string 所选班次 id 列表, JSON 数组格式, 如 '[123,456]' (可选)
--group-vo string 完整 groupVO JSON 字符串, 用于修改复杂子对象 (可选)
```
更新考勤组配置。--group-id 必填,其余均可选,但至少需指定一个修改项。仅需对要修改的字段进行赋値,其余字段会自动从已有配置补充。小改用单字段 flag;修改打卡地址、wifi、蓝牙设备、循环排班等复杂子对象时用 `--group-vo` 传入完整 JSON。`--group-vo` 与单字段 flag 同时传入时,单字段 flag 优先级更高。
`--group-vo` 支持字段(均可选,只需包含要修改的字段):
- 基础:`name`(名称) `type`(FIXED/TURN/NONE) `owner`(主负责人 userId) `managerList`([]string 子负责人) `skipHolidays`(bool,只在固定班制和自由工时生效) `defaultGroup`(bool) `classIds`([]number,所选班次 id,只有固定班制和排班制才有班次,自由工时没有)
- 打卡范围:`trimDistance`(微调距离) `enablePositionOfGps/Wifi/Ble`(bool)
- 打卡地址:`positions`([]对象: title/address/latitude/longitude/offset,其中 offset 为该地址允许的打卡范围米)
- Wifi:`wifis`([]对象: ssid/macAddr/groupId)
- 蓝牙:`bleDeviceVOList`([]对象: name/deviceUid/sn/productType/devServId)
- 外勤:`enableOutsideCheck`(bool) `enableOutsideCameraCheck/Remark/Apply`(bool) `outsideCheckApproveMode`(NO_NEED_APPROVE/APPROVE_FIRST/CHECK_FIRST/APPROVE_EVERYTIME) `outSideCheckApplyType`(1全天/2上班/3下班) `forbidHideOutSideAddress`(bool) `enableOutSideUpdateNormalCheck`(下班时允许外勤卡更新内勤卡) `enableOnDutyNormalUpdateOutsideCheck`(上班时允许内勤卡更新外勤卡)
- 打卡方式:`enableCameraCheck/openCameraCheck` `openFaceCheck` `enableFaceStrictMode` `enableFaceBeauty`(bool) `onlyMachineCheck`(bool) `permitMaxBeaconCount`(number) `disableCheckWhenRest`(bool,休息日打卡需审批,只在固定班制和排班制生效)
- 固定班制设置(FIXED):`defaultClassId`(number) `workDayClassList`([]number,共7个值代表周日到周六每天的班次id,为0表示当天休息,如[0,1279240003,0,0,0,0,0]表示只有周一上班)
- 排班制设置(TURN):`disableCheckWithoutSchedule`(bool,true=未排班时禁止打卡) `enableEmpSelectClass`(未排班时员工可选班次) `enableScheduleAutoMatch`(未排班时系统自动匹配)
- 循环排班(非必填,不设置则由管理员手动排班):`cycleDays`(number) `startCycleDate`(时间戳,毫秒) `cycleScheduleList`([]对象: cycleName/groupId/isValid(Y/N)/itemList[{classId/className/isValid}])
- 自由工时设置(NONE):`workDays`([1-7],1=周一7=周日) `freeCheckDayStartMinOffset`(number,距0点分钟数) `freeCheckCoreTime`(最短工作时长,分钟) `freeCheckDemandWorkMinutes`(要求打卡时长,分钟) `freeCheckSettingVO`(对象: freeCheckType(CYCLE上下班交替/MAX_TIME_UPDATE最大时间打卡)/freeWorkDayLackSwitch/freeOnDutyLackMinOffset/freeOffDutyLackMinOffset/delimitOffsetMinutesBetweenDays/freeCheckGapVO{onOffCheckGapMinutes/offOnCheckGapMinutes}) `freeGroupSpecialDayVO`(对象: specialOnDutyDays[]/specialOffDutyDays[])
### 创建考勤组 (写场景接口,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要,包括考勤组名称、类型、班次列表等
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传全局 `--yes` 执行命令
**禁止未经用户确认直接执行或自动添加 `--yes`。**
```
Usage:
dws attendance group create [flags]
Example:
dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10
dws attendance group create --name "自由工时分组" --type NONE --timeout 10
Flags:
--name string 考勤组名称 (必填)
--type string 考勤组类型:FIXED 固定班制 / TURN 排班制 / NONE 自由工时 (必填)
--owner string 考勤组主负责人 userId (可选)
--group-vo string 完整 groupVO JSON 字符串,用于传入复杂子对象 (可选)
```
创建一个新的考勤组。`--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一。
**条件必填(type=FIXED 固定班制时)**:`--group-vo` 必须包含 `workDayClassList`(工作日班次列表,不能为空,共 7 个值,代表周日到周六每天的班次 ID,为 0 表示当天休息)和 `defaultClassId`(默认班次 ID,不能为 null)。
`--group-vo` 可用字段说明(与 `group update` 的 `--group-vo` 一致,均可选,只需包含要设置的字段):
【基础信息】
id number 考勤组 id(创建时无需传入,由服务端分配)
name string 考勤组名称(必填)
type string 考勤组类型:FIXED(固定班制)/ TURN(排班制)/ NONE(自由工时)(必填)
owner string 考勤组主负责人 userId
managerList []string 考勤组子负责人 userId 列表
skipHolidays bool 节假日自动排休(只有固定班制和自由工时考勤组生效)
defaultGroup bool 是否默认考勤组
classIds []number 所选班次 id 列表(只有固定班制和排班制才有班次,自由工时没有)
【打卡范围与定位】
trimDistance number 定位允许微调距离(米)
enablePositionOfGps bool 打卡是否允许开启 GPS 定位
enablePositionOfWifi bool 打卡是否允许开启 Wifi 定位
enablePositionOfBle bool 打卡是否允许开启蓝牙定位
enableMacCheck bool 开启 MAC 地址校验
checkDistanceType string 打卡距离类型:NORMAL(正常) / OUTER_ADDRESS(仅允许在外勤地址打卡)
wifiCompanyId number 允许打卡的 Wifi 公司 ID
【打卡地址】
positions []object 打卡地址列表,每个对象字段:
title string 地址名称
address string 详细地址
latitude number 纬度
longitude number 经度
offset number 该地址允许的打卡范围(米)
【打卡 Wifi】
wifis []object 打卡 Wifi 列表,每个对象字段:
ssid string Wifi 名称
macAddr string MAC 地址
groupId number 所属考勤组 ID
【蓝牙设备】
bleDeviceVOList []object 蓝牙设备列表,每个对象字段:
name string 设备名称
deviceUid string 设备 ID
sn string 序列号
productType string 产品类型
devServId number 设备服务 ID
【外勤打卡设置】
enableOutsideCheck bool 是否允许外勤打卡
openOutsideCameraCheck bool 外勤打卡是否开启拍照
enableOutsideRemark bool 是否允许外勤备注
enableOutsideApply bool 外勤打卡是否需审批
forbidHideOutSideAddress bool 禁止隐藏外勤打卡地址
enableOutSideUpdateNormalCheck bool 下班时允许外勤卡更新内勤卡
enableOnDutyNormalUpdateOutsideCheck bool 上班时允许内勤卡更新外勤卡
outsideCheckApproveMode string 外勤审批模式:NO_NEED_APPROVE / APPROVE_FIRST / CHECK_FIRST / APPROVE_EVERYTIME
outSideCheckApplyType number 外勤打卡申请类型:1 全天 / 2 上班 / 3 下班
【打卡方式】
openCameraCheck bool 是否开启拍照打卡
enableFaceStrictMode bool 是否开启人脸严格模式
enableFaceBeauty bool 是否开启人脸美颜
onlyMachineCheck bool 是否仅允许考勤机打卡
permitMaxBeaconCount number 允许最大蓝牙信标数量
disableCheckWhenRest bool 休息日打卡需审批(只在固定班制和排班制生效)
【固定班制设置(FIXED)】
defaultClassId number 默认班次 ID
workDayClassList []number 工作日班次列表,共 7 个值代表周日到周六每天的班次 id,为 0 表示当天休息。
例如 [0,1279240003,0,0,0,0,0] 表示只有周一上班
【排班制设置(TURN)】
disableCheckWithoutSchedule bool 未排班时禁止打卡
enableEmpSelectClass bool 未排班时员工可选班次
enableScheduleAutoMatch bool 未排班时系统自动匹配
cycleDays number 循环排班周期天数
startCycleDate number 循环排班开始日期(时间戳,毫秒)
cycleScheduleList []object 循环排班列表:
cycleName string 循环排班名称
groupId number 考勤组 ID
isValid string 是否生效:Y/N
itemList []object 循环排班明细:
classId number 班次 ID
className string 班次名称
isValid string 是否生效:Y/N
【自由工时设置(NONE)】
workDays []number 工作日,[1-7],1=周一,7=周日
freeCheckDayStartMinOffset number 距 0 点分钟数
freeCheckCoreTime number 最短工作时长(分钟)
freeCheckDemandWorkMinutes number 要求打卡时长(分钟)
freeCheckSettingVO object 自由打卡设置:
freeCheckType string 打卡类型:CYCLE(上下班交替)/ MAX_TIME_UPDATE(最大时间打卡)
freeWorkDayLackSwitch bool 工作日缺卡开关
freeOnDutyLackMinOffset number 上班缺卡分钟数偏移
freeOffDutyLackMinOffset number 下班缺卡分钟数偏移
delimitOffsetMinutesBetweenDays number 跨天切割分钟数偏移
freeCheckGapVO object 打卡间隔:
onOffCheckGapMinutes number 上班到下班最小间隔(分钟)
offOnCheckGapMinutes number 下班到上班最小间隔(分钟)
freeGroupSpecialDayVO object 特殊日期设置:
specialOnDutyDays []number 特殊上班日期
specialOffDutyDays []number 特殊休息日期
当使用 `--group-vo` 传入完整 JSON 时,`--name`、`--type`、`--owner` 仍可覆写 JSON 中的同名字段。由于保存考勤组耗时较久,建议加 `--timeout 10`。
> **创建成功后的跳转链接**:`group create` 调用成功后,CLI 会在标准输出之后额外打印一条钉钉 PC 端跳转链接(仅在响应中同时包含 `corpId` 与 `groupId`/`id` 时输出),格式为 `dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Fhrmregister.dingtalk.com%2Fsubapp%2Fattend%2Findex%3Fcode%3Dattend%26corpId%3D{corpId}%26ddtab%3Dtrue%26from%3Dattend%23%2FgroupModify%3Fid%3D{groupId}`,用于用户在钉钉 PC 客户端一键打开该考勤组详情页验证创建结果。Agent 遇到该跳转链接时应原样呈现给用户,不要对链接进行二次编码或裁剪。
> **新建考勤组并同时添加成员的工作流**:`group create` 不支持在创建时直接传入成员列表。若用户需要在新建考勤组后立即添加成员,必须先执行 `group create` 创建考勤组,从返回结果中提取 `groupId`,再执行 `group update-members --group-id <GROUP_ID> --add-users ...` 完成成员添加。
### 查询某个人的考勤统计摘要
```
Usage:
dws attendance summary [flags]
Example:
dws attendance summary --user USER_ID --date 2026-03-12 --stats-type week
Flags:
--date string 查询日期,格式 YYYY-MM-DD 或 YYYY-MM-DD HH:mm:ss(必填)
--stats-type string 统计类型:week(周统计)/ month(月统计)(必填)
--user string 钉钉用户 ID(必填)
```
### 查询考勤组与考勤规则
```
Usage:
dws attendance rules [flags]
Example:
dws attendance rules --date 2026-03-14
dws attendance rules --date "2026-03-14 09:00:00"
Flags:
--date string 考勤日期, 格式 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss (必填)
```
查询考勤组/考勤规则。例如:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算。
### 查询个人规则设置
```
Usage:
dws attendance selfsetting get [flags]
Example:
dws attendance selfsetting get --setting-scene checkRemind --user <USER_ID> --format json
dws attendance selfsetting get --setting-scene fastCheck --user <USER_ID> --format json
Flags:
--setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填)
--user string 查询用户 ID (必填)
```
调用 MCP 工具 query_self_setting 查询个人规则设置,包括打卡提醒、极速打卡、打卡结果通知、缺卡提醒、个人考勤统计通知、团队考勤统计通知等设置项。MCP 入参 `userId` 必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。
`--setting-scene` 枚举值:
- `checkRemind`: 打卡提醒
- `fastCheck`: 极速打卡
- `checkResultNotify`: 打卡结果通知
- `lackRemind`: 缺卡提醒
- `personalAttendStatNotify`: 个人考勤统计通知
- `bossAttendStatNotify`: 团队考勤统计通知
返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 可能根据 `--setting-scene` 仅返回对应设置项相关字段。常见字段包括:
- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc`
- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch`
- `checkResultNotify`: `checkResultMsg`, 取值 0 表示关闭, 1 表示开启
- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser`, 取值 0 表示关闭, `null` 或 1 表示开启
- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType`
- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType`
其中周报/月报通知渠道枚举值:0 表示全关闭,1 表示工作通知,2 表示钉邮,3 表示工作通知和钉邮。
### 更新保存个人规则设置 (写场景接口,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要,包括目标用户、设置场景、当前值、新值和最终命令参数
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传全局 `--yes` 执行命令
**禁止未经用户确认直接执行或自动添加 `--yes`。**
```
Usage:
dws attendance selfsetting save [flags]
Example:
# 开启打卡结果通知(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes)
dws attendance selfsetting save --setting-scene checkResultNotify --user <USER_ID> --check-result-msg 1 --yes --format json
# 更新极速打卡设置(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes)
dws attendance selfsetting save --setting-scene fastCheck --user <USER_ID> --onduty-check-type 3 --voice-remind-switch=true --yes --format json
# 更新打卡提醒设置(执行前必须展示变更摘要并获得用户明确确认,确认后再追加 --yes)
dws attendance selfsetting save --setting-scene checkRemind --user <USER_ID> --check-remind-user-on-duty=false \
--check-remind-setting '{"onDutyRemind":{"openRemind":true,"remindMinutes":10}}' --yes --format json
Flags:
--setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填)
--user string 更新用户 ID (必填)
--check-remind-setting string 打卡提醒 DING 渠道设置 JSON
--check-remind-user-on-duty 打卡提醒工作通知渠道:用户个人上班打卡提醒开关
--check-remind-user-off-duty 打卡提醒工作通知渠道:用户个人下班打卡提醒开关
--enable-onduty-check-remind-of-pc PC 端弹窗渠道:上班打卡提醒开关
--enable-offduty-check-remind-of-pc PC 端弹窗渠道:下班打卡提醒开关
--onduty-check-type int 上班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡
--offduty-check-type int 下班极速打卡方式:1 提醒打卡,2 不提醒且不自动打卡,3 自动打卡
--onduty-remind-start-min int 上班打卡提醒开始时间,单位:分钟
--onduty-remind-end-min int 上班打卡提醒结束时间,单位:分钟
--offduty-remind-start-min int 下班打卡提醒开始时间,单位:分钟
--offduty-remind-end-min int 下班打卡提醒结束时间,单位:分钟
--fast-check-late-need-confirm 迟到时是否需要二次确认
--can-update-off-duty 是否允许用户更新下班打卡设置
--voice-remind-switch 极速打卡提示音开关
--vibration-remind-switch 极速打卡震动提醒开关
--check-result-msg int 打卡结果通知开关:0 关闭,1 开启
--lack-send-todo-msg int 缺卡提醒待办渠道:0 关闭,null 或 1 开启
--lack-remind-user int 缺卡提醒工作通知渠道:0 关闭,null 或 1 开启
--person-daily-report-switch int 个人考勤统计日报推送开关:0 关闭,1 开启
--person-week-report-type int 个人考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
--person-month-report-type int 个人考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
--boss-push-start-min int 团队考勤统计日报推送开始时间,单位:分钟;-1 表示关闭日报推送
--boss-week-report-type int 团队考勤统计周报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
--boss-month-report-type int 团队考勤统计月报通知渠道:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
--yes 用户已确认,跳过交互式确认提示
传入前必须已获得用户明确确认
```
调用 MCP 工具 save_self_setting 更新保存个人规则设置,请求体封装在 `RuleMcpSaveSelfSettingRequest` 中。`settingScene` 必填;MCP 入参 `userId` 必填,CLI 的 `--user` 也必填,必须显式传入目标用户 ID。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。
`selfsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值:
- `checkRemind`: `checkRemindSetting`、`checkRemindUserOnDuty`、`checkRemindUserOffDuty`、`enableOndutyCheckRemindOfPc`、`enableOffdutyCheckRemindOfPc`
- `fastCheck`: `ondutyCheckType`、`offdutyCheckType`、`ondutyRemindStartMin`、`ondutyRemindEndMin`、`offdutyRemindStartMin`、`offdutyRemindEndMin`、`fastCheckLateNeedConfirm`、`canUpdateOffDuty`、`voiceRemindSwitch`、`vibrationRemindSwitch`
- `checkResultNotify`: `checkResultMsg`
- `lackRemind`: `lackSendTodoMsg`、`lackRemindUser`
- `personalAttendStatNotify`: `personDailyReportSwitch`、`personWeekReportType`、`personMonthReportType`
- `bossAttendStatNotify`: `bossPushStartMin`、`bossWeekReportType`、`bossMonthReportType`
返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。其中 `result` 为 boolean,表示保存是否成功。
#### 强制执行流程:Agent 调用 `selfsetting save`
`selfsetting save` 是写操作,会修改用户个人规则设置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交:
1. **识别写操作**:用户表达“更新个人规则设置 / 保存打卡提醒 / 修改极速打卡 / 关闭缺卡提醒 / 开启打卡结果通知 / 设置个人考勤统计通知 / 设置团队考勤统计通知”等意图时,命中 `selfsetting save`。
2. **收集必要参数**:必须明确 `--user`、`--setting-scene`,以及对应场景下将要修改的字段和值。
3. **前置查询当前设置**:执行 `dws attendance selfsetting get --setting-scene <SCENE> --user <USER_ID> --format json`,获取当前配置,用于确认目标用户和当前值。
4. **展示待写入数据并等待确认**:向用户展示目标用户、设置场景、要更新的字段、当前值、新值和最终命令参数摘要,并等待用户明确确认。
5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以追加全局 `--yes` 执行 `dws attendance selfsetting save ... --yes --format json`。
确认话术示例:
```text
即将更新个人考勤规则设置,请确认:
- 用户 ID:<USER_ID>
- 设置场景:checkResultNotify
- 修改内容:
- 打卡结果通知(checkResultMsg):关闭 → 开启
是否确认执行更新?
```
禁止在未获得用户明确确认前执行保存;禁止为了推进流程自动添加全局 `--yes`。即使用户在最初需求中表达“直接改/不用问”,也必须先展示完整参数摘要并获得明确确认,才允许追加 `--yes` 执行。
### 查询全局规则设置(仅管理员)
```
Usage:
dws attendance globalsetting get [flags]
Example:
dws attendance globalsetting get --scope 企业 --setting-scene checkRemind --format json
dws attendance globalsetting get --scope 全公司 --setting-scene bossAttendStatNotify --format json
Flags:
--setting-scene string 查询设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填)
--scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填)
```
调用 MCP 工具 `query_global_setting` 查询全局规则设置,请求体封装在 `RuleMcpQueryGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认查询的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。
返回 `ServiceResult`,包含 `success`、`code`、`message`、`result`。`result` 常见字段包括:
- `checkRemindCorp`: 打卡提醒企业总开关
- `checkRemindPcCorp`: 打卡提醒 PC 端企业总开关
- `fastCheckCorp`: 极速打卡企业总开关
- `enableCheckCertPush`: 打卡结果通知企业总开关
- `lackRemindCorp`: 缺卡提醒企业总开关
- `enablePersonalDailyReport`: 个人考勤统计通知日报企业总开关
- `enablePersonalWeeklyReport`: 个人考勤统计通知周报企业开关,钉邮渠道
- `enablePersonalWeeklyReportCard`: 个人考勤统计通知周报企业开关,工作通知渠道
- `enablePersonalMonthlyReport`: 个人考勤统计通知月报企业总开关
- `bossDailyReportType`: 团队考勤统计通知日报发送渠道类型,0 全关闭,1 开启
- `bossWeeklyReportType`: 团队考勤统计通知周报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
- `bossMonthlyReportType`: 团队考勤统计通知月报发送渠道类型,0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
### 更新保存全局规则设置(写场景接口,仅管理员,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须先向用户展示待执行操作的完整参数摘要,等待用户明确确认后,才能追加 `--yes` 执行。
```
Usage:
dws attendance globalsetting save [flags]
Example:
dws attendance globalsetting save --scope 企业 --setting-scene checkRemind --check-remind-corp=true --yes --format json
dws attendance globalsetting save --scope 全公司 --setting-scene fastCheck --fast-check-corp=false --yes --format json
dws attendance globalsetting save --scope 所有人 --setting-scene bossAttendStatNotify --boss-daily-report-type 1 --boss-weekly-report-type 3 --yes --format json
Flags:
--setting-scene string 更新设置项: checkRemind/fastCheck/checkResultNotify/lackRemind/personalAttendStatNotify/bossAttendStatNotify (必填)
--scope string 全局范围确认,必须明确输入:企业/全公司/所有人(必填)
--check-remind-corp 打卡提醒企业总开关
--check-remind-pc-corp 打卡提醒 PC 端弹窗企业总开关
--fast-check-corp 极速打卡企业总开关
--enable-check-cert-push 打卡结果通知企业总开关
--lack-remind-corp 缺卡提醒企业总开关
--enable-personal-daily-report 个人考勤统计通知日报企业总开关
--enable-personal-weekly-report 个人考勤统计通知周报企业开关,钉邮渠道
--enable-personal-weekly-report-card 个人考勤统计通知周报企业开关,工作通知渠道
--enable-personal-monthly-report 个人考勤统计通知月报企业总开关
--boss-daily-report-type int 团队考勤统计通知日报发送渠道类型:0 全关闭,1 开启
--boss-weekly-report-type int 团队考勤统计通知周报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
--boss-monthly-report-type int 团队考勤统计通知月报发送渠道类型:0 全关闭,1 工作通知,2 钉邮,3 工作通知和钉邮
--yes 用户已确认,跳过交互式确认提示
```
调用 MCP 工具 `save_global_setting` 更新保存全局规则设置,请求体封装在 `RuleMcpSaveGlobalSettingRequest` 中。`settingScene` 必填;CLI 必须通过 `--scope` 明确输入 `企业`、`全公司` 或 `所有人`,用于确认更新的是全局范围。认证信息 `corpId` 和 `opUserId` 由当前登录上下文自动注入,无需手动传入。该接口仅管理员可以调用。
`globalsetting save` 必须按 `--setting-scene` 传入对应场景的字段,且至少一个字段有值:
- `checkRemind`: `checkRemindCorp`、`checkRemindPcCorp`
- `fastCheck`: `fastCheckCorp`
- `checkResultNotify`: `enableCheckCertPush`
- `lackRemind`: `lackRemindCorp`
- `personalAttendStatNotify`: `enablePersonalDailyReport`、`enablePersonalWeeklyReport`、`enablePersonalWeeklyReportCard`、`enablePersonalMonthlyReport`
- `bossAttendStatNotify`: `bossDailyReportType`、`bossWeeklyReportType`、`bossMonthlyReportType`
返回 `ServiceResult`,其中 `result` 为 boolean,表示保存是否成功。
### 获取企业考勤字段列表(仅管理员)
```
Usage:
dws attendance report columns
Example:
dws attendance report columns
```
根据操作者的列权限,过滤并返回其有权查看的考勤字段列表。操作者必须是管理员,否则返回权限错误。
### 根据字段查询考勤数据(仅管理员)
```
Usage:
dws attendance report query-data [flags]
Example:
dws attendance report query-data \
--users userId1,userId2 --columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59"
Flags:
--columns string 字段 ID 列表, 逗号分隔, 可通过 report columns 获取(必填)
--end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填)
--start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填)
--users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填)
```
根据字段查询考勤数据,含列权限过滤和用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。
### 查询用户假期数据(仅管理员)
```
Usage:
dws attendance report query-leave [flags]
Example:
dws attendance report query-leave \
--users userId1,userId2 --leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59"
Flags:
--end string 结束日期, 格式 yyyy-MM-dd HH:mm:ss(必填)
--leave-names string 假期类型名称列表, 逗号分隔, 不填则查询所有假期类型(选填)
--start string 开始日期, 格式 yyyy-MM-dd HH:mm:ss(必填)
--users string 目标用户 ID 列表, 逗号分隔, 最多 20 人(必填)
```
查询用户假期数据,含用户查看权限校验。--users 最多 20 人,--start 到 --end 不超过 32 天。
### 查询当前用户假期规则列表
```
Usage:
dws attendance vacation types
Example:
dws attendance vacation types
Flags:
无
```
调用 MCP 工具 get_leave_types 查询当前用户可用的假期规则列表。例如:年假、事假、病假等假期类型及对应规则。请求体封装在 McpLeaveTypeRequest 中,认证信息(corpId、opUserId)由系统自动注入,无需手动传入。
### 查询指定员工假期余额
```
Usage:
dws attendance vacation balance [flags]
Example:
dws attendance vacation balance --users userId1,userId2 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890
Flags:
--users string 目标员工 ID 列表, 逗号分隔 (必填)
--leave-code string 假期规则 code (选填,不传则查询所有假期规则余额)
```
调用 MCP 工具 get_leave_balance_quota 查询指定员工的假期余额。例如:查询某员工年假还剩多少、病假额度等。`--leave-code` 可通过 `vacation types` 获取;不传 `--leave-code` 时查询所有假期规则余额。认证信息(corpId、opUserId)由系统自动注入。
如用户需要“所有假期规则余额 / 导出假期余额列表 / 所有假期规则余额 Excel / 按截图样式导出假期余额”,必须先读取 [attendance-vacation.md](./attendance-vacation.md),再按其中工作流调用脚本生成 Excel。
### 查询指定员工假期余额变更记录
```
Usage:
dws attendance vacation records [flags]
Example:
dws attendance vacation records --user USER_ID --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --start 2026-04-01 --end 2026-04-22
Flags:
--user string 指定查询员工 ID (必填)
--leave-code string 假期规则 code (必填, 不传则无法查询)
--start string 查询开始日期, 格式 YYYY-MM-DD (必填)
--end string 查询结束日期, 格式 YYYY-MM-DD (必填)
```
调用 MCP 工具 get_leave_balance_records 查询指定员工的假期余额变更记录。例如:查询某员工年假变更历史、请假扣减记录等。`--leave-code` 可通过 `vacation types` 获取。认证信息(corpId、opUserId)由系统自动注入。
### 更新假期规则(写场景接口,必须走二次确认流程)
**强制执行流程**:此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传 `--user-say-yes=true` 执行命令
**禁止未经用户确认直接执行或自动添加 `--user-say-yes=true`。**
```
Usage:
dws attendance vacation update-type [flags]
Example:
# 更新假期规则名称
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --name "事假(修改版)"
# 更新假期单位
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --unit hour --per-hours 8
# 改为指定部门可见
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["1","2","3"]}]'
# 改为全公司可见(哨兵约定:必须显式传 "-1",空数组 [] 不生效)
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --visibility-rules '[{"type":"dept","visible":["-1"]}]'
Flags:
--leave-code string 假期编码(必填)
--name string 假期名称(可选)
--unit string 假期单位:day/halfDay/hour(可选)
--paid bool 是否带薪假期(可选,默认 false)
--per-hours int 一天折算小时数(可选)
--when-can-leave string 新员工请假规则:entry/formal(可选)
--visibility-rules string 适用范围规则 JSON 数组(可选)
- 不传:不修改原有可见范围
- 改为指定范围:[{"type":"staff|dept|label","visible":["id1",...]}, ...]
- 改为全公司可见(哨兵):必须显式传 [{"type":"dept","visible":["-1"]}]
- 空数组 []、[{}]、visible 为空等 → CLI 报错(HSF 端会静默忽略)
--user-say-yes 用户已确认,跳过交互式确认提示
Agent 调用时传 true 前必须完成用户二次确认
```
调用 MCP 工具 save_leave_type 更新已有假期规则。`--leave-code` 必填,指定要更新的假期规则编码。其他字段均为可选,仅需传入要修改的字段。除 `--leave-code` 外,必须至少传入一个更新字段。
**`--visibility-rules` HSF 反序列化约定(必读)**:
| 入参形态 | 业务语义 | CLI / 服务端处理 |
|---|---|---|
| 不传 | 不修改可见范围 | 服务端保留原有 `visibilityRules` |
| `[{"type":"dept","visible":["-1"]}]`(哨兵) | 改为全公司可见 | 服务端落库为空数组(全公司语义) |
| `[{"type":"staff","visible":["uid1"]}, ...]` 等有效规则 | 改为指定可见范围 | 覆盖原有 `visibilityRules` |
| 空数组 `[]` / `[{}]` / `visible` 为空 | — (反例) | **CLI 直接报错**,避免 HSF 端被静默忽略 |
- `type` 可取 `dept`(部门 ID,`-1` 是根部门 = 全公司哨兵)、`staff`(userId)、`label`(角色 ID)。
- 哨兵识别采用宽松匹配:传入列表中任意一条规则满足 `type=dept` 且 `visible` 包含 `"-1"`,即视为全公司可见,其它规则被忽略。
- 若意图是“改为全公司可见”,**必须**显式传哨兵值 `["-1"]`,**不能**传空数组 `[]`。
#### 强制执行流程:Agent 调用 `vacation update-type`
`vacation update-type` 是写操作,会修改假期规则配置。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交:
1. **识别写操作**:用户表达"更新假期规则 / 修改假期类型 / 编辑假期规则"等意图时,命中 `vacation update-type`。
2. **收集必要参数**:必须明确 `--leave-code`,以及至少一个更新字段(`--name`、`--unit`、`--paid`、`--per-hours`、`--when-can-leave`、`--visibility-rules`)。
3. **前置查询当前规则**:需先调用 `vacation types` 确认该规则是否存在及当前配置。
4. **展示待写入数据并等待确认**:向用户展示假期编码、要更新的字段及新值,并询问是否确认执行。**必须等待用户明确确认**。
5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation update-type ... --format json`。
确认话术示例:
```text
即将更新假期规则,请确认:
- 假期编码:a1b2c3d4-e5f6-7890-abcd-ef1234567890
- 更新内容:
- 名称:事假 → 事假(修改版)
是否确认执行更新?
```
如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。
#### 强制执行流程:Agent 调用 `vacation save-balance`
`vacation save-balance` 是写操作,会直接替换员工的假期余额(SET 接口,而非 ADD)。Agent 调用时 **必须按以下流程执行**,**禁止**在未确认的情况下直接提交:
1. **识别写操作**:用户表达"设置假期余额 / 调整假期额度 / 更新假期余额 / 增加假期余额 / 发放年假 / 给员工加年假"等意图时,命中 `vacation save-balance`。
2. **前置查询当前余额**:必须先调用 `vacation balance --users <target> --leave-code <code>` 获取当前余额,因为这是 SET 接口,传入值会直接替换而非累加。
3. **收集必要参数**:必须明确 `--target`(目标员工)、`--leave-code`(假期编码)、`--num`(新余额数量)、`--reason`(变更原因),以及可选参数 `--start/--end`(有效期)。
4. **计算变更并展示确认**:向用户展示目标员工、假期类型、当前余额、新余额、差额(增加或减少)、变更原因、有效期等,并询问是否确认执行。**必须等待用户明确确认**。
5. **用户确认后再执行保存**:**只有用户明确确认后**,才可以传 `--user-say-yes=true` 执行 `dws attendance vacation save-balance ... --format json`。
确认话术示例:
**设置余额场景**:
```text
即将设置员工假期余额,请确认:
- 目标员工:张三(user001)
- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890)
- 当前余额:5 天
- 新余额:8 天
- 变更差额:+3 天(增加)
- 变更原因:年度发放
- 有效期:2024-01-01 至 2024-12-31
是否确认执行设置?
```
**减少余额场景**:
```text
即将设置员工假期余额,请确认:
- 目标员工:李四(user002)
- 假期类型:年假(leaveCode: a1b2c3d4-e5f6-7890-abcd-ef1234567890)
- 当前余额:10 天
- 新余额:2 天
- 变更差额:-8 天(减少)
- 变更原因:请假扣减
注意:此操作将大幅减少余额,请确认是否继续?
是否确认执行设置?
```
如用户明确要求跳过确认,可传 `--user-say-yes=true`;否则默认必须等待确认。
### 设置员工假期余额
```
Usage:
dws attendance vacation save-balance [flags]
Example:
# 设置员工年假余额为8天
dws attendance vacation save-balance --target user001 \
--leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放"
# 设置带有效期的假期余额
dws attendance vacation save-balance --target user001 \
--leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --num 8 --reason "年度发放" \
--start 2024-01-01 --end 2024-12-31
Flags:
--target string 目标员工工号(必填)
--leave-code string 假期编码(必填)
--num string 余额数量,如8天传8,7.5天传7.5(必填)
--reason string 变更原因,最长100字符(必填)
--start string 有效期开始日期 YYYY-MM-DD(可选)
--end string 有效期结束日期 YYYY-MM-DD(可选)
--user-say-yes 用户已确认,跳过交互式确认提示
Agent 调用时传 true 前必须完成用户二次确认
```
**重要:这是设置(SET)接口,传入的值会替换当前余额,而非增加(ADD)。**余额数量在传递给 MCP 时会自动乘以 100(如 8 天传 800)。执行前会展示待写入数据,需用户确认后提交。
### 查询指定员工的签到记录
```
Usage:
dws attendance checkin records [flags]
Example:
dws attendance checkin records \
--operator-staff-id op001 --staff-ids user001,user002 --start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00"
Flags:
--end string 结束时间, 格式 yyyy-MM-dd HH:mm:ss(必填)
--operator-corp-id string 操作者企业 ID(必填)
--operator-staff-id string 操作者员userID(必填)
--staff-ids string 目标员工userID 列表, 逗号分隔(必填),员工数最多100个人
--start string 开始时间, 格式 yyyy-MM-dd HH:mm:ss(必填),开始到结束时间限制在7天
```
调用 MCP 工具 get_checkin_record 查询指定员工在一段时间内的签到记录。权限说明:Boss/超级管理员可查看全公司员工,子管理员可查看管理范围内员工,部门主管可查看所管理部门员工,普通员工只能查询自己。接口单次最多返回100条签到记录。
返回结构:`result.list` 为签到记录数组,每条记录包含以下字段(按 sortedProps 顺序):
- `corpId`(string):企业 ID
- `name`(string):用户名称
- `userId`(string):用户 ID
- `timestamp`(number):签到时间
- `place`(string):签到地点
- `detailPlace`(string):签到详细地点
- `longitude`(number):签到地点经度
- `latitude`(number):签到地点纬度
- `remark`(string):备注信息
- `imageList`(string[]):图片列表
- `customers`(string):拜访客户
- `checkinType`(string):签到类型
- `mobileId`(string):设备 ID
顶层还包含 `success`(boolean)和 `arguments`(object[])字段。
@@ -0,0 +1,10 @@
# 严格约束
## 严格约束
- 不要凭历史记忆复用 userId / classId / leaveCode / groupId / instanceId 等任何 ID,每次必须从当次命令返回值中提取
- 不要猜测命令,先查询明确命令,不要使用记忆中未经证实(即不存在于本.md文件中的指令)或执行失败(指令传参有误或flag不正确)的的命令
- 制定 plan 并自我审查,严格按 plan 执行
- 涉及超过 3 条记录的聚合(求和、分组、计数、排序、跨字段计算)时必须落 Python 脚本处理,禁止用大模型口算或目测。脚本里如果用到 mcp,先提前看下 mcp 返回的结构,避免执行异常
- 遇到时长字段时,注意区分单位是秒、分钟还是小时
- 遇到意图不清晰的场景不要猜测,主动询问用户明确意图
- 如果查询结果很多时,不要自作主张省略,必须明确告知用户或者用表格或展示所有。
@@ -0,0 +1,13 @@
# 上下文传递表
## 上下文传递表
| 操作 | 提取 | 用于 |
|------|------|------|
| `contact user get-self` | `userId` | summary 的 --user |
| `rules` | `groupId` | schedule import 的 --group-id |
| `schedule import` | `classId` | schedule import 的 schedules 中的 classId |
| `contact user search` | `userId` | schedule import/get 的 userId |
| `contact user get-self` / `contact user search` | `userId` | summary 的 --user, vacation records 的 --user;selfsetting get/save 的 --user(必填) |
| 当前登录上下文 | `corpId`, `opUserId` | selfsetting get/save 自动补齐 MCP 入参, CLI 不需要传 `--corp-id` / `--op-user` |
| `vacation types` | `leaveCode` | vacation balance 的 --leave-code, vacation records 的 --leave-code |
@@ -0,0 +1,11 @@
# 考勤能力索引
| 主题 | 详见 |
|---|---|
| 日期范围计算、签到与打卡分流 | [`attendance-routing.md`](./attendance-routing.md) |
| 打卡/记录/排班/补卡/加班等核心命令 | [`attendance-commands.md`](./attendance-commands.md) |
| 排班规则/班次/排班表 | [`attendance-schedule.md`](./attendance-schedule.md) |
| 报表/统计/查询 | [`attendance-report.md`](./attendance-report.md) |
| 假期/调休/余额 | [`attendance-vacation.md`](./attendance-vacation.md) |
| 意图、工作流与上下文 | [`attendance-intent.md`](./attendance-intent.md) · [`attendance-workflow.md`](./attendance-workflow.md) · [`attendance-context.md`](./attendance-context.md) |
| 注意事项、脚本与严格约束 | [`attendance-notes.md`](./attendance-notes.md) · [`attendance-scripts.md`](./attendance-scripts.md) · [`attendance-constraints.md`](./attendance-constraints.md) |
@@ -0,0 +1,62 @@
# 意图判断
## 意图判断
用户说"签到记录/签到数据/签到明细/外勤签到/导出签到" → `checkin records`(查询签到记录)。如果用户意图是**导出签到报表/签到Excel**,则走下方的签到报表导出工作流。
- **优先级**:只要用户句中含"签到"二字,就走本条,不要走 `check record`。"签到" ≠ "打卡",签到是外勤场景的独立功能
用户说"打卡记录/出勤/考勤" → `check record`
用户说"指定用户打卡结果/考勤结果/迟到早退/缺卡异常" → `check result`
用户说"指定用户打卡流水/打卡详情/打卡时间地点/打卡记录详情" → `check record`
用户说"审批单/请假记录/加班记录/出差记录/补卡记录" → `approve list`
用户说"查询排班记录/获取排班详情/查看排班/排班表/导出排班/导出排班表/排班导出/XX考勤组的排班" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班查询导出工作流」执行**。
- **优先级**:只要用户句中含"排班"二字且意图是查看/导出,就走本条,不要走 `attendance-report.md`。"导出排班表" ≠ "导出考勤报表"
- **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule get` 命令
- 脚本自动处理:分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、排班表格式 Excel 输出
用户说"排班/导入排班/安排排班/设置排班/安排班次/调班/换班/排休" → **必须先 `read_file` 读取 [attendance-schedule.md](./attendance-schedule.md) 后按其中的「排班导入工作流」执行**。
- **严禁**绕过 `attendance-schedule.md` 直接调用 `dws attendance schedule import` 命令
- **严禁**仅凭命令 `--help` 或本文件中的命令参考自行组装排班命令
- 该文档定义了:考勤组类型校验(必须为 TURN)、班次校验(必须属于该考勤组)、排班回显确认、错误处理等约束,缺一不可
- 违反约束的后果:排错班次、排错人员、排班数据覆盖无法回退
用户说"班次定义/班次列表/有哪些班次/我负责的班次" → `class search`(返回结果已包含全量属性,无需再调 get)
用户说"班次详情/某个班次的具体信息" → `class search --name "..."`(search 直出,直接返回详情)。`class get` 仅在需要按已知 classId 精确查询时使用
用户说"更新班次/修改班次/班次改名/修改上下班时间" → `class update`
用户说"补卡规则/补卡设置" → `adjustment search`(返回结果已包含全量属性,无需再调 get)
用户说"补卡规则详情/某条补卡规则的具体信息" → `adjustment search --name "..."`(search 直出)。`adjustment get` 仅在需要按已知 adjustmentId 精确查询时使用
用户说"加班规则/加班设置/加班计算" → `overtime search`(返回结果已包含全量属性,无需再调 get)
用户说"加班规则详情/某条加班规则的具体信息" → `overtime search --name "..."`(search 直出)。如需查已删除/被覆盖的历史记录 → `overtime get`
用户说"考勤组列表/有哪些考勤组" → `group search`
用户说"考勤组详情/全量考勤组信息" → `group get`,若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids <userId>`(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId
用户说"考勤组成员/打卡地址/打卡wifi/打卡蓝牙" → `group filtered-get`(按需查询,节省成本),若返回结果中含成员 userId 列表,则对每个 userId 调用 `dws contact user get --user-ids <userId>`(或等价通讯录查询),在最终输出中展示员工姓名而非裸 userId
用户说"更新考勤组成员/添加考勤人员/删除考勤人员/添加考勤部门/删除考勤部门/加入考勤组/移出考勤组/设置无需考勤/取消无需考勤" → `group update-members`
用户说"修改考勤组/更新考勤组配置/考勤组改名/改变考勤组绑定的班次/修改打卡范围/设置考勤组负责人" → `group update`
用户说"创建考勤组/新建考勤组/添加考勤组" → `group create`
用户说"查询某人的考勤汇总/考勤统计/周统计/月统计" → `summary`
用户说"考勤组/考勤规则/打卡规则" → `rules`
用户说"查询个人规则设置/查看打卡提醒/查看极速打卡/查看缺卡提醒/查看打卡结果通知/查看个人考勤统计通知/查看团队考勤统计通知" → `selfsetting get`
用户说"更新个人规则设置/保存打卡提醒/修改极速打卡/关闭缺卡提醒/开启打卡结果通知/设置个人考勤统计通知/设置团队考勤统计通知" → `selfsetting save`
用户说"考勤字段/考勤列" → `report columns`
用户说"考勤数据/查询考勤报表数据" → `report query-data`(单次查询场景,非导出)
**导出签到记录/签到报表/签到数据导出/签到明细/外勤签到导出** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行(报表类型为"签到报表")**。
- 触发关键词:签到记录导出、签到报表、签到数据、签到明细、外勤签到
- 数据来源为 `attendance checkin records`(签到接口),非 `report query-data`
- **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_checkin.py`
**导出考勤/导出报表/生成考勤报表/出勤汇总导出/考勤明细导出/迟到早退统计导出/全员考勤数据导出/月度考勤报表/考勤表格/考勤 Excel** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行**。
- **排除**:如果用户说的是"导出**排班**表"/"导出**排班**"/"**排班**导出",这属于**排班查询导出**,应路由到 [attendance-schedule.md](./attendance-schedule.md),而非本条。判断标准:句中含"排班"二字 → 走排班;不含"排班"或明确说"考勤报表/考勤数据/出勤统计" → 走报表。
- **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_*.py` 任何脚本
- **严禁**仅凭脚本 `--help` 或本文件"自动化脚本"表格里的脚本路径就推断参数自行组装命令
- 该文档定义了:报表类型默认值、列选择策略(`--column-keywords`)、阶段 1 人员获取流程、错误处理、输出摘要规范,缺一不可
- 违反约束的后果:报表数据不全、列错位、人员遗漏、用户得到错误结果
**导出补卡记录/导出请假记录/导出出差记录/导出外出记录/补卡导出/请假导出/出差导出/外出导出/考勤审批记录导出/请假明细/补卡明细/出差明细/外出明细** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行(报表类型为"考勤记录")**。
- 触发关键词:补卡记录、请假记录、出差记录、外出记录、审批记录导出、请假明细导出、补卡明细导出
- 判断标准:句中含"补卡/请假/出差/外出"且含"记录/导出/明细/报表" → 走 attendance-report.md 的"考勤记录"类型
- **严禁**绕过 `attendance-report.md` 直接调用 `python scripts/attendance_report_record.py`
- 该文档定义了:考勤记录子类型(leave/trip/out/patch)选择策略、阶段 1 人员获取流程、阶段 3 脚本调用规范
用户说"假期数据/年假/病假/请假记录" → `report query-leave`
用户说"假期/我的假期/假期规则" → `vacation types`
用户说"病假余额/年假余额/事假剩余假期"等查询指定假期规则的余额 → `vacation balance`
用户说"导出假期余额/假期余额列表/所有假期规则余额/假期余额 Excel/年假病假调休余额导出"等全部假期规则余额的查询 → **必须先 `read_file` 读取 [attendance-vacation.md](./attendance-vacation.md) 后按其中工作流执行**
用户说"假期变更/假期记录/请假扣减" → `vacation records`
用户说"更新假期规则/修改假期类型/编辑假期规则" → `vacation update-type --leave-code <LEAVE_CODE>`
用户说"设置假期余额/调整假期额度/更新假期余额" → 先调用 `vacation balance` 获取当前余额,计算修改后的值,再调用 `vacation save-balance`
用户说"增加假期余额/发放年假/给员工加年假" → 先调用 `vacation balance` 获取当前余额,加上要增加的天数,再调用 `vacation save-balance` 设置新总额度
用户说"签到/签到记录" → `checkin records`
@@ -0,0 +1,107 @@
# 注意事项
## 注意事项
**Agent 使用引导**:
- 执行 vacation 子命令前,**必须先调用** `dws attendance vacation --help` 查看完整子命令列表和参数说明
- 新增命令可能不在 Agent 缓存中,直接猜测命令会失败
- 正确流程:查看帮助 → 选择命令 → 查看命令详细参数(如 `dws attendance vacation update-type --help`)→ 执行
- `record get` 的 `--date` 格式: YYYY-MM-DD(如 `2026-03-08`),CLI 自动转换为毫秒时间戳
- `shift list` 查询班次信息,`--start/--end` 使用 YYYY-MM-DD 格式,间隔不超过 7 天
- `schedule import` 导入排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流执行
- `schedule get` 查询排班记录 — 必须通过 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流执行(脚本自动分批、姓名转换、排班表格式导出)
- `schedule import` 是写操作,AI 调用时必须先展示导入摘要并引导用户二次确认;用户明确确认后才允许执行。用户明确要求跳过确认或命令包含全局 `--yes` 时,可跳过二次确认
- `class search` 所有参数均为可选,不填时返回全部可管理班次(默认第 1 页,每页 20 条)
- **概念区分**:班次是员工当天打卡安排;排班是为排班制考勤组导入的排班记录;班次定义是考勤管理员创建的工作时间规则
- `class get` 的 `--class-id` 必填,班次 ID 可从 `class search` 结果中提取
- `class search` 返回结果已包含全量属性,无需再调用 `class get`;`class get` 仅在需要按已知 classId 精确查询时使用
- `class update` 的 `--class-id` 必填,其余均可选,仅需对要修改的字段赋值,未传字段会自动从已有配置补充;由于保存班次耗时较久,建议加 `--timeout 10`
- `adjustment search` 返回结果已包含全量属性,无需再调用 `adjustment get`;`adjustment get` 仅在需要按已知 adjustmentId 精确查询时使用
- `overtime search` 返回结果已包含全量属性,无需再调用 `overtime get`;`overtime get` 仅在需要按已知 overtimeId 查询时使用(包括已删除/被覆盖的历史记录)
- `adjustment search` / `overtime search` 分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20
- `group search` 的分页字段为 `--page` 和 `--limit`,不传时自动使用默认值 1 / 20
- `group get` 的 `--group-id` 必填,返回考勤组全量字段;如仅需成员/地址/蓝牙/Wifi,优先使用 `group filtered-get` 节省成本。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids <userId1>,<userId2>,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。**
- `group update-members` 的 --group-id 必填,其余参数均可选,但至少需传一个变更项;各参数每次最多 20 个 ID;`--add-extra-users` 和 `--remove-extra-users` 操作的是"无需考勤"豁免名单,不影响考勤组主成员列表
- `group update` 的 --group-id 必填,其余均可选,至少需指定一个修改项;仅需对要修改的字段赋値,未传字段会从已有配置自动补充;修改打卡地址/wifi/蓝牙等复杂子对象时用 `--group-vo` 传入完整 JSON;`--group-vo` 与单字段 flag 同时传入时单字段 flag 优先级更高
- `group create` 的 `--name` 和 `--type` 必填,`--type` 必须为 FIXED/TURN/NONE 之一;type=FIXED 时 `--group-vo` 必须包含 `workDayClassList`(非空)和 `defaultClassId`(非 null);由于保存考勤组耗时较久,建议加 `--timeout 10`
- `group filtered-get` 的 `--group-id` 必填,`--member/--position/--wifi/--bles` 均可选,默认 false。**返回结果中如含成员 userId 列表,必须调用 `dws contact user get --ids <userId1>,<userId2>,...`(支持逗号分隔传多个 ID),将 userId 转换为员工姓名后再输出;不得直接输出裸 userId。**
- `summary` 必须同时传 `--user`、`--date`、`--stats-type`(`week` 周统计 / `month` 月统计);`--date` 支持 `YYYY-MM-DD` 或 `YYYY-MM-DD HH:mm:ss`
- `rules` 的 `--date` 支持 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss 两种格式
- `selfsetting get/save` 的 `--setting-scene` 必须是 `checkRemind`、`fastCheck`、`checkResultNotify`、`lackRemind`、`personalAttendStatNotify`、`bossAttendStatNotify` 之一
- `selfsetting get/save` 的 MCP 入参 `userId` 为必填;CLI 的 `--user` 也必填,必须显式传入目标用户 ID
- `selfsetting save` 必须传入与 `--setting-scene` 对应的至少一个设置字段;不同场景的字段不能混用
- `selfsetting save` 是敏感写操作,必须先执行 `selfsetting get` 查询当前值,并向用户展示目标用户、设置场景、修改字段、“当前值 → 新值”和最终命令参数摘要,等待用户明确确认;确认后才允许追加全局 `--yes` 执行保存。禁止未经确认直接执行或自动添加 `--yes`
- `selfsetting get/save` 不需要传 `--corp-id` / `--op-user`,`corpId` 和 `opUserId` 由当前登录上下文自动补齐
- `report columns` 无需额外参数,corpId 和 operatorId 由系统自动传入
- `report query-data` 和 `report query-leave` 的 `--start/--end` 格式: yyyy-MM-dd HH:mm:ss,间隔不超过 32 天,最多 20 人
- report 系列接口仅对管理员开放
- 用户 ID 需从 `contact user get-self` 或 `aisearch person` 获取
- 考勤组 ID 需从 `rules` 命令返回结果中获取
- `vacation types` 无需任何参数,认证信息自动注入
- `vacation balance` 的 `--users` 为目标员工 ID 列表,逗号分隔;`--leave-code` 选填,可通过 `vacation types` 获取
- `vacation records` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳;`--leave-code` 选填
- `vacation balance` 和 `vacation records` 的认证参数(corpId、opUserId)由系统自动注入,无需手动传入
- `vacation update-type` 的 `--leave-code` 必填;其他字段均为可选,但至少需传一个更新字段
- `vacation update-type` 的 `--visibility-rules` 为 JSON 数组字符串,**HSF 反序列化无法区分「未传」与「空数组 `[]`」**,所以约定了哨兵语义:
- 不传 → 不修改可见范围
- `[{"type":"dept","visible":["-1"]}]` → **哨兵**,改为全公司可见(服务端落库为空)
- `[{"type":"staff|dept|label|employee_type","visible":["id1",...]}, ...]` → 改为指定范围
- 空数组 `[]`、`[{}]`、`visible` 为空等无效写法 → **CLI 报错**(服务端会静默忽略,故在 CLI 提前拦截)
- **「清空可见范围」必须显式传哨兵值 `["-1"]`,不能用 `[]`**
- `vacation save-balance` 是 **SET 接口**而非 ADD 接口:传入值会直接替换当前余额,而非累加
- `vacation save-balance` 的 `--num` 输入为实际天数(如 8 或 7.5),内部会乘以 100 传给 MCP(如 800 或 750)
- `vacation save-balance` 执行前需先调用 `vacation balance` 查询当前余额,再计算新值,避免误操作
- `vacation save-balance` 的 `--start/--end` 使用 YYYY-MM-DD 格式,CLI 自动转换为毫秒时间戳
- `vacation update-type` 和 `vacation save-balance` 执行前会展示待写入数据,需用户输入 yes/y 确认后提交
- 假期编码为 UUID 格式字符串,可通过 `vacation types` 命令查询获取
### 改签打卡记录
**命令**: `dws attendance boss-check`
**功能**: 改签打卡记录,管理员可修改员工的打卡时间、打卡结果等信息。
**强制执行流程**: 此命令为写操作,Agent 调用时必须遵守以下流程:
1. 先向用户展示待执行操作的完整参数摘要
2. 展示变更摘要并等待用户明确确认
3. 用户确认后,再传 `--user-say-yes=true` 执行命令
4. **禁止**未经用户确认直接执行或自动添加 `--user-say-yes=true`
**参数**:
| 参数 | 必填 | 说明 | 来源 |
|------|------|------|------|
| --plan-id | y* | 排班ID(与 --result-id 二选一) | `dws attendance schedule get` 返回的 `id` 字段 |
| --result-id | y* | 打卡结果ID(与 --plan-id 二选一,优先使用) | **暂不支持**(record get 未返回此字段) |
| --time | n | 新打卡时间,格式 yyyy-MM-dd HH:mm | - |
| --result | n | 打卡结果枚举值 | - |
| --absent-min | n | 缺勤时长(分钟) | - |
| --remark | n | 备注,最长500字符 | - |
| --user-say-yes | n | 用户已确认,跳过交互式确认提示 | - |
**获取 planId 步骤**:
1. 查询排班记录:`dws attendance schedule get --userIdList USER_ID --workDateBegin DATE --workDateEnd DATE`
2. 从返回结果中找到对应打卡类型(OnDuty=上班,OffDuty=下班)的记录
3. 使用该记录的 `id` 字段作为 `--plan-id` 参数
4. 示例返回:`{"id": 948964045503, "checkType": "OffDuty", ...}` → `--plan-id 948964045503`
**打卡结果枚举值**:
- Normal: 正常
- TimesResultA: 迟到
- TimesResultB: 早退
- TimesResultC: 缺卡
- TimesResultD: 迟到+早退
- TimesResultE: 缺卡+早退
- TimesResultF: 迟到+缺卡
**示例**:
```bash
# 步骤1:获取排班记录的 planId
dws attendance schedule get --userIdList 03642229451220076 --workDateBegin 2026-05-13 --workDateEnd 2026-05-13 -f json
# 步骤2:使用返回的 id 作为 --plan-id 改签
# 假设返回 id: 948964045503 (OffDuty 下班打卡)
dws attendance boss-check --plan-id 948964045503 --result Normal --user-say-yes
# 同时修改打卡时间
dws attendance boss-check --plan-id 948964045503 --time "2026-05-13 18:00" --result Normal --yes
```
@@ -11,7 +11,7 @@
1. 未按"阶段 1"做人员解析 → `--users` 传入部门 ID 而非员工 userId,脚本虽内置回退但会浪费一次失败的接口调用
2. 未按"阶段 0"判断报表类型 → 用户说"汇总"被理解成"明细",导致输出粒度错误
3. 未按"列选择"判断是否传 `--column-keywords` → 用户要"迟到情况报表"被输出成全字段默认报表
4. 未按"错误处理"规则处理无权限错误(errorCode 6001「无权限操作」/ `AUTH_ERROR`)/ `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户
4. 未按"错误处理"规则处理 403 / `HSF_ILLEGALPARAMS` → 把环境错误当成业务错误反馈给用户
5. 未按"阶段 4"返回结果 → 把 Excel 内容贴在对话里,或者裸 userId 直接输出
**执行前自检(必须能在心中回答)**:
@@ -22,7 +22,7 @@
如果上述任何一项答不出,**回到本文档对应章节重新阅读**,禁止凭记忆/想象组装命令。
**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回业务权限错误(`server_error_code` 为 `AUTH_ERROR`、errorCode `6001`「无权限操作」),**不是 HTTP 403**。提示需要管理员权限,不要重试。
**前提**:当前用户必须是钉钉管理员,否则 report 系列接口返回 403 权限错误。
## 核心原则
@@ -46,7 +46,7 @@ Agent 解析用户意图(报表类型、人员范围、时间范围、关注
- 所有 `dws` 命令必须携带 `--format json`
- 时间参数 `--start` / `--end` 格式必须为 `yyyy-MM-dd HH:mm:ss`
- 字段 ID 与字段名的映射必须从 `report columns` 实时建立,禁止硬编码
- 任何接口失败(含无权限错误 6001/`AUTH_ERROR`)必须向用户清晰报错,禁止静默吞掉
- 任何接口失败(含 403)必须向用户清晰报错,禁止静默吞掉
## 涉及工具
@@ -102,10 +102,10 @@ Agent 解析用户意图(报表类型、人员范围、时间范围、关注
| 用户说 | 应路由到 |
|--------|---------|
| "今天打卡了吗" | `dws attendance record get`(单次查询,非报表) |
| "帮我排班" | [attendance-schedule.md](./attendance-schedule.md) 排班工作流(排班,非报表) |
| "帮我排班" | `dws attendance schedule import`(排班写入,非报表;执行前确认) |
| "我的假期还剩多少" | `dws attendance vacation balance`(单次查询) |
| "帮我请假" | `dws oa`(审批流程,非报表) |
| "我这个月的考勤怎么样" | `dws attendance summary`(个人统计,非报表) |
| "我这个月的考勤怎么样" | `dws attendance summary --user <USER_ID> --date <YYYY-MM-DD> --stats-type month`(个人统计,非报表) |
## 工作流
@@ -246,7 +246,7 @@ python scripts/attendance_report_checkin.py \
- 脚本依赖 `openpyxl`,若未安装需先 `pip install openpyxl`
- 脚本摘要输出到 stdout,进度日志输出到 stderr
- 首次调试可加 `--inspect` 参数查看首条记录原始结构(`attendance_report_detail.py` / `attendance_report_monthly.py` / `attendance_report_daily.py` / `attendance_report_checkin.py` 支持;**`attendance_report_record.py` 无 `--inspect` 参数**)
- 首次调试可加 `--inspect` 参数查看首条记录原始结构
- 脚本执行失败(exit ≠ 0)时,stderr 中有具体错误信息
### 阶段 4: 返回结果给用户
@@ -488,11 +488,11 @@ python scripts/attendance_report_checkin.py \
| 错误 | 原因 | 处理方式 |
|------|------|---------|
| 权限错误(errorCode 6001「无权限操作」/ `AUTH_ERROR`,非 HTTP 403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 |
| 权限错误(403) | 当前账号非管理员 | 提示需要管理员权限,不要重试 |
| userId 无效 | 用户 ID 错误或已离职 | 脚本跳过并在摘要中标注 |
| 时间区间超长 | 接口可能性能不佳 | 提示"超过 1 年的数据建议分阶段导出" |
| openpyxl 未安装 | 环境缺包 | 输出 `pip install openpyxl` 安装提示 |
| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户,可加 `--inspect` 重试(`attendance_report_record.py` 不支持 `--inspect`) |
| 脚本执行失败 | 接口异常/配置问题 | 将 stderr 错误信息转告用户,可加 `--inspect` 重试 |
## 使用示例
@@ -611,9 +611,9 @@ python scripts/attendance_report_checkin.py \
| 脚本 | 报表类型 | 数据来源 | CLI 参数 |
|------|---------|---------|---------|
| [attendance_report_detail.py](../../scripts/attendance_report_detail.py) | 明细 | `check result` + `check record` | `--users --start --end [--out]` |
| [attendance_report_monthly.py](../../scripts/attendance_report_monthly.py) | 月度汇总(默认) | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` |
| [attendance_report_daily.py](../../scripts/attendance_report_daily.py) | 每日统计 | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` |
| [attendance_report_record.py](../../scripts/attendance_report_record.py) | 考勤记录 | `attendance approve list` + `oa approval detail` | `--type --users --start --end [--out]` |
| [attendance_report_checkin.py](../../scripts/attendance_report_checkin.py) | 签到报表 | `attendance checkin records` | `--users --start --end [--out]` |
| [attendance_report_common.py](../../scripts/attendance_report_common.py) | 公共模块(不可单独执行) | — | — |
| [attendance_report_detail.py](../scripts/attendance_report_detail.py) | 明细 | `check result` + `check record` | `--users --start --end [--out]` |
| [attendance_report_monthly.py](../scripts/attendance_report_monthly.py) | 月度汇总(默认) | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` |
| [attendance_report_daily.py](../scripts/attendance_report_daily.py) | 每日统计 | `report columns` + `report query-data` | `--users --start --end [--column-keywords] [--out]` |
| [attendance_report_record.py](../scripts/attendance_report_record.py) | 考勤记录 | `attendance approve list` + `oa approval detail` | `--type --users --start --end [--out]` |
| [attendance_report_checkin.py](../scripts/attendance_report_checkin.py) | 签到报表 | `attendance checkin records` | `--users --start --end [--out]` |
| [attendance_report_common.py](../scripts/attendance_report_common.py) | 公共模块(不可单独执行) | — | — |
@@ -0,0 +1,55 @@
# 日期范围与签到优先路由
> **【必读】日期范围严格计算规则 — 所有含 --start/--end 或 --from/--to 的命令均适用**
>
> **禁止随意猜测日期范围,必须按以下规则精确计算后再组装命令参数:**
>
> | 用户表达 | --start(含)| --end(含)| 说明 |
> |---------|------------|------------|------|
> | 本周 / 这周 / 当周 | 本周**周一** | 本周**周日** | 周一为一周的第一天,周日为最后一天 |
> | 上周 / 上一周 | 上周**周一** | 上周**周日** | 同上,往前推一周 |
> | 本月 / 这个月 / 当月 | 本月 **1 日** | 本月**最后一天**(28/29/30/31)| 必须计算当月实际天数 |
> | 上月 / 上个月 | 上月 **1 日** | 上月**最后一天** | 同上,往前推一个月 |
> | 今天 / 今日 | **今天** | **今天** | start == end |
> | 昨天 | **昨天** | **昨天** | start == end |
>
> **必须遵守:**
> 1. **本周一定是周一到周日**,严禁将"本周"解释为周一到周五或其他任意范围。
> 2. **本月一定是 1 日到该月实际最后一天**,严禁将"本月"截断为 1 日到 28 日或其他固定天数。
> 3. 计算日期时必须参考当前系统时间,不得使用硬编码或模糊估算。
> 4. 如果用户指定了具体日期范围(如"4月1日到4月15日"),直接使用用户给定的范围,不套用上述规则。
> 5. 日期格式统一按各命令要求:`shift list` / `check result` 等使用 `YYYY-MM-DD`;`report query-data` / `report query-leave` 等使用 `yyyy-MM-dd HH:mm:ss`(start 取当天 `00:00:00`,end 取当天 `23:59:59`)。
## 签到(checkin)— 优先路由
> **只要用户句中含"签到"二字(签到记录/签到数据/签到明细/外勤签到/导出签到/签到报表),就走本节,不要走 `check record`(打卡流水)。"签到" ≠ "打卡",签到是外勤场景的独立功能。**
### 导出 vs 查询 判断规则(必须先判断)
| 用户说 | 路由 |
|--------|------|
| 含"导出"/"报表"/"Excel"/"表格"/"生成"中**任何一个** + 含"签到" | → **导出签到报表**(走脚本) |
| 不含上述导出关键词,仅"查签到"/"签到记录"/"看看签到" | → **查询签到记录**(走命令) |
> **示例**:"帮我导出最近一个月的签到记录" → 含"导出" → 走报表脚本
> "查一下张三的签到记录" → 不含导出关键词 → 走查询命令
- **导出签到报表** → **必须先 `read_file` 读取 [attendance-report.md](./attendance-report.md) 后按其中的工作流执行(报表类型为"签到报表")**,对应脚本 `attendance_report_checkin.py`
- **严禁**绕过 `attendance-report.md` 直接调用脚本
- **严禁**自己手动分段调用 `dws attendance checkin records` 来拼数据,必须用脚本
- **查询签到记录**(无需导出,仅查看) → `dws attendance checkin records`(数据源为 MCP 工具 `get_checkin_record`)
签到记录返回字段(`result.list` 数组,按 sortedProps 顺序):
- `corpId`(string):企业 ID
- `name`(string):用户名称
- `userId`(string):用户 ID
- `timestamp`(number):签到时间
- `place`(string):签到地点
- `detailPlace`(string):签到详细地点
- `longitude`(number):签到地点经度
- `latitude`(number):签到地点纬度
- `remark`(string):备注信息
- `imageList`(string[]):图片列表
- `customers`(string):拜访客户
- `checkinType`(string):签到类型
- `mobileId`(string):设备 ID
@@ -52,7 +52,7 @@ Agent 解析用户意图(考勤组、员工、日期范围、班次安排)
- 禁止在未确认考勤组类型为排班制(TURN)的情况下执行排班
- 禁止在班次未经校验的情况下执行排班
- 禁止跳过用户确认直接执行排班
- **禁止在未向用户展示完整排班明细表格(含每天的班次名称)的情况下弹出确认卡片**。用户必须先看到"谁、哪天、上什么班"才能做出确认决策
- **禁止在未向用户展示完整排班明细表格(含每天的班次名称)的情况下请求确认**。用户必须先看到"谁、哪天、上什么班"才能做出确认决策
- 禁止编造任何字段值或用户姓名
- 禁止直接输出裸 userId,脚本已内置 userId → 姓名转换
- **禁止直接输出裸 classId 数字**,必须展示班次名称(如"早班"),用户不理解 classId 是什么
@@ -118,7 +118,7 @@ dws attendance rules --date <今天日期> --format json
如果是给指定员工排班,先查该员工的 userId,再查其考勤组。
**步骤 0b — 查询考勤组详情和关联班次(必须在 ask_question 之前完成)**:
**步骤 0b — 查询考勤组详情和关联班次(必须在收集班次选择前完成)**:
```bash
# 获取考勤组详情(含绑定的班次列表)
@@ -132,7 +132,7 @@ dws attendance group get --group-id <groupId> --format json
- 考勤组类型:`groupVO.type`(必须为 TURN)
- 关联班次列表:`groupVO.shiftVOList[].shiftSetting.{shiftId, shiftName}`
**步骤 0c — 收集缺失参数(ask_question 卡片交互)**:
**步骤 0c — 收集缺失参数**:
拿到考勤组和关联班次后,再向用户收集缺失的参数。排班所需的四个参数:
- **考勤组**:已在 0a 自动获取,无需询问
@@ -140,59 +140,12 @@ dws attendance group get --group-id <groupId> --format json
- **员工范围**(必填):指定员工姓名 / 部门 / 考勤组全员 / "给我排班"
- **日期范围**(必填):具体日期 / 日期范围(如"下周"、"5月19日到5月23日")
**只收集真正缺失的参数**,用户已经提供的不要重复询问。将缺失参数**合并到一次 `ask_question` 调用中**。
示例:用户说"帮我排班",Agent 应先自动查询考勤组和关联班次(步骤 0a + 0b),然后只询问日期范围和班次:
```
// ===== 步骤 0a + 0b 已完成,此时你已经拿到了以下真实数据 =====
// groupId = 实际的考勤组ID
// groupName = 实际的考勤组名称
// shiftVOList = 考勤组关联的班次列表(来自 dws attendance group get 的返回)
// 从 shiftVOList 构建班次选项(伪代码):
shiftOptions = []
for each shift in groupVO.shiftVOList:
shiftOptions.append({ id: String(shift.shiftSetting.shiftId), label: shift.shiftSetting.shiftName })
shiftOptions.append({ id: "rest", label: "排休" })
// 如果考勤组只关联了一个班次,直接使用,不需要询问用户
if shiftVOList.length == 1:
selectedShift = shiftVOList[0] // 自动选定,跳过班次选择
// 构建日期选项(必须填入实际计算的日期):
todayStr = 当天日期(YYYY-MM-DD)
thisWeekEnd = 本周日日期
nextWeekStart = 下周一日期
nextWeekEnd = 下周日日期
ask_question({
title: "排班参数确认",
questions: [
{
id: "date_range",
prompt: "请选择排班日期范围",
options: [
{ id: "this_week", label: "本周剩余时间(" + todayStr + "-" + thisWeekEnd + ")" },
{ id: "next_week", label: "下周(" + nextWeekStart + "-" + nextWeekEnd + ")" },
{ id: "custom", label: "自定义日期范围" }
]
},
{
id: "shift",
prompt: "请选择班次(以下为您考勤组「" + groupName + "」关联的班次)",
options: shiftOptions // 直接使用上面从 shiftVOList 动态构建的选项,严禁替换为任何硬编码值
}
]
})
```
**⚠️ 班次选项严禁硬编码**:上述伪代码中的 `shiftOptions` 必须在运行时从 `shiftVOList` 动态生成。禁止在 `ask_question` 中写入任何固定的班次名称(如"早班"、"晚班"、"正常班"、"全部排XX班"等)。如果你发现自己在 options 里手写班次名,说明你做错了。
**只收集真正缺失的参数**,用户已经提供的不要重复询问;多个缺失参数可集中询问。日期选项必须展示实际计算后的日期范围,班次选项必须在运行时从 `shiftVOList` 动态生成,并以真实 `shiftId` 作为值、真实 `shiftName` 作为展示名称;可额外提供“排休”。只有一个关联班次时直接使用,无需再次选择。
**动态选项原则(严格执行)**:
- 班次选项**必须且只能**来自考勤组的 `shiftVOList`,**严禁编造任何班次名称**(如"正常班"、"早班"、"晚班"、"全部排XX班"等都是编造)
- 每个班次选项的 `id` 必须是 `shiftVOList` 中的真实 `shiftId`,`label` 必须是真实的 `shiftName`
- 如果 `shiftVOList` 为空,降级从 `groupVO.classIds` + `class search` 按 ID 精确查询(不是全局搜索)
- 如果 `shiftVOList` 为空,降级从 `groupVO.classIds` + `class get` 按 ID 精确查询(不是全局搜索)
- **只收集缺失的参数**:用户已经提供的参数不要重复询问
- **如果考勤组只有一个班次,直接使用该班次,不需要询问用户选择**
- **禁止用 `class search` 全局搜索来给用户展示班次选项**——全局班次列表包含不属于该考勤组的班次,用户选了也会被校验拒绝
@@ -251,21 +204,21 @@ dws attendance group filtered-get --group-id <groupId> --member --format json
**禁止调用 `dws attendance class search` 全局搜索班次**——全局搜索会返回不属于该考勤组的班次,即使用户指定了班次名称,也必须在 `shiftVOList` 中匹配,而不是全局搜索。
**校验规则**:
1. 用户在 `ask_question` 卡片中选择的班次,其 `shiftId` 必须存在于 `shiftVOList` 中(卡片选项本身就是从 `shiftVOList` 构建的,所以天然满足)
1. 用户选择的班次 `shiftId` 必须存在于 `shiftVOList` 中;所有候选都必须由 `shiftVOList` 动态生成
2. 如果用户通过自然语言指定了班次名称(如"排早班"),必须在 `shiftVOList` 中**按名称模糊匹配**,找到对应的 `shiftId`
3. 如果用户指定的班次不在 `shiftVOList` 中,**必须拒绝**,并列出该考勤组关联的全部班次让用户重新选择
4. 仅当 `shiftVOList` 为空时,才降级从 `groupVO.classIds` + `dws attendance class get` 按 ID 精确查询(仍然不是全局搜索)
**提取信息**:班次 ID(`classId` / `shiftId`)、班次名称(`shiftName`)
### 阶段 4: 回显排班内容并使用 ask_question 卡片确认
### 阶段 4: 回显排班内容并获得明确确认
> **[硬性门禁]** 必须先用普通文本向用户展示**完整的排班明细表格**(包含每个人、每天、具体班次名称),用户看到排班明细后,才能弹出 `ask_question` 确认卡片。
> **禁止在用户还不知道"谁、哪天、上什么班"的情况下就弹确认卡片**——这等于让用户盲签,体验极差。
> **[硬性门禁]** 必须先向用户展示**完整的排班明细表格**(包含每个人、每天、具体班次名称),再获得用户明确确认。
> **禁止在用户还不知道"谁、哪天、上什么班"的情况下请求确认**。
**步骤 4a — 展示排班明细(必须在确认卡片之前)**:
**步骤 4a — 展示排班明细(必须在请求确认之前)**:
在调用 `ask_question` 之前,**必须先**用普通文本向用户展示排班内容。表格中**必须包含班次名称**(如"早班 09:00-18:00"),不能只展示 classId 数字:
请求确认前,**必须先**向用户展示排班内容。表格中**必须包含班次名称**(如"早班 09:00-18:00"),不能只展示 classId 数字:
```
排班预览
@@ -291,27 +244,9 @@ dws attendance group filtered-get --group-id <groupId> --member --format json
- [ ] 排休的记录标注了"排休"
- [ ] 表格涵盖了所有待排班的员工和日期
**步骤 4b — 使用 `ask_question` 卡片确认(必须在展示表格之后)**:
**步骤 4b — 获得明确确认(必须在展示表格之后)**:
```
ask_question({
title: "排班执行确认",
questions: [
{
id: "confirm_execute",
prompt: "以上排班将覆盖所选日期的现有排班记录,确认执行吗?",
options: [
{ id: "yes", label: "确认执行" },
{ id: "no", label: "取消" }
]
}
]
})
```
**处理用户选择**:
- 用户选择 **"确认执行"** → 进入阶段 5
- 用户选择 **"取消"** → 终止流程,提示"已取消排班操作"
说明排班将覆盖所选日期的现有记录,并等待用户明确确认。确认后进入阶段 5;用户取消或未明确同意时不执行导入。
### 阶段 5: 调用脚本执行排班
@@ -508,8 +443,8 @@ python scripts/attendance_schedule_import.py \
| 脚本 | 用途 | CLI 参数 |
|------|------|---------|
| [attendance_schedule_import.py](../../scripts/attendance_schedule_import.py) | 排班导入(含校验、回显、执行) | `--group-id --schedules --confirm` |
| [attendance_schedule_export.py](../../scripts/attendance_schedule_export.py) | 排班查询导出(分批查询、排班表 Excel) | `--users --start --end [--output]` |
| [attendance_schedule_import.py](../scripts/attendance_schedule_import.py) | 排班导入(含校验、回显、执行) | `--group-id --schedules --confirm` |
| [attendance_schedule_export.py](../scripts/attendance_schedule_export.py) | 排班查询导出(分批查询、排班表 Excel) | `--users --start --end [--output]` |
---
@@ -0,0 +1,21 @@
# 自动化脚本
## 自动化脚本
| 脚本 | 场景 | 用法 |
|------|------|------|
| [attendance_my_record.py](../scripts/attendance_my_record.py) | 查看我今天/指定日期的考勤记录 | `python attendance_my_record.py today` |
| [attendance_team_shift.py](../scripts/attendance_team_shift.py) | 查询团队成员本周排班 | `python attendance_team_shift.py --users userId1,userId2` |
| [attendance_report_common.py](../scripts/attendance_report_common.py) | 考勤报表导出公共模块(不可单独执行) | — |
| [attendance_vacation_balance.py](../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出 | **禁止直接调用**,必须先读 [attendance-vacation.md](./attendance-vacation.md) 按工作流执行 |
| attendance_report_detail.py | 考勤报表 — **明细粒度** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 |
| attendance_report_monthly.py | 考勤报表 — **月度汇总** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 |
| attendance_report_daily.py | 考勤报表 — **每日统计** | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 |
| attendance_report_record.py | 考勤报表 — **考勤记录**(补卡/请假/出差/外出) | **禁止直接调用**,必须先读 [attendance-report.md](./attendance-report.md) 按工作流执行 |
| attendance_schedule_import.py | 排班导入(含校验、回显、执行) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 |
| attendance_schedule_export.py | 排班查询导出(分批查询、排班表 Excel) | **禁止直接调用**,必须先读 [attendance-schedule.md](./attendance-schedule.md) 按工作流执行 |
> 说明:
> - `attendance_report_*.py` 四个脚本由 [attendance-report.md](./attendance-report.md) 工作流编排使用:detail/monthly/daily 自动处理 `--users` 超过 20 人分批、`--start/--end` 超过 32 天按月切片;record 自包含数据查询+解析+Excel 生成(补卡/请假/出差/外出审批记录)
> - `attendance_schedule_import.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班导入工作流编排使用,自动处理考勤组校验、班次校验、排班回显确认
> - `attendance_schedule_export.py` 由 [attendance-schedule.md](./attendance-schedule.md) 排班查询导出工作流编排使用,自动处理分批查询(超 20 人自动分批)、userId→姓名转换、classId→班次名称转换、输出排班表格式 Excel
@@ -137,4 +137,4 @@ python scripts/attendance_vacation_balance.py \
| 脚本 | 场景 | CLI 参数 |
|------|------|---------|
| [attendance_vacation_balance.py](../../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出;脚本按假期名称关键词筛选规则,并逐个 `leaveCode` 调用余额查询 | `--users [--leave-keywords] [--out] [--inspect]` |
| [attendance_vacation_balance.py](../scripts/attendance_vacation_balance.py) | 假期余额列表 Excel 导出;脚本按假期名称关键词筛选规则,并逐个 `leaveCode` 调用余额查询 | `--users [--leave-keywords] [--out] [--inspect]` |
@@ -0,0 +1,148 @@
# 核心工作流
## 核心工作流
```bash
# 导入排班记录
dws attendance schedule import --group-id 123456 \
--schedules '[{"userId":"user001","classId":123,"workDate":"2026-04-22","checkBeginTime":"09:00","checkEndTime":"18:00"}]' \
--yes --format json
# 获取排班记录 — 禁止直接调用,必须走 attendance-schedule.md 排班查询导出工作流
# python scripts/attendance_schedule_export.py --users user001,user002 --start 2026-04-01 --end 2026-04-30
# 查询可管理的班次列表
dws attendance class search --format json
dws attendance class search --query "早班" --filter-type MINE_OWN --format json
# 查询班次详情
dws attendance class get --class-id 1170996821 --format json
# 更新班次
dws attendance class update --class-id 1170996821 --name "新早班" --timeout 10 --format json
dws attendance class update --class-id 1170996821 --class-vo '{"sections":[{"times":[{"checkType":"OnDuty","checkTime":"08:30","across":0},{"checkType":"OffDuty","checkTime":"17:30","across":0}]}]}' --timeout 10 --format json
# 查询补卡规则
dws attendance adjustment search --page 1 --limit 20 --format json
dws attendance adjustment search --query "标准" --page 1 --limit 20 --format json
# 查询补卡规则详情
dws attendance adjustment get --adjustment-id 12345 --format json
# 查询加班规则
dws attendance overtime search --page 1 --limit 20 --format json
# 查询加班规则详情
dws attendance overtime get --overtime-id 12345 --format json
# 查询考勤组列表
dws attendance group search --query "研发" --page 1 --limit 20 --format json
dws attendance group search --type FIXED --page 1 --limit 20 --format json
# 查询考勤组全量信息
dws attendance group get --group-id 123456 --format json
# 按需查询考勤组成员/地址/蓝牙/Wifi
dws attendance group filtered-get --group-id 123456 --member --format json
dws attendance group filtered-get --group-id 123456 --position --wifi --format json
# 更新考勤组成员
dws attendance group update-members --group-id 123456 --add-users userId1,userId2 --timeout 10 --format json
dws attendance group update-members --group-id 123456 --remove-users userId1 --timeout 10 --format json
dws attendance group update-members --group-id 123456 --add-depts deptId1 --remove-users userId2 --timeout 10 --format json
# 更新考勤组配置
dws attendance group update --group-id 123456 --name "研发考勤组" --timeout 10 --format json
dws attendance group update --group-id 123456 --classIds '[1374234767]' --timeout 10 --format json
dws attendance group update --group-id 123456 --group-vo '{"positions":[{"title":"总部","address":"北京市","latitude":39.9,"longitude":116.4,"offset":200}]}' --timeout 10 --format json
# 创建考勤组
dws attendance group create --name "研发考勤组" --type FIXED --group-vo '{"defaultClassId":1170996821,"workDayClassList":[0,1170996821,0,0,0,0,0]}' --timeout 10 --format json
dws attendance group create --name "自由工时分组" --type NONE --timeout 10 --format json
# 查看考勤统计摘要
dws attendance summary --user <USER_ID> --date 2026-03-12 --stats-type week --format json
# 查看考勤组和规则
dws attendance rules --date 2026-03-14 --format json
# 查看指定用户的打卡提醒设置
dws attendance selfsetting get --setting-scene checkRemind --user <USER_ID> --format json
# 查看指定用户的极速打卡设置
dws attendance selfsetting get --setting-scene fastCheck --user <USER_ID> --format json
# 开启指定用户的打卡结果通知
dws attendance selfsetting save --setting-scene checkResultNotify --user <USER_ID> --check-result-msg 1 --format json
# 更新指定用户的极速打卡设置
dws attendance selfsetting save --setting-scene fastCheck --user <USER_ID> \
--onduty-check-type 3 --voice-remind-switch=true --format json
# 获取考勤字段列表(管理员)
dws attendance report columns --format json
# 根据字段查询考勤数据(管理员)
dws attendance report query-data --users userId1,userId2 \
--columns 1001,1002 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json
# 查询用户假期数据(管理员)
dws attendance report query-leave --users userId1,userId2 \
--leave-names 年假,病假 --start "2026-03-01 00:00:00" --end "2026-03-31 23:59:59" --format json
# 查看假期规则列表
dws attendance vacation types --format json
# 查看指定员工假期余额
dws attendance vacation balance --users userId1,userId2 --format json
# 查看指定员工某类假期余额
dws attendance vacation balance --users userId1 --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json
# 查看指定员工假期余额变更记录
dws attendance vacation records --user USER_ID --start 2026-04-01 --end 2026-04-22 --format json
# 更新假期规则名称
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--name "事假(修改版)" --format json
# 更新假期单位
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--unit hour --per-hours 8 --format json
# 改为指定部门可见
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--visibility-rules '[{"type":"dept","visible":["1","2","3"]}]' --format json
# 改为全公司可见(哨兵约定:必须显式传 "-1",不能传空数组 [])
dws attendance vacation update-type --leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--visibility-rules '[{"type":"dept","visible":["-1"]}]' --format json
# 设置员工假期余额完整流程
# 1. 查询当前余额
dws attendance vacation balance --users user001 \
--leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json
# 2. 根据查询结果计算新值(如当前5天,要设置为8天)
# 3. 执行设置(SET操作,会替换当前余额)
dws attendance vacation save-balance --target user001 \
--leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--num 8 --reason "年度发放" --start 2024-01-01 --end 2024-12-31 --format json
# 增加员工假期余额完整流程(ADD场景)
# 1. 查询当前余额(假设返回5天)
dws attendance vacation balance --users user001 \
--leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 --format json
# 2. 计算增加后的新值(5 + 3 = 8天)
# 3. 设置新总额度
dws attendance vacation save-balance --target user001 \
--leave-code a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--num 8 --reason "绩效奖励发放3天" --format json
# 查询签到记录
dws attendance checkin records --operator-staff-id op001 --staff-ids user001,user002 \
--start "2026-04-01 00:00:00" --end "2026-04-07 00:00:00" --format json
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
# attendance 局部意图消歧
本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|---|---|---|---|---|
| "查/提交 请假/加班/外出/出差/补卡 审批单" | 考勤业务审批单 | `attendance approve`(查询走 `attendance approve list`;提交走 `attendance approve templates --type leave\ | overtime\ | repair-check\ |
@@ -48,25 +48,7 @@ def get_my_user_id(dry_run: bool = False) -> Optional[str]:
return '<MY_USER_ID>'
if not data or not isinstance(data, dict):
return None
# 兼容两种结构:
# 1) 顶层直接给 userId
# 2) {result: [{orgEmployeeModel: {userId}}]} 包裹
uid = data.get('userId') or data.get('userid')
if uid:
return uid
inner = data.get('result')
if isinstance(inner, dict):
inner = [inner]
if isinstance(inner, list):
for item in inner:
if not isinstance(item, dict):
continue
emp = item.get('orgEmployeeModel')
if isinstance(emp, dict) and emp.get('userId'):
return emp['userId']
if item.get('userId'):
return item['userId']
return None
return data.get('userId') or data.get('userid')
def main():
@@ -3,7 +3,7 @@
考勤报表导出 — 签到记录粒度
[AI Agent 强制门禁] 调用本脚本前必须先阅读:
dingtalk-workspace/references/products/attendance-report.md
references/attendance-report.md
本脚本是"签到报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md。
@@ -142,7 +142,7 @@ def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"导出签到报表 — 签到记录粒度。"
"[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本。"
"[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本。"
),
)
parser.add_argument("--users", required=True,
@@ -4,7 +4,7 @@
[AI Agent 强制门禁] 本模块不可单独执行,且任何调用方脚本
(attendance_report_detail/monthly/daily.py)执行前都必须先阅读:
dingtalk-workspace/references/products/attendance-report.md
references/attendance-report.md
工作流细节、报表类型判断、人员获取、列选择、错误处理等约束
全部在 attendance-report.md,禁止凭本脚本源码或 --help 自行组装命令。
@@ -3,7 +3,7 @@
考勤报表导出 — 每日统计粒度
⛔ 【AI Agent 强制门禁】调用本脚本前必须先阅读:
dingtalk-workspace/references/products/attendance-report.md
references/attendance-report.md
本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md,
包含但不限于:
@@ -113,7 +113,7 @@ def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(
description=(
"导出考勤报表 — 每日统计粒度。"
"⛔ AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本,"
"⛔ AI Agent 必须先读 references/attendance-report.md 再调用本脚本,"
"禁止凭 --help 或脚本路径自行拼命令。"
),
)
@@ -8,7 +8,7 @@
[AI Agent 强制门禁] 调用本脚本前必须先阅读:
dingtalk-workspace/references/products/attendance-report.md
references/attendance-report.md
本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md,
包含但不限于:
@@ -88,7 +88,7 @@ def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(
description=(
"导出考勤报表 — 明细粒度(打卡记录)。"
"[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本,"
"[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本,"
"禁止凭 --help 或脚本路径自行拼命令。"
),
)
@@ -3,7 +3,7 @@
考勤报表导出 — 月度汇总粒度
[AI Agent 强制门禁] 调用本脚本前必须先阅读:
dingtalk-workspace/references/products/attendance-report.md
references/attendance-report.md
本脚本仅是"考勤报表导出工作流"的执行末端,工作流完整定义在 attendance-report.md,
包含但不限于:
@@ -108,7 +108,7 @@ def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(
description=(
"导出考勤报表 — 月度汇总粒度。"
"[强制] AI Agent 必须先读 references/products/attendance-report.md 再调用本脚本,"
"[强制] AI Agent 必须先读 references/attendance-report.md 再调用本脚本,"
"禁止凭 --help 或脚本路径自行拼命令。"
),
)
@@ -3,7 +3,7 @@
考勤排班查询导出脚本
[AI Agent 强制门禁] 本脚本执行前必须先阅读:
dingtalk-workspace/references/products/attendance-schedule.md
references/attendance-schedule.md
职责:
1. 分批查询排班记录(支持大量用户自动分批)
@@ -3,7 +3,7 @@
考勤排班导入脚本
[AI Agent 强制门禁] 本脚本执行前必须先阅读:
dingtalk-workspace/references/products/attendance-schedule.md
references/attendance-schedule.md
排班工作流、参数校验、班次校验、回显确认等约束全部在
attendance-schedule.md,禁止凭本脚本源码或 --help 自行组装命令。
@@ -3,7 +3,7 @@
假期余额 Excel 导出脚本。
[AI Agent 强制门禁] 调用本脚本前必须先阅读:
dingtalk-workspace/references/products/attendance-vacation.md
references/attendance-vacation.md
本脚本负责:
1. 通过 dws attendance vacation types 获取假期规则列表,用于确定列顺序
@@ -86,7 +86,7 @@ def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"导出假期余额 Excel。AI Agent 必须先读 "
"references/products/attendance-vacation.md 再调用本脚本。"
"references/attendance-vacation.md 再调用本脚本。"
),
)
parser.add_argument("--users", required=True, help="userId 或 deptId 列表,逗号分隔")
+45 -8
View File
@@ -1,6 +1,6 @@
---
name: dingtalk-calendar
description: 钉钉日历与会议室。Use when 用户说 约会议/查日程/订会议室/查闲忙/加参会人/改期/取消会议/今天的日程/本周日程/共同空闲。视频会议发起/邀请入会/会中控制当前 CLI 不支持,应提示在钉钉客户端操作。Distinct from dingtalk-minutes(听记)、dingtalk-todo(待办)。命令前缀:dws calendar。
description: 钉钉日历与会议室。Use when 用户说 约会议/查日程/订会议室/查闲忙/加参会人/改期/取消会议/今天的日程/本周日程/共同空闲。视频会议发起/邀请入会/会中控制当前 CLI 不支持,应提示在钉钉客户端操作;AI 听记走 dingtalk-minutes,待办任务走 dingtalk-todo。命令前缀:dws calendar。
cli_version: ">=0.2.14"
metadata:
category: product
@@ -14,13 +14,12 @@ metadata:
> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。
> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH.
## 前置条件 — 执行操作前必读
> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 命令参考:[calendar.md](references/calendar.md);剧本:[03-meeting.md](references/03-meeting.md)。
## 意图表
@@ -30,17 +29,52 @@ metadata:
| "今天 / 明天 / 本周日程" | `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 <id> --start "<ISO>" --end "<ISO>"` |
| "查闲忙" | `dws calendar event list --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 --keyword "<姓名>" --dimension name --format json` 取 `userId`,多人逗号拼接。
2. **执行(必须)**:`dws calendar event create --summary "<主题>" --start "<ISO>" --end "<ISO>" --attendees <userId1,userId2> --format json`(按需加 `--location`/`--description`/`--rooms`)。
3. **验证(必须)**:从返回取 `eventId`,可 `dws calendar event list --start "<ISO>" --end "<ISO>" --format json` 复核。
**禁止**:跳过与会人 userId 解析直接传姓名、编造会议室 roomId。
### SOP-3 查闲忙(check-busy)
**触发**:某人/会议室是否有空/找空闲时段/避免冲突。
1. **解析对象(必须)**:姓名 → `dws aisearch person --keyword "<姓名>" --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`;不要把选择权抛回用户导致任务停住。
- 用户明确授权“任意空闲会议室”时,`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 calendar event/attendee/room` 命令完成同一流程。
- 所有 dws 命令带 `--format json`;查询时间必须显式 `--start` / `--end`。
## 跨产品协作
@@ -52,3 +86,6 @@ metadata:
## 注意
`schedule-meeting` 必须读 [03-meeting.md](references/03-meeting.md) 中的「两准则」「搜房失败硬门禁」,禁止假设 `roomId`。
## 局部意图与 Recipe
- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。
@@ -1,7 +1,7 @@
# 会议管理(日程与会议室)
> **lite 速查表**含 `list-today-meetings`、`check-users-busy`;视频会议 `start-conference` 当前 CLI 不支持。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。**`schedule-meeting` 不做内联**:须读本文件 **「两准则」「搜房失败硬门禁」** 及下表 **schedule-meeting** 行全文。
> **听记、会后待办、摘要分享** 见 `dingtalk-minutes` sub-skill 的 `references/07-minutes.md`。
> lite recipe(`list-today-meetings`、`check-users-busy`)见 [lite-recipes.md](./lite-recipes.md);视频会议发起、邀请入会与会中控制当前 CLI 不支持,请在钉钉客户端操作。列表类操作须遵循 [calendar.md](./calendar.md) **「CLI 命令树与黄金路径」**,禁止无子命令的 `dws calendar` 或臆造 `calendar list`(见该文 **「反模式(禁止)」**)。预约会议须遵循本文件的时段、会议室与失败收束规则。
> **听记、会后待办、摘要分享** 见 `dingtalk-minutes/references/07-minutes.md`。
## 日程与会议室两准则(强制)
@@ -22,24 +22,15 @@
### 搜房失败硬门禁(园区/范围搜尽仍无 roomId)
在用户限定的园区、楼宇、楼层或评测固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),**本回合订房流程结束**。
在用户限定的园区、楼宇、楼层或固定分组内,已按早停规则**逐组 `room search` 查完**仍得不到任何有效 `rooms[].roomId`(或无任何空闲房)→ **立即停止**,向用户**明确报错/失败结论**(例如:该时段在指定范围内未检索到可预订会议室或无法获得 roomId),当前预订链路结束。
**用户汇报硬门禁**:一旦触发上面的失败条件,**下一条对外输出必须直接面向用户汇报结果**,不得继续在会话里自言自语式地延长推理。允许的后续只有两类:
1. **失败汇报**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订”
2. **确认放宽条件**:仅在需要继续推进时,明确问用户是否放宽地点范围、改时间或接受不订会议室
**失败处理**:明确说明“指定范围/指定会议室在该时段未找到可预订会议室,因此当前无法完成预订”。只有用户同意放宽地点范围、改时间或接受不订会议室后,才可按新条件继续。
以下表述/行为视为**违例**:继续写“让我再试一次”“也许是 Mock/测试环境”“可能存在预设 roomId 映射”“我去别的 group 看看”“我换个时间验证一下”“我先看看脚本/示例还能不能推断出 roomId”。
空结果只表示当前授权范围内没有返回合法 `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. **用户确认**:明确询问是否放宽范围、换时间,或接受不订会议室
出现空结果、`roomId invalid` 或授权范围查尽时,停止当前预订链路;不得沿原假设重复搜索。后续只能报告失败,或在用户确认新范围/新时间后重新搜索。
| # | 规范 |
|---|------|
@@ -47,10 +38,10 @@
| 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`、或开始换园区/绕路时 → **必须回读本节**;回读后只允许「报错收束」或「向用户确认是否放宽条件」 |
| 5 | **授权范围无结果即收束**:在用户允许的搜索范围内仍无可用会议室或有效 `roomId`,说明当前范围不可预订;不得假设 ID、用 `room add` 试探或绕路推断 |
| 6 | **无效 ID 恢复**:遇到 `roomId invalid` 时停止预订,回到 `room search` 的真实返回;若范围已查尽则报告无可用房,否则先确认是否放宽地点或时间 |
| Recipe | 行动指南(固定路线) |
| ------------------ | ------------------- |
| schedule-meeting | **见上文「两准则」**、**「搜房失败硬门禁」**。**未给时段且仅说「发起/开个会」**→ 不走本 recipe;当前 CLI 不支持实时视频会议,告知用户请在钉钉客户端操作。**未给时段但有预约意图**("安排""约""定"等词):追问具体开始/结束时间。**已有时段后**,按固定顺序执行:1. `dws calendar event create` 建日程;2. 有参会人则 `dws calendar attendee 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/测试环境。细则见「会议室搜索早停」。 |
| schedule-meeting | **未给时段且仅说“发起/开个会”**时不走本 recipe;当前 CLI 不支持实时视频会议,应告知用户在钉钉客户端操作。有预约日程意图但缺时段时先确认开始和结束时间。时段明确后固定执行:1. `calendar event create`;2. 有参会人则 `calendar attendee add`;3. 再处理会议室。无明确地点范围可用 `calendar_schedule_meeting.py --book-room`;有楼宇/楼层范围时先 `room list-groups` 锁定最相关 group;点名会议室时用 `room search` 返回的 `rooms[].roomId` 调 `room add`。授权范围内无空房则报告不可预订;需要扩大范围或改时间时先取得用户同意。 |
| 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 --group <openConversationId> --text "<变更通知>"` 通知变更 |
@@ -0,0 +1,7 @@
# 日历能力索引
| 主题 | 详见 |
|---|---|
| 日程/会议/会议室/闲忙 | [`calendar.md`](./calendar.md) |
| 会议最佳实践 | [`03-meeting.md`](./03-meeting.md) |
| 轻量流程 | [`lite-recipes.md`](./lite-recipes.md) |
@@ -2,18 +2,17 @@
## CLI 命令树与黄金路径
- **二级子命令(必选其一)**:`event`(日程)、`attendee`(参会人)、`room`(会议室)、`busy`(闲忙)、`attachment`(日程附件)、`book`(用户日历列表)。`dws calendar` 后**必须**紧跟上述之一;**禁止**只执行 `dws calendar`(无子命令)。
- **二级子命令(必选其一)**:`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 book list`(返回字段为 `calendarId`,主日历 `calendarId == "primary"` 等;注意无 `id` 字段,按 `.id` 提取会取空)。**重要**可以查询他人共享给自己的日历本,根据日历本id可以进一步查询对方的日程信息。
- **查用户日历本列表**:`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`。
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 输出作为「观察」重复提交给模型;若误触,应直接改用本节黄金路径中的合法命令并重试。
4. **禁止**继续使用已弃用的 `--max-results` 参数 —— 它们仍可被解析但**会被丢弃**,不再透传到 MCP,模型生成命令时也不要再带。
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`,再在命令中整体重传。
@@ -30,6 +29,9 @@
响应状态(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 相关指令,也无法添加会议室。
## 命令概览
@@ -61,7 +63,6 @@ dws calendar room add [flags]
dws calendar room delete [flags]
```
> room是会议室,用于线下开会场景。
> **组织限制**:部分企业使用自建会议室系统,未接入钉钉会议室能力。此时 `room search` / `room list-groups` 会返回业务错误 `400056`("所选组织不支持预定钉钉会议室")。这是组织级配置限制,不是命令用法问题;应直接告知用户该组织需在钉钉客户端手动预订会议室,不要重试或换参。
### busy 相关三级子命令
```
@@ -75,10 +76,26 @@ dws calendar busy search [flags]
dws calendar attachment add [flags]
```
### book 相关三级子命令
### 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 进一步查看指令明细,也可以继续查看下一节 命令总览
@@ -90,17 +107,22 @@ 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)
```
> `--max-results` 已弃用:MCP 不再支持,CLI 仍接受但参数会被丢弃,结果固定最多返回 100 条。
**默认行为**:不传 `--start` / `--end` 时,默认返回今天的日程(00:00:00 ~ 23:59:59)。
**权限**:查询共享日历下的日程时,至少要有reader权限。
**分页**:单次最多返回 `--limit` 指定的条数(默认/最大 100);当结果超过 limit 时,返回体包含 `nextCursor` 字段。首次查询无需传 `--cursor`,仅在翻页时将上一次返回的 `nextCursor` 作为 `--cursor` 传入。
当前用户的个人日程使用 `dws calendar event list` 查询;参数包含 `--limit` 和 `--cursor` 分页。
### 获取日程详情
```
@@ -130,10 +152,17 @@ Example:
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
@@ -153,6 +182,7 @@ Flags:
--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` 即可。
@@ -166,8 +196,10 @@ Example:
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
@@ -199,8 +231,10 @@ 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 (必填)
--id string 日程 ID (必填)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取)
```
### 查看参会人
@@ -209,8 +243,10 @@ 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 (必填)
--event string 日程 ID (必填)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取, 注意:订阅日历下的日程无参会人,因此不可查看)
```
### 添加参会人
@@ -220,10 +256,12 @@ Usage:
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 参会人可选 (默认必选参会人)
--event string 日程 ID (必填)
--attendees string 参会人 userId 列表,逗号分隔 (必填,最多500人)
--optional 参会人可选 (默认必选参会人)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可添加)
```
### 移除参会人
@@ -235,36 +273,54 @@ 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 列表,逗号分隔 (必填)
--event string 日程 ID (必填)
--attendees string 参会人 userId 列表,逗号分隔 (必填)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程无参会人,因此不可移除)
```
### 搜索会议室
> 此指令可用于搜索当前用户可用的会议室。**注意**,大部分会议室**仅在工作时间**可用,如果检索时间不在工作时间可能查不到任何结果。
> 此指令支持两种模式:**按名称搜索**(不传 --start/--end)和**按时间段搜索可用会议室**(传 --start/--end 或不传任何参数)。
> 此指令搜索到的会议室结果中,有两个值需要注意:
- customApprovalProcess: true - 表示该会议室设置了自定义审批流程,只能通过客户端完成预订。
- supportRecurring: true - 表示该会议室支持循环预定;false - 表示不支持循环预定,直接加入到循环日程会失败。
> - 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 --room-name 永澄亭 # 注意:用户即使说「永澄亭会议室」,也应仅传「永澄亭」
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 按会议室名称过滤(可选,服务端模糊匹配;传入前必须由调用方精简,只保留核心专名)
--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 不会再做任何删减**。
@@ -274,13 +330,16 @@ Flags:
- 用户:「西湖厅有空吗」 → `--room-name 西湖厅`(本身就是专名,不删即可)
- 用户:「预定贡嘎山大会议室」 → `--room-name 贡嘎山`
**优先路径**:当用户给了会议室中文名时,**优先用 `room search --room-name <核心专名> --start <开始时间> --end <结束时间>`**代替「先走 `list-groups`再遍历」的旧流程。`--room-name` 可与 `--group-id` 同时使用,表示「在指定分组内按名称过滤」。若返回空列表,再降级使用 `list-groups` 定位分组再查。
**优先路径**:
- 当用户仅给出会议室名称、未指定时间段时,用 `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 指令
---
---
### 预定会议室
```
@@ -288,9 +347,11 @@ 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 列表 (必填)
--event string 日程 ID (必填)
--rooms string 会议室 ID 列表 (必填)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取,注意:订阅日历下的日程不可添加会议室)
```
> room是会议室,用于线下开会场景。将room加入到日程完成预订
> 重复性日程,预订会议室时,必须设置 循环结束时间(recurrence-end-date),noEnd 或者 指定循环次数 都无法完成预定。
@@ -305,9 +366,11 @@ 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 列表 (必填)
--event string 日程 ID (必填)
--rooms string 会议室 ID 列表 (必填)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程不可添加会议室)
```
### 会议室分组列表
@@ -328,24 +391,104 @@ 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>,多项逗号分隔 (必填)
--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
```
> 通过此接口可查询当前用户的日历列表,包含 主日历本、他人共享的日历、订阅的公共/团队日历。
> 共享日历本中有来自 xxx 的,且权限大于reader,那么通过 `event list --calendar-id <xxx的日历本id> `可查到xxx完整的日程安排
> 查询"我能看哪些日历本",包含:我的主日历、他人共享**给我**的日历、订阅的公共/团队日历。注意区分:`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:
@@ -398,9 +541,11 @@ 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(暂定) (必填)
--id string 日程 ID (必填)
--status string 响应状态: needsAction(未操作)|accepted(接受)|declined(拒绝)|tentative(暂定) (必填)
--calendar-id string 日历 ID (可选,默认 primary 主日历;指定其他日历本时填写,可通过 `book list` 获取。注意:订阅日历下的日程无参会人,因此不可响应)
```
> **说明**:作为日程参会人,设置自己的响应状态(接受、拒绝、暂定)。`--status` 可选值:`needsAction`(未操作,默认值)、`accepted`(接受)、`declined`(拒绝)、`tentative`(暂定)。
@@ -410,7 +555,7 @@ Flags:
用户说"日程/会议/约会/日历":
- 查看 → `event list`
- 详情 → `event get`
- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`)
- 创建/约/给自己留时间块/个人日程 → `event create`(带参会人时加 `--attendees`,循环日程加 `--recurrence-*`,自定义提醒加 `--remind-minutes`)
- 修改/改时间/改描述 → `event update`(支持修改标题、时间、描述、时区、循环规则)
- 取消/删除 → `event delete`
- 推荐时间/什么时候有空/协调时间 → `event suggest`
@@ -422,13 +567,14 @@ Flags:
- 移除 → `attendee delete --attendees <USER_ID>`
用户说"会议室/订会议室":
- 哪个空闲 → `room search`
- 按名找会议室(如「永澄亭」「永澄亭会议室」「约西湖厅」)→ 先在模型层精简名称(剔除「会议室」等通用后缀),再用 `room search --room-name <核心专名>`
- 哪个空闲 → `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`(可再叠加 `--room-name` 在分组内过滤)
- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`(需配合 `--start`/`--end` 按时间段搜索;可再叠加 `--room-name` 在分组内过滤)
用户说"有空吗/忙不忙/闲忙":
- 查询用户闲忙 → `busy search --users <USER_ID>`
@@ -438,8 +584,18 @@ Flags:
用户说"日程附件/给会议加文件/上传日程材料":
- 添加 → `attachment add`(先用钉盘上传得 fileId,再 `attachment add --files <fileId>:<name>`)
用户说"我有几个日历/查所有日历/共享日历本":
- 列表 → `book list`(主日历 id 固定为 `primary`)
用户说"我有几个日历/查所有日历/别人共享给我的日历/他人共享给我的日历本":
- 列表 → `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`
@@ -506,12 +662,16 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `event create` | `eventId` | attendee/room/attachment 操作的 --event |
| `event list` | `events[].eventId` | event get/update/delete/respond 的 --id |
| `event list` | `events[].eventId`, `nextCursor` | event get/update/delete/respond 的 --id;下一页 --cursor |
| `event suggest` | 推荐的时间段 | event create 的 --start/--end |
| `event respond` | 响应结果 | — |
| `room search` | `rooms[].roomId` | room add 的 --rooms 或 event create 的 --rooms |
| `room list-groups` | `groups[].groupId` | room search 的 --group-id |
| `book list` | `calendarId`(如 `primary`) | event list/get 的 --calendar-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>` |
## 注意事项
@@ -527,20 +687,22 @@ dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15
- `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`
- **自动化校验**:凡涉及 `room add` / `event create --rooms` 的流程,`--rooms` 只能填上游 `room search`(或等价接口)返回 JSON 中的 **`rooms[].roomId`**;不得以会议室展示名、楼层文案或用户口语当作 `roomId`
- **附件**:`attachment add` 仅负责挂载,**不上传**文件;fileId 必须先通过钉盘流程取得;`--files` 多附件用 `<fileId>:<name>` 元素逗号分隔
- **日历本**:`book list` 返回的 `calendarId` 字段才是合法日历 id(无 `id` 字段);如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历
- **已弃用入参**:`--max-results`(event list)在新 MCP schema 中被移除;CLI 仍接受但**不会**透传到 MCP;模型生成命令时**禁止**继续使用
- **日历本**:`book list` 返回的 `id` 才是合法 `calendarId`;如无明确说明,`event list` / `event get` 都不要带 `--calendar-id`,让接口默认走 primary 主日历
- **分页查询**:`event list` / `list-mine` 支持 `--limit`(控制每页条数,默认/最大 100)和 `--cursor`(翻页游标);**首次查询无需传 `--cursor`**,仅当返回体中包含 `nextCursor` 时,将其作为 `--cursor` 传入可获取下一页
- **日程提醒**:`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` |
| [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 视频会议 — 当前 CLI 不支持发起/预约/邀请入会/会中控制;请在钉钉客户端操作
- [contact](./contact.md) — 搜索同事 userId,用于 attendee add --attendees
- conference(视频会议预约) — 仅视频会议预约(返回入会链接),不含参会人/会议室管理
- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事 userId,用于 attendee add --attendees
@@ -0,0 +1,10 @@
# calendar 局部意图消歧
本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|---|---|---|---|---|
| "给自己留一个明天下午的时间块/建个个人日程" | 创建个人日程 | `calendar event create` | `todo` | 个人 schedule 仍属于日历事件,不是待办 |
| "帮我预约一个视频会议" / "开个会" / "发起会议" | 视频会议 | 钉钉客户端(当前 CLI 不支持) | `calendar` | 不要把实时视频会议诉求改写成日历日程 |
| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室);视频会议当前 CLI 不支持 |
| "明早 9 点提醒我提交周报" | 创建个人待办,但需先声明 reminder 边界 | `todo` | `calendar` | todo 当前只支持 dueTime 截止时间,不支持独立精确 reminder |
@@ -0,0 +1,27 @@
# 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 --keyword "<姓名>" --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) 中「查询用户闲忙状态」。
### 视频会议能力边界
当前 CLI 不提供视频会议发起、预约、入会邀请或会中控制。用户只说“开个会/发起会议”且未给日程时段时,直接说明请在钉钉客户端操作,不要构造视频会议命令。
- 有具体时间(如“明天 3 点开会”)或明确预约日程 → 走 [03-meeting.md](./03-meeting.md) 的 `schedule-meeting`
- 只有实时视频会议诉求 → 说明当前 CLI 不支持,请在钉钉客户端发起
@@ -51,51 +51,30 @@ def fmt_iso(dt: datetime) -> str:
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
def _extract_time_str(value: Any) -> str:
"""时间字段兼容字符串与 {dateTime, date} 对象两种形态"""
if isinstance(value, dict):
return value.get('dateTime') or value.get('date') or ''
return value or ''
def parse_busy_intervals(
data: Any,
) -> List[Tuple[datetime, datetime]]:
intervals = []
if not data:
return intervals
# 兼容 {result: [...]} 包裹结构
if isinstance(data, dict) and 'result' in data:
data = data['result']
rows = []
if isinstance(data, list):
rows = data
elif isinstance(data, dict):
rows = list(data.values())
items = []
for row in rows:
if isinstance(row, list):
items.extend(row)
elif isinstance(row, dict):
# 新结构: {userId, scheduleItems: [{start, end, status}]}
if isinstance(row.get('scheduleItems'), list):
items.extend(row['scheduleItems'])
elif isinstance(row.get('busyTimes'), list):
items.extend(row['busyTimes'])
elif row.get('startTime') or row.get('start'):
items.append(row)
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:
if not isinstance(item, dict):
continue
# FREE 时段不算忙
if str(item.get('status') or '').upper() == 'FREE':
continue
start_str = _extract_time_str(
item.get('startTime') or item.get('start')
)
end_str = _extract_time_str(
item.get('endTime') or item.get('end')
)
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 (
@@ -193,16 +172,6 @@ def main():
if args.dry_run:
return
# 查询失败必须中止, 否则会把"查不到"当成"全天空闲"给出危险结论
if data is None:
print('错误:忙闲查询失败,无法给出空闲时段结论', file=sys.stderr)
sys.exit(2)
if isinstance(data, dict) and data.get('success') is False:
print(f"错误:忙闲查询失败: "
f"{data.get('errorMsg') or data.get('errorCode') or data}",
file=sys.stderr)
sys.exit(2)
busy = parse_busy_intervals(data)
free = find_free_slots(day_start, day_end, busy, args.duration)
@@ -212,7 +181,6 @@ def main():
print(f" 会议时长: {args.duration} 分钟")
print(f" 工作时间: {args.start_hour}:00 ~ "
f"{args.end_hour}:00")
print(f" 已识别忙时段: {len(busy)} 个")
print('=' * 50)
if not free:
+73 -8
View File
@@ -1,6 +1,6 @@
---
name: dingtalk-chat
description: 钉钉群聊与消息。Use when 用户提到 发消息/单聊/群聊/建群/拉人进群/改群名/搜索群/群成员管理/@消息/收藏消息/撤回消息/机器人群发/Webhook通知/发图片或文件到群。Distinct from dingtalk-ding(紧急DING消息/短信/电话)、dingtalk-mail(邮件)、dingtalk-edu-group(班级群)。命令前缀:dws chat。
description: 钉钉群聊与消息。Use when 用户提到 发消息/单聊/群聊/建群/拉人进群/改群名/搜索群/群成员管理/@消息/收藏消息/撤回消息/机器人群发/Webhook通知/发图片或文件到群/标记未读/清除红点/置顶消息/全部群列表。不做紧急 DING/短信/电话(走 dingtalk-ding)、邮件(走 dingtalk-mail)、班级群(走 dingtalk-edu-group)。命令前缀:dws chat。
cli_version: ">=0.2.14"
metadata:
category: product
@@ -14,21 +14,22 @@ metadata:
> 🧪 **EXPERIMENTAL · 试验版 / Preview** — multi 模式当前未达 stable 标准。全部 dingtalk-* skill 已通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整;生产 / 共享环境请优先使用 mono 模式(`dws skill setup --mode mono`)。问题请提 issue 反馈。
> **PREREQUISITE:** Read the `dws-shared` skill first for auth, global flags, product routing, URL preflight, error codes, and safety rules. The `dws` binary must be on PATH.
## 前置条件 — 执行操作前必读
> **`use_skill(dws-shared)`** — 认证、全局参数(`--format json` / `--yes`)、错误码、URL 模板、跨产品消歧、安全规则与 capability 边界。**执行任何 `dws` 命令前先读;** 单产品的清晰命令可直接用本 skill。
<!-- SAFETY_PREAMBLE_INJECT -->
> ⚠️ **命令可用性以当前 dws 二进制为准**。服务发现已下线,本文档随内置 skill 发布;如果 `dws <cmd> --help` 不存在,说明当前版本未暴露该命令。若命令存在但调用失败,请按错误中的 endpoint 或 tool 提示确认静态端点目录和后端工具注册。实际调用前可用 `dws <cmd> --help` 或 `--dry-run` 验证。
> 渐进式参考:[chat-index.md](references/chat-index.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。先按索引选择命令族。
> 命令参考:[chat.md](references/chat.md);表情:[chat-emoji-list.md](references/chat-emoji-list.md);剧本:[01-messaging.md](references/01-messaging.md)。
> 旧路径兼容入口:[chat.md](references/chat.md)。
## 意图表
| 用户说 | 命令 |
|--------|------|
| "发消息给张三" | `dws chat message send --open-dingtalk-id <id> --title "<标题>" --text "<内容>"` |
| "发到XX群" | `dws chat search --query "<群名>"` → `dws chat message send --group <openConversationId> --title "<标题>" --text "<内容>"` |
| "发消息给张三" | `dws chat message send --open-dingtalk-id <id> --text "<内容>"` |
| "发到XX群" | `dws chat search --query "<群名>"` → `dws chat message send --group <openConversationId> --text "<内容>"` |
| "建群" / "拉人进群" | `dws chat group create` / `dws chat group members add` |
| "改群名" / "踢人" | `dws chat group rename` / `dws chat group members remove --yes`(踢人不可逆,确认目标后加 --yes;踢群主会被 CLI 拦截,需先 `transfer-owner`)|
| "@我消息" | `dws chat message list-mentions` |
@@ -39,8 +40,69 @@ metadata:
| "Webhook 推一条" | `dws chat message send-by-webhook --token <token> --title "<标题>" --text "<内容>"` |
| "撤回我发的消息" | `dws chat message recall`(撤回当前用户发送的消息)|
| "撤回机器人消息" | `dws chat message recall-by-bot --robot-code <code> --group <openConversationId> --keys <processQueryKey>`(撤回机器人发的)|
| "标记未读 / 清除红点 / 全部已读" | `dws chat mark-unread` / `dws chat clear-red-point` / `dws chat clear-all-red-point` |
| "置顶某条消息 / 取消消息置顶" | `dws chat message set-top-msg` / `dws chat message unset-top-msg` |
| "我加入的所有群 / 全部群列表" | `dws chat group list-all` |
> **注**:`chat message send` 的 `--title` 可选(不传时用正文首行作标题);`send-by-bot` / `send-by-webhook` 的 `--title` 必填。
## 标准 SOP(必遵流程)
> 命中以下意图**必须**按对应 SOP 顺序执行;**禁止**跳步、替换命令、编造 ID。每条命令必须带 `--format json`,执行后必须按"解析"步取真实字段(`openDingTalkId` / `openConversationId` / `clientMsgId`)。
### SOP-1 发消息(send-message)
**触发**:发消息/单聊/通知某人/发到群里。
1. **解析收件人(必须)**:人名 → 先 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `openDingTalkId`(优先)或 `userId`;群名 → 先 `dws chat search --query "<群名>" --format json` 取 `openConversationId`。
2. **执行(必须)**:单聊 `dws chat message send --open-dingtalk-id <openDingTalkId> --text "<内容>" --format json`(只有拿不到 `openDingTalkId` 时才用 `--user <userId>`);群聊 `dws chat message send --group <openConversationId> --text "<内容>" --format json`。
3. **验证(必须)**:从返回取 `messageId` / `openMessageId` / `clientMsgId` 备用(撤回要用);返回非 `success` 必须如实报错,不要谎报已发。
**禁止**:把人名/群名直接当 ID 传入、跳过 `aisearch person`/`chat search` 解析、跳过 `--format json`、未发送成功就答复"已发送"。
### SOP-2 建群(create-group)
**触发**:建群/拉人进群/新建讨论组。
1. **解析成员(必须)**:对每个成员 `dws aisearch person --keyword "<姓名>" --dimension name --format json` 取 `userId`,多人英文逗号拼接。
2. **执行(必须)**:`dws chat group create --name "<群名>" --users <userId1,userId2,...> --format json`;外部群加 `--type EXTERNAL`,话题圈加 `--thread`。
3. **验证(必须)**:从返回取 `openConversationId`,可用 `dws chat search --query "<群名>" --format json` 复核。
**禁止**:跳过成员 userId 解析直接传姓名、编造 `openConversationId`。
### SOP-3 Webhook 推送(send-by-webhook)
**触发**:用机器人群 webhook 推一条消息。
1. **执行(必须)**:`dws chat message send-by-webhook --token <webhookToken> --title "<标题>" --text "<内容>" --format json`。
2. **@ 人(必须)**:需要 @ 时,`--text` 中**必须**先包含对应 `@userId` / `@手机号` / `@10`,再配合 `--at-users` / `--at-mobiles` / `--at-all`;否则 @ 不生效。
**禁止**:只传 `--at-users` 而 `--text` 里不含 `@<标识>`。
### SOP-4 共同群查询(search-common-group)
**触发**:"我和 XX 的共同群"。
1. **取昵称(必须)**:先 `dws contact user get-self --format json` 取自己昵称;对方昵称从历史/上下文取,拿不到必须先问用户。
2. **执行(必须)**:`dws chat search-common --nicks "<昵称1>,<昵称2>" --limit 20 --cursor 0 --format json`;`hasMore=true` 时**必须**用 `nextCursor` 翻页,不要停在第一页。
**禁止**:跳过昵称解析、忽略 `hasMore` 不翻页。
### SOP-5 红点 / 未读管理(manage-red-point)
**触发**:标记未读/清除红点/全部已读。
1. **执行(必须)**:标记某会话未读 `dws chat mark-unread --conversation-id <openConversationId> --format json`;清除某会话红点 `dws chat clear-red-point --conversation-id <openConversationId> --format json`;全部已读 `dws chat clear-all-red-point --format json`。
2. **取会话 ID(必须)**:`openConversationId` 拿不准时先 `dws chat group list-all --format json` 或 `dws chat search --query "<群名>" --format json`,**禁止**编造。
**禁止**:未确认会话就批量"全部已读"(破坏性,必须先与用户确认)。
### SOP-6 特别关注消息(focus-messages)
**触发**:"特别关注的人最近发了什么/聊了什么"。
1. **执行(必须)**:`dws chat message list-focused --limit 50 --format json`,直接基于返回答复。
2. **边界(必须)**:只有用户终点是"我关注了谁"这种**人员列表**时,才切 `dingtalk-contact` 关系查询。
**禁止**:用普通 `message list` 冒充 focused、把人员列表需求硬塞进 chat。
## 跨产品协作
@@ -48,3 +110,6 @@ metadata:
- 要发图片/文件 → 先 `dt_media_upload` 上传 → `python scripts/extract_media_id.py "<URL>"` 提取 mediaId → 再用 `--media-id`
- 紧急升级(应用内/短信/电话)→ 切到 `dingtalk-ding`
- 发邮件 → 切到 `dingtalk-mail`
## 局部意图与 Recipe
- [局部意图消歧](references/intent-guide.md);[Lite Recipe](references/lite-recipes.md)。
@@ -1,16 +1,14 @@
# 消息沟通
> **安全红线**:收件人搜索返回多条/零条结果、或名称不完全匹配时,**必须列出候选项让用户确认**,严禁自动选择第一条。即使支持机器人消息撤回 `chat message recall-by-bot`,对方可能已读,发错代价高。禁止自行发送"测试消息"验证通道。
> lite recipe 见 `dws-shared/references/best_practices/_common/lite-recipes.md`。
> lite recipe(`send-message`、`create-group`、`send-by-webhook`、`search-common-group`、`focus-messages`)见 [lite-recipes.md](./lite-recipes.md)。
| Recipe | 行动指南(固定路线) |
|--------|-------------------|
| query-group-chat | **优先**:`python scripts/chat_export_messages.py --query "<群名>" --time "<yyyy-MM-dd HH:mm:ss>" [--no-forward] [--limit N] [--output messages.json]`(自动搜群+翻页+导出)<br>备选:1. `chat search --query "<群名>"` → 取 `openConversationId`<br>2. `chat message list --group <openConversationId> --time "<yyyy-MM-dd HH:mm:ss>"` → 取消息列表<br>3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`<br>4. `--direction older` 拉给定时间**之前**的消息(`newer` 拉之后)<br>5. 合并全部消息后总结 |
| query-private-chat | **优先**:`python scripts/chat_history_with_user.py --name "<姓名>" --time "<yyyy-MM-dd HH:mm:ss>" [--no-forward] [--limit N] [--output messages.json]`(自动搜人+翻页+导出)<br>备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`<br>2. `chat message list-direct --user <userId> --time "<yyyy-MM-dd HH:mm:ss>"` → 取消息列表<br>3. **翻页**:同 query-group-chat<br>4. 合并全部消息后总结 |
| escalate-ding | 三级升级:<br>1. `ding message send --robot-code <robotCode> --type app --users <userId> --content "<内容>"`(必填项见 `dingtalk-ding/references/ding.md`)<br>2. `chat message send --group <openConversationId> --text "<内容>"` 群里提醒(可选 `--title` / `@` 见 [chat.md](./chat.md))<br>3. `todo task create --title "<标题>" --executors <userId> --priority 40` 建紧急待办<br>前置:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`;`chat search --query "<群名>"` → 取 `openConversationId` |
| query-group-chat | **优先**:`python scripts/chat_export_messages.py --query "<群名>" --time "<yyyy-MM-dd HH:mm:ss>" [--no-forward] [--limit N] [--output messages.json]`(自动搜群+翻页+导出)<br>备选:1. `chat search --query "<群名>"` → 取 `openConversationId`<br>2. `chat message list --group <openConversationId> --time "<yyyy-MM-dd HH:mm:ss>"` → 取消息列表<br>3. **翻页**:`hasMore=true` 时取本页最后 `createTime` 作为下次 `--time`,重复至 `hasMore=false`<br>4. `--forward=false` 拉给定时间**之前**的消息<br>5. 合并全部消息后总结 |
| query-private-chat | **优先**:`python scripts/chat_history_with_user.py --name "<姓名>" --time "<yyyy-MM-dd HH:mm:ss>" [--no-forward] [--limit N] [--output messages.json]`(自动搜人+翻页+导出)<br>备选:1. `aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`<br>2. `chat message list --user <userId> --time "<yyyy-MM-dd HH:mm:ss>"` → 取消息列表<br>3. **翻页**:同 query-group-chat<br>4. 合并全部消息后总结 |
| escalate-ding | 三级升级:<br>1. `ding message send --robot-code <robotCode> --type app --users <userId> --content "<内容>"`(必填项见 `dingtalk-ding`)<br>2. `chat message send --group <openConversationId> --text "<内容>"` 群里提醒(可选 `--title` / `@` 见 [chat.md](./chat-commands.md))<br>3. `todo task create --title "<标题>" --executors <userId> --priority 40` 建紧急待办<br>前置:`aisearch person --keyword "<姓名>" --dimension name` → 取 `userId`;`chat search --query "<群名>"` → 取 `openConversationId` |
| send-by-bot | **多群批量优先**:`python scripts/bot_broadcast.py --robot-code <robotCode> --chats <id1>,<id2> --title "<标题>" --text "<内容>"`<br>单群:1. `chat bot search` → 取 `robotCode`<br>2. `chat search --query "<群名>"` → 取 `openConversationId`<br>3. `chat message send-by-bot --robot-code <robotCode> --group <openConversationId> --title "<标题>" --text "<内容>"` |
| forward-message | 1. `chat search --query "<群名>"` → 取 `openConversationId` → `chat message list --group <openConversationId> --time "<起始时间>"` 拉源消息<br>2. `contact user search --query "<姓名>"` → 取 `openDingTalkId`(推荐);或 `chat search --query "<群名>"` → 取目标 `openConversationId`<br>3. `chat message send --open-dingtalk-id <openDingTalkId> --text "<内容>"`(推荐)或 `--group <openConversationId> --text "<内容>"` 发送。仅当无法获取 openDingTalkId 时才用 `--user <userId>`(备选) |
| search-common-group | `chat search-common --nicks "<昵称1>,<昵称2>" --limit 20 --cursor 0`(`--match-mode AND`=全在/`OR`=任一在,翻页:`hasMore=true` 时用 `nextCursor`)<br>用户说"我和XX的共同群" → nicks 包含"我"时,需先 `contact user get-self` 取自己昵称再拼接 |
| focus-messages | **零参数一行命令**:`chat message list-focused --limit 50`(拉特别关注人发的消息聚合)<br>触发 query:`"我特别关注的人最近发了什么消息"`、`"关注的人最近聊了啥"`、`"星标联系人最近的动态"`<br>**强消歧**:query 含动词【发/说/聊/讲】或名词【消息/聊天/动态】 → **必须**走本命令,**不要**先去拉 `contact relation list-my-followings`;仅当用户终点是"人员列表"(如"我关注了谁")才走 `relation list-my-followings`(详见 `dingtalk-contact/references/contact.md` 意图判断)<br>翻页:`hasMore=true` 时用 `nextCursor` 作为下次 `--cursor`<br>按人精控(可选):先 `contact relation list-my-followings` 取 `openDingTalkId`,再 `chat message list-by-sender --sender-open-dingtalk-id <openDingTalkId> --start <ISO> --end <ISO>` |
| focus-messages | **零参数一行命令**:`chat message list-focused --limit 50`(拉特别关注人发的消息聚合)<br>触发 query:`"我特别关注的人最近发了什么消息"`、`"关注的人最近聊了啥"`、`"星标联系人最近的动态"`<br>**强消歧**:query 含动词【发/说/聊/讲】或名词【消息/聊天/动态】 → **必须**走本命令,**不要**先去拉 `contact relation list-my-followings`;仅当用户终点是"人员列表"(如"我关注了谁")才走 `relation list-my-followings`(详见 `dingtalk-contact` 易混淆硬规则)<br>翻页:`hasMore=true` 时用 `nextCursor` 作为下次 `--cursor`<br>按人精控(可选):先 `contact relation list-my-followings` 取 `openDingTalkId`,再 `chat message list-by-sender --sender-open-dingtalk-id <openDingTalkId> --start <ISO> --end <ISO>` |
| favorite-message | 收藏:先从消息查询结果取得 `openMessageId` 和 `openConversationId`,再执行 `chat message add-favorite --open-message-id <id> --open-conversation-id <cid>`<br>取消收藏:对应执行 `remove-favorite`,不会删除原消息<br>查询收藏:`chat message list-favorites [--cursor N] [--size 20]`,`hasMore=true` 时继续传 `nextCursor` |
@@ -0,0 +1,417 @@
# 机器人、分组与会话状态命令
### bot (机器人管理)
#### 搜索【我创建的】机器人 — 仅返回当前用户自己创建的机器人
范围: 仅限当前登录用户自己创建的机器人(不含他人创建、官方机器人)。
返回字段: 没有 openDingTalkId,如果需要给机器人发单聊消息请用 find。
典型触发词: "我创建的机器人""我的机器人""我自己的机器人""我做的机器人""查看我的机器人"。
```
Usage:
dws chat bot search [flags]
Example:
dws chat bot search --page 1
dws chat bot search --page 1 --size 10 --name "日报"
Flags:
--name string 按名称搜索
--page int 页码,从1开始 (默认 1)
--size int 每页条数 (默认 50),别名: --limit
```
#### 搜索【全部可用】机器人 — 含他人创建/官方机器人,额外返回 openDingTalkId
范围: 当前用户可用的全部机器人(含他人创建、官方机器人)。
返回字段: 额外返回 openDingTalkId(可用于给机器人发单聊消息),search 没有此字段。
典型触发词: "搜索机器人""找一个机器人""帮我找 XXX 机器人""所有可用机器人""查机器人"。
```
Usage:
dws chat bot find [flags]
Example:
dws chat bot find --query "日报"
dws chat bot find --query "日报" --limit 20
dws chat bot find --query "日报" --limit 20 --cursor <上次返回的 nextCursor>
Flags:
--query string 搜索关键词 (必填)
--limit int 每页返回数量(默认 20)
--cursor string 分页游标(首次调用不传,翻页时传上次返回的 nextCursor)
注意:
- cursor 必须用上次返回的 nextCursor 字符串原值,不要传 "0" 或其他数字字面量
(服务端 String 类型,但网关会把数字字符串 auto-coerce 回 Integer 导致 PARAM_ERROR)
```
search 与 find 选择指南:
| 维度 | `chat bot search` | `chat bot find` |
|------|-------------------|-----------------|
| 范围 | 仅我创建的机器人 | 全部可用机器人(含他人/官方) |
| 额外返回 openDingTalkId | 无 | 有(可用于给机器人发单聊消息) |
| 触发词 | "我创建的""我的""我自己的" | "搜索机器人""找机器人""查机器人" |
### category (会话分组管理)
#### 获取用户自定义会话分组
```
Usage:
dws chat category list
Example:
dws chat category list
# 返回当前用户的所有自定义会话分组
```
#### 拉取指定分组下的会话列表
```
Usage:
dws chat category list-conversations [flags]
Example:
dws chat category list-conversations --category-id <分组ID>
# 分组ID 可通过 dws chat category list 获取
Flags:
--category-id int 会话分组 ID (必填)
--exclude-muted 是否排除已设置免打扰的会话(默认 false)
```
#### 创建用户自定义会话分组
```
Usage:
dws chat category create [flags]
Example:
dws chat category create --title "工作群"
dws chat category create --title "项目组"
Flags:
--title string 分组名称 (必填)
```
#### 创建智能会话分组 — 可指定群名称关键词和群内成员作为匹配规则
```
Usage:
dws chat category create-smart [flags]
Example:
dws chat category create-smart --name "工作群"
dws chat category create-smart --name "项目组" --keywords "项目,开发"
dws chat category create-smart --name "团队群" --members openDingTalkId1,openDingTalkId2
dws chat category create-smart --name "重点群" --keywords "重点" --members openDingTalkId1
Flags:
--name string 分组名称 (必填)
--keywords string 群名称关键词列表,逗号分隔(可选)
--members string 群内成员 openDingTalkId 列表,逗号分隔(可选)
```
#### 删除用户自定义会话分组
```
Usage:
dws chat category delete [flags]
Example:
dws chat category delete --category-id <分组ID>
# 分组ID 可通过 dws chat category list 获取
Flags:
--category-id int 会话分组 ID (必填)
```
#### 更新用户自定义会话分组的名称
```
Usage:
dws chat category rename [flags]
Example:
dws chat category rename --category-id <分组ID> --title "新名称"
# 分组ID 可通过 dws chat category list 获取
Flags:
--category-id int 会话分组 ID (必填)
--title string 新的分组名称 (必填)
```
#### 将会话移动到指定的自定义分组中
```
Usage:
dws chat category add-conv [flags]
Example:
dws chat category add-conv --group <openConversationId> --category-ids 123,456
# 分组ID 可通过 dws chat category list 获取
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 会话 openConversationId (必填)
--category-ids string 目标分组 ID 列表,逗号分隔 (必填)
```
#### 将会话从指定的自定义分组中移出
```
Usage:
dws chat category remove-conv [flags]
Example:
dws chat category remove-conv --group <openConversationId> --category-ids 123,456
# 分组ID 可通过 dws chat category list 获取
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 会话 openConversationId (必填)
--category-ids string 目标分组 ID 列表,逗号分隔 (必填)
```
### mute (会话免打扰)
#### 会话消息免打扰 — 开启或关闭会话消息免打扰(支持单聊和群聊)
```
Usage:
dws chat mute [flags]
Example:
dws chat mute --conversation-id <openConversationId>
dws chat mute --conversation-id <openConversationId> --off
# 查询群 ID: dws chat search --query "群名"
# 查询单聊会话 ID: dws chat conversation-info --user <userId>
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
--off 关闭免打扰(不传则开启免打扰)
注意:
- 默认行为是开启免打扰,传 --off 则关闭免打扰
- 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
```
### hide (隐藏会话)
#### 隐藏会话 — 在会话列表中隐藏指定会话(支持单聊/群聊),收到新消息时会重新出现
```
Usage:
dws chat hide [flags]
Example:
dws chat hide --conversation-id <openConversationId>
dws chat hide --id <openConversationId>
# 查询群 ID: dws chat search --query "群名"
# 查询单聊会话 ID: dws chat conversation-info --user <userId>
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
注意:
- 隐藏后会话不再显示在列表中,收到新消息时会重新出现
- 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
```
### mute-at-all (关闭@所有人通知)
#### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知
```
Usage:
dws chat mute-at-all [flags]
Example:
dws chat mute-at-all --conversation-id <openConversationId>
dws chat mute-at-all --conversation-id <openConversationId> --off
# 查询群 ID: dws chat search --query "群名"
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
--off 恢复接收 @所有人通知(不传则关闭通知)
注意:
- 默认行为是关闭 @所有人通知,传 --off 则恢复接收通知
- 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
```
### mute-red-envelope (关闭红包通知)
#### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知
```
Usage:
dws chat mute-red-envelope [flags]
Example:
dws chat mute-red-envelope --conversation-id <openConversationId>
dws chat mute-red-envelope --conversation-id <openConversationId> --off
# 查询群 ID: dws chat search --query "群名"
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
--off 恢复接收红包通知(不传则关闭通知)
注意:
- 默认行为是关闭红包通知,传 --off 则恢复接收通知
- 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
```
### mark-unread (标记会话为未读)
#### 标记会话为未读 — 将指定会话标记为未读状态
```
Usage:
dws chat mark-unread [flags]
Example:
dws chat mark-unread --conversation-id <openConversationId>
dws chat mark-unread --id <openConversationId>
Flags:
--conversation-id string 会话 openConversationId (必填,支持群聊/单聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
注意:
- 支持群聊和单聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
- 标记未读后会话列表中会显示未读状态
```
### clear-red-point (清除会话红点)
#### 清除会话红点 — 清除指定会话的未读红点
```
Usage:
dws chat clear-red-point [flags]
Example:
dws chat clear-red-point --conversation-id <openConversationId>
dws chat clear-red-point --id <openConversationId>
Flags:
--conversation-id string 会话 openConversationId (必填,支持群聊/单聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
注意:
- 支持群聊和单聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
- 清除红点后该会话不再显示未读标记
```
### clear-all-red-point (红点清零)
#### 清除所有会话红点 — 一键全部已读
```
Usage:
dws chat clear-all-red-point
Example:
dws chat clear-all-red-point
注意:
- 无需任何参数,直接清除当前用户所有会话的未读红点
- 等效于“全部已读”操作
```
### list-all-conversations (全部会话列表)
#### 分页获取全部会话列表 — 获取当前用户的所有会话
```
Usage:
dws chat list-all-conversations [flags]
Example:
dws chat list-all-conversations
dws chat list-all-conversations --limit 50
dws chat list-all-conversations --limit 100 --cursor <nextCursor>
dws chat list-all-conversations --exclude-muted
Flags:
--limit int 每页数量(默认 1000)
--cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor)
--exclude-muted 是否排除已免打扰会话(默认 false)
注意:
- 返回结果包含单聊和群聊,不区分会话类型
- 翻页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor
- 与 list-top-conversations 的区别: 本命令返回全部会话(单聊+群聊),list-top-conversations 仅返回置顶会话
```
### clear-messages (清空会话聊天记录)
#### 清空会话聊天记录 — 清空当前用户指定会话的消息
```
Usage:
dws chat clear-messages [flags]
Example:
dws chat clear-messages --conversation-id <openConversationId>
dws chat clear-messages --id <openConversationId>
Flags:
--conversation-id string 会话 openConversationId (必填,支持群聊/单聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
注意:
- 仅清空当前用户视角的消息,不影响其他成员
- openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
```
### mark-read (标记消息已读)
#### 标记消息已读 — 将指定消息及之前的消息标记为已读
```
Usage:
dws chat mark-read [flags]
Example:
dws chat mark-read --conversation-id <openConversationId> --message-id <openMessageId>
dws chat mark-read --id <openConversationId> --message-id <openMessageId>
Flags:
--conversation-id string 会话 openConversationId (必填,支持群聊/单聊)
--id string --conversation-id 的别名
--chat string --conversation-id 的别名
--message-id string 消息 openMessageId (必填)
注意:
- 标记该消息及之前的所有消息为已读
- openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取
- openMessageId 可通过 chat message list 获取
```
### group list-all (分页拉取所有群)
#### 分页拉取我所有群列表 — 获取当前用户加入的所有群聊
```
Usage:
dws chat group list-all [flags]
Example:
dws chat group list-all
dws chat group list-all --limit 50
dws chat group list-all --limit 100 --cursor <nextCursor>
Flags:
--limit int 每页返回数量(默认 100,最大 200)
--cursor string 分页游标(首次不传,翻页传返回的 nextCursor)
注意:
- 与 `chat group list-my-groups` 区别: list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群
- 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor
```
### group list-join-validations (分页拉取入群验证记录)
#### 分页拉取入群验证记录 — 获取当前用户的所有入群验证记录
包括自己被拒绝的记录以及作为审批者的记录。
```
Usage:
dws chat group list-join-validations [flags]
Example:
dws chat group list-join-validations
dws chat group list-join-validations --limit 30
dws chat group list-join-validations --limit 20 --cursor <nextCursor>
Flags:
--limit int 单页数量(默认 20,最大 50)
--cursor string 分页游标(首次不传,翻页传返回的 nextCursor)
注意:
- 分页: hasMore=true 时用返回的 nextCursor 作为下次 --cursor
- cursor 首次拉取不传或传 null 时从当前时间开始拉
```
### group audit-join-validation (审批入群验证)
#### 审批入群验证 — 通过、拒绝、删除单个审核
支持通过、拒绝、删除、忽略、拒绝并拉黑等操作。
```
Usage:
dws chat group audit-join-validation [flags]
Example:
dws chat group audit-join-validation --group <openConversationId> --record-id 123456 --applicant <openDingTalkId> --inviter <openDingTalkId> --status AuditApprove
dws chat group audit-join-validation --group <openConversationId> --record-id 123456 --applicant <openDingTalkId> --inviter <openDingTalkId> --status AuditRefuse --description "不符合入群条件"
# 查询入群验证记录: dws chat group list-join-validations
Flags:
--group string 群 openConversationId (必填)
--record-id int64 申请记录 ID (必填)
--applicant string 申请人 openDingTalkId (必填)
--inviter string 邀请人 openDingTalkId (必填)
--status string 审批动作: AuditApprove/AuditDelete/AuditIgnore/AuditRefuse/AuditBlock (必填)
--description string 审批说明(可选)
注意:
- status 可选值: AuditApprove(通过), AuditDelete(删除), AuditIgnore(忽略), AuditRefuse(拒绝), AuditBlock(拒绝且拉黑)
- record-id、applicant、inviter 可通过 dws chat group list-join-validations 查询获得
```
@@ -0,0 +1,10 @@
# 聊天命令路由
> `dws im` 等价于 `dws chat`。按命令族只加载一份详细参考。
| 命令族 | 详见 |
|---|---|
| 群创建/设置/成员/角色/搜索 | [`chat-group-commands.md`](./chat-group-commands.md) |
| 消息发送/回复/转发/撤回与会话文件 | [`chat-message-file-commands.md`](./chat-message-file-commands.md) |
| 置顶会话、媒体下载、共同群、会话详情 | [`chat-conversation-commands.md`](./chat-conversation-commands.md) |
| 机器人、会话分组、免打扰、红点与入群审批 | [`chat-admin-commands.md`](./chat-admin-commands.md) |
@@ -0,0 +1,139 @@
# 复合工作流
## 复合工作流
### 机器人发消息后撤回(完整流程)
`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 `--robot-code` + `--keys`)。`chat message recall` 通过 IM 接口撤回当前用户自己发出的消息(需要 `--conversation-id` + `--msg-id`)。
```bash
# Step 1: 查我的机器人 — 提取 robot-code
dws chat bot search --format json
# Step 2: 用机器人发消息 — 提取返回中的 processQueryKey
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> \
--title "通知" --text "内容" --format json
# Step 3: 用同一个 robot-code + processQueryKey 撤回
dws chat message recall-by-bot --robot-code <robot-code> --group <openconversation_id> \
--keys <processQueryKey> --format json
```
### 机器人发群消息(含机器人不在群内的处理)
机器人通过 `send-by-bot --group` 发群消息时,如果返回"机器人不存在"错误,说明该机器人尚未加入目标群,需先邀请进群再发送。
```bash
# Step 1: 查我的机器人 — 提取 robot-code
dws chat bot search --format json
# Step 2: 尝试发送,若报"机器人不存在"则执行 Step 3
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> \
--title "通知" --text "内容" --format json
# Step 3: 邀请机器人进群
dws chat group members add-bot --group <openconversation_id> --robot-code <robot-code>
# Step 4: 重新发送
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> \
--title "通知" --text "内容" --format json
```
### 给机器人发单聊消息(必须先用 find 拿 openDingTalkId)
给机器人发单聊消息时,必须先用 `chat bot find` 搜索机器人拿到 `openDingTalkId`,再用 `chat message send --open-dingtalk-id` 发送。不能用 `chat bot search`,因为 search 不返回 `openDingTalkId`。
```bash
# Step 1: 搜索机器人 — 提取 openDingTalkId(必须用 find,search 没有此字段)
dws chat bot find --query "玉澜" --format json
# Step 2: 用 openDingTalkId 发单聊消息
dws chat message send --open-dingtalk-id <openDingTalkId> --text "你好" --format json
```
### 机器人 @指定人发群消息
通过 `--at-user-ids` 传入 userId 列表或 `--at-open-dingtalk-ids` 传入 openDingtalkId 列表来 @指定成员,多个用逗号分隔。`--text` 中需包含 `@userId` 或 `@openDingtalkId` 文本(不要用尖括号,不要用姓名)。通过 `--at-all` @所有人。
```bash
# Step 1: 搜人获取 userId
dws contact user search --query "张三" --format json
# Step 2: 用 userId 发送并 @(注意 text 中 @userId)
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> \
--at-user-ids userId1,userId2 \
--title "提醒" --text "@userId1 @userId2 请查收本周报告" --format json
# 或者用 openDingtalkId 发送并 @
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> \
--at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 \
--title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告" --format json
# @所有人
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> \
--at-all --title "通知" --text "请所有人注意" --format json
```
### 发送图片 / 文件(统一一条命令)
**`dws chat message send --msg-type file --file-path <本地路径>`** 适用于所有发图片/文件场景,任意扩展名。CLI 内部完成上传与发送,无需任何前置工具调用。
```bash
# 群聊
dws chat message send --group <openConversationId> --msg-type file --file-path ./screenshot.png --format json
dws chat message send --group <openConversationId> --msg-type file --file-path ./report.pdf --format json
# 单聊(推荐 --open-dingtalk-id)
dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file-path ./screenshot.png --format json
```
**带文字说明**:在上一步发完文件后,再补一条文本消息即可。不要尝试把文字塞进 `--msg-type file` 命令(该命令不读 `--text`)。
```bash
dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file-path ./screenshot.png --format json
dws chat message send --open-dingtalk-id <openDingTalkId> --text "这是本周数据汇总" --format json
```
**旧链路(仅当上游已经持有 `dt_media_upload` 返回的 mediaId 时才用)**:
```bash
dws chat message send --group <openConversationId> --msg-type image --media-id "@lQLPD4JNnliqBq3NBQDNA8Cw" --format json
```
#### 创建并推送流式卡片 — 向群聊或单聊发送流式卡片消息
群聊传 --group,单聊传 --receiver,二者互斥。
**注意:send-card 必须和 update-card 搭配使用。** 创建卡片时无需传入内容,后续通过 update-card 更新内容,最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。
flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。
```
Usage:
dws chat message send-card [flags]
Example:
dws chat message send-card --group <openConversationId>
dws chat message send-card --receiver <openDingTalkId>
# 查询群 ID: dws chat search --query "群名"
# 查询人员: dws contact user search --keyword "姓名" --format json
Flags:
--group string 群聊 openConversationId(群聊时必填,与 --receiver 互斥)
--receiver string 单聊接收者 openDingTalkId(单聊时必填,与 --group 互斥)
```
#### 流式更新卡片内容 — 更新已发送的流式卡片内容
--biz-id 为 send-card 返回的业务 ID,--flow-status 控制流式状态。
flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成(FINISH),4=执行中(EXECUTING),5=错误(ERROR)。
**最后一次更新必须将 --flow-status 设为 3(finish),否则卡片会一直处于"生成中"的加载状态。**
```
Usage:
dws chat message update-card [flags]
Example:
dws chat message update-card --biz-id <bizId> --content "更新的卡片内容" --flow-status 2
dws chat message update-card --biz-id <bizId> --content "最终内容" --flow-status 3
Flags:
--biz-id string 卡片业务 ID (必填)
--content string 卡片消息内容 (必填)
--flow-status int 流式状态 (必填)
```
@@ -0,0 +1,27 @@
# 上下文传递表
## 上下文传递表
| 操作 | 从返回中提取 | 用于 |
|------|-------------|------|
| `chat search` | `openConversationId` | message send/list、group members 等的 --group |
| `chat group create` | `openConversationId` | 同上 |
| `chat message list-all` | `nextCursor` | 下次 list-all 的 --cursor |
| `aisearch person` | `userId` | message send 的 --user、send-by-bot 的 --users、send-by-bot 的 --at-user-ids、list-by-sender 的 --sender-user-id |
| `contact user search` | `openDingTalkId` | message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot 的 --open-dingtalk-ids、send-by-bot 的 --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id、message list 的 --open-dingtalk-id |
| `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code(仅我创建的机器人,无 openDingTalkId) |
| `chat bot find` | `openDingTalkId` | 给机器人发单聊消息(全部可用机器人,额外返回 openDingTalkId) |
| `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys |
| `chat message send` | `openTaskId` | query-send-status 的 --open-task-id |
| `chat message list` | `openMessageId` | recall 的 --msg-id |
| `chat message search` | `nextCursor` | 下次 message search 的 --cursor |
| `chat message search-advanced` | `nextCursor` | 下次 message search-advanced 的 --cursor |
| `chat search-common` | `openConversationId` | message send/list 等的 --group |
| `chat message list` | `openMsgId` | message read-status 的 --message-id |
| `chat group-role list` | `openRoleId` | group-role update/remove/set-user/remove-user 的 --role-id |
| `chat message create-text-emotion` | `emotionId` | add-text-emotion 的 --emotion-id |
| `chat category list` | `categoryId` | category list-conversations 的 --category-id |
| `chat group get-by-group-id` | `openConversationId` | 同 chat search,将群号转为 openConversationId |
| `chat message send-card` | `bizId` | update-card 的 --biz-id |
| `chat message list` | `openMessageId` | message reply 的 --ref-msg-id、message forward 的 --msg-id |
| `chat search` | `openConversationId` | set-top 的 --conversation-id、group-mute / group-mute-member 的 --group |
@@ -0,0 +1,206 @@
# 会话查询与媒体命令
### list-top-conversations (置顶会话)
#### 拉取置顶会话列表
拉取当前用户的置顶会话列表。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。
```
Usage:
dws chat list-top-conversations [flags]
Example:
dws chat list-top-conversations --limit 1000
dws chat list-top-conversations --limit 1000 --cursor <nextCursor>
Flags:
--limit int 每页返回数量(默认 1000)
--cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor)
--exclude-muted 是否排除已设置免打扰的会话(默认 false)
注意:
- 用户询问"置顶会话"时,直接调用此命令返回置顶会话列表即可
- 用户询问"置顶消息"时,需两步:先调用此命令拉取置顶会话列表获取各会话的 openConversationId,再用 `chat message list --group <openConversationId>` 分别拉取每个会话内的消息
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
```
### download-media (下载消息资源)
#### 下载消息中的资源(图片/视频/语音等)到本地
下载聊天消息中的图片、视频、语音等资源到本地文件。流程:先获取下载 URL,再 HTTP GET 下载。
```
Usage:
dws chat message download-media [flags]
Example:
dws chat message download-media --type mediaId --resource-id <mediaId> --message-id <openMessageId> --open-conversation-id <openConversationId> --output ./downloads/
dws chat message download-media --type mediaId --resource-id <mediaId> --message-id <openMessageId> --open-conversation-id <openConversationId> --output ./photo.jpg
Flags:
--type string 资源类型: mediaId (必填)
--resource-id string 资源 ID,mediaId 类型时为消息中的 mediaId 值 (必填)
--message-id string 消息 openMessageId (必填)
--open-conversation-id string 会话 openConversationId (必填)
--output string 本地保存路径,文件或目录 (必填)
注意:
- resource-id 从 `dws chat message list` 返回的消息内容中获取 mediaId
- message-id 从 `dws chat message list` 返回的 openMessageId
- open-conversation-id 从 `dws chat search` 获取 openConversationId
- --output 如果指定目录,文件名会从下载 URL 中自动推断
```
### search-common (搜索共同群)
#### 搜索共同群 — 查询指定人共同所在的群聊
根据昵称列表搜索共同群聊。--nicks 指定要搜索的人员昵称(逗号分隔,必填)。--match-mode 控制匹配模式:AND 表示所有人都在群里,OR 表示任一人在群里(默认 AND)。分页参数 --limit(默认 20)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。
```
Usage:
dws chat search-common [flags]
Example:
dws chat search-common --nicks "风雷,山乔" --limit 20 --cursor 0
dws chat search-common --nicks "天鸡,乐函" --match-mode OR --limit 20 --cursor 0
dws chat search-common --nicks "风雷,山乔,天鸡" --limit 10 --cursor <nextCursor>
Flags:
--nicks string 要搜索的昵称列表,逗号分隔 (必填)
--match-mode string 匹配模式:AND=所有人都在群里,OR=任一人在群里(默认 AND)
--limit int 每页返回数量(默认 20)
--cursor string 分页游标(默认 "0",翻页传 nextCursor)
--exclude-muted 是否排除已设置免打扰的群聊(默认 false)
注意:
- --nicks 传人员昵称(花名),逗号分隔,如 "风雷,山乔"
- --match-mode AND 表示群里必须包含所有指定的人;OR 表示包含任意一人即可
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
```
### conversation-info (获取会话基础信息)
#### 获取会话基础信息
获取指定会话的基础信息。发送本地文件消息请优先使用 `dws chat message send --msg-type file --file-path <本地文件>`,CLI 不再要求调用方获取或传递 spaceId。
```
Usage:
dws chat conversation-info [flags]
Example:
dws chat conversation-info --group <openConversationId> --format json
dws chat conversation-info --user <userId> --format json
dws chat conversation-info --open-dingtalk-id <openDingTalkId> --format json
Flags:
--group string 群聊 openConversationId(群聊时使用)
--user string 单聊对方 userId(单聊时使用)
--open-dingtalk-id string 单聊对方 openDingTalkId(单聊时使用)
注意:
- --group、--user、--open-dingtalk-id 互斥,必须且只能指定其一
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- 文件发送不再依赖调用方读取 newCSpaceIdIM;使用 `dws chat message send --msg-type file --file-path <本地文件>` 会自动上传到会话文件空间
```
#### 合并转发多条消息 — 将多条消息合并后转发到目标会话(源/目标会话均支持单聊/群聊)
```
Usage:
dws chat message combine-forward [flags]
Example:
dws chat message combine-forward --src-conversation-id <srcOpenCid> --msg-ids <id1>,<id2>,<id3> --dest-conversation-id <destOpenCid>
dws chat message combine-forward --src-conversation-id <srcOpenCid> --msg-ids <id1>,<id2> --dest-conversation-id <destOpenCid> --uuid <idempotencyKey>
Flags:
--src-conversation-id string 源会话 openConversationId (必填)
--msg-ids string 源消息 openMessageId 列表,逗号分隔 (必填)
--dest-conversation-id string 目标会话 openConversationId (必填)
--uuid string 幂等键(可选)
注意:
- 与 chat message forward 区别: forward 转单条,combine-forward 合并多条为一条转发
- --msg-ids 多个消息 ID 用逗号分隔,无顺序要求
```
#### 转发话题消息 — 将话题消息从源会话转发到目标会话
```
Usage:
dws chat message forward-topic [flags]
Example:
dws chat message forward-topic --src-msg-id <srcOpenMessageId> --src-conversation-id <srcOpenConversationId> --src-thread-id <srcOpenConvThreadId> --dest-conversation-id <destOpenConversationId>
Flags:
--src-msg-id string 源消息 openMessageId (必填,要转发的消息)
--src-conversation-id string 源会话 openConversationId (必填,消息所在的会话)
--src-thread-id string 话题 ID (必填,格式: convThread + 加密后的convThreadId)
--dest-conversation-id string 目标会话 openConversationId (必填,转发到的会话)
注意:
- 与 chat message forward 区别: forward 转发普通单条消息,forward-topic 专用于转发话题消息
- --src-thread-id 格式为 "convThread" + 加密后的 convThreadId,可通过 dws chat message list 返回的话题信息获取
```
#### 钉住某条消息(Pin) — 将指定消息设置为钉住状态
```
Usage:
dws chat message set-pin-msg [flags]
Example:
dws chat message set-pin-msg --open-conversation-id <openConversationId> --msg-id <openMessageId>
Flags:
--open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊)
--msg-id string (必填)消息 openMessageId
注意:
- 钉住消息后,会话成员均可在会话中看到被钉住的消息
```
#### 取消钉住某条消息(Unpin) — 取消指定消息的钉住状态
```
Usage:
dws chat message unset-pin-msg [flags]
Example:
dws chat message unset-pin-msg --open-conversation-id <openConversationId> --msg-id <openMessageId>
Flags:
--open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊)
--msg-id string (必填)消息 openMessageId
注意:
- 取消钉住后消息仍保留在会话中,只是不再被标记为钉住状态
```
#### 拉取某个会话中钉住的消息列表 — 拉取指定会话中被钉住的消息列表
```
Usage:
dws chat message list-pin-msg [flags]
Example:
dws chat message list-pin-msg --open-conversation-id <openConversationId>
dws chat message list-pin-msg --open-conversation-id <openConversationId> --size 50
dws chat message list-pin-msg --open-conversation-id <openConversationId> --cursor <nextCursor> --size 20
Flags:
--open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊)
--cursor string (选填)分页游标,首次不传,翻页时传上次返回的 nextCursor
--size int (选填)一次拉取的消息数量(默认 20,最大 100)
注意:
- 与 `chat message list` 区别: list-pin-msg 只返回被钉住的消息;list 拉取全部消息
- 分页: hasMore=true 时,用返回的 nextCursor 作为下次 --cursor 继续翻页
```
#### 置顶某条消息 — 将指定消息设置为置顶状态
```
Usage:
dws chat message set-top-msg [flags]
Example:
dws chat message set-top-msg --open-conversation-id <openConversationId> --msg-id <openMessageId>
Flags:
--open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊)
--msg-id string (必填)消息 openMessageId
注意:
- 置顶消息后,会话成员可在会话顶部看到该条消息
- 与 `chat set-top`(会话置顶)不同:set-top-msg 是将会话内某条消息置顶;set-top 是将整个会话在列表中置顶
```
#### 取消置顶某条消息 — 取消指定消息的置顶状态
```
Usage:
dws chat message unset-top-msg [flags]
Example:
dws chat message unset-top-msg --open-conversation-id <openConversationId> --msg-id <openMessageId>
Flags:
--open-conversation-id string (必填)会话 openConversationId(支持群聊/单聊)
--msg-id string (必填)消息 openMessageId
注意:
- 取消置顶后消息仍保留在会话中,只是不再被标记为置顶状态
```
@@ -0,0 +1,484 @@
# 群组、群身份与群搜索命令
## 命令总览
### group (群组管理)
#### 创建群 — 支持内部群/外部群/普通群/话题圈,当前登录用户自动成为群主
创建一个群聊,支持指定群名称、初始成员列表、群类型和话题模式。默认创建内部群。
```
Usage:
dws chat group create [flags]
Example:
dws chat group create --name "Q1 项目冲刺群" --users userId1,userId2,userId3
dws chat group create --name "外部合作群" --users userId1,userId2 --type EXTERNAL
dws chat group create --name "话题圈" --users userId1,userId2 --thread
# 指定他人为群主
dws chat group create --name "项目群" --users userId1,userId2 --owner <openDingTalkId>
Flags:
--name string 群名称 (必填)
--users string 成员 userId 或 openDingTalkId(可混传),用户本身会自动加入无需包含,逗号分隔,不超过20个 (必填)
--type string 群类型: INTERNAL(内部群,默认)/EXTERNAL(外部群)/NORMAL(普通群)
--thread 开启话题模式,将创建话题圈
--owner string 群主 openDingTalkId(可选,未指定时默认操作者为群主)
```
#### 查看群成员列表 — 分页查询指定群聊的成员
```
Usage:
dws chat group members [flags]
Example:
dws chat group members --id <openconversation_id>
Flags:
--cursor string 分页游标,首次从 0 开始
--id string 群 ID / openconversation_id (必填)
```
#### 添加群成员 — 向指定群聊添加成员,需传入群 ID 与用户 ID 列表
```
Usage:
dws chat group members add [flags]
Example:
dws chat group members add --id <openconversation_id> --users userId1,userId2
Flags:
--id string 群 ID / openconversation_id (必填)
--users string 要添加的用户 userId 列表,逗号分隔 (必填)
```
#### 移除群成员 — 从指定群聊中移除成员,需传入群 ID 与待移除的用户 ID 列表
```
Usage:
dws chat group members remove [flags]
Example:
dws chat group members remove --id <openconversation_id> --users userId1,userId2
Flags:
--id string 群 ID / openconversation_id (必填)
--users string 要移除的用户 userId 列表,逗号分隔 (必填)
```
#### 将机器人添加到群中 — 将自定义机器人添加到当前用户有管理权限的群聊中,如果没有权限则会报错
```
Usage:
dws chat group members add-bot [flags]
Example:
dws chat group members add-bot --robot-code <robot-code> --id <openconversation_id>
Flags:
--id string 群聊 openConversationId (必填)
--robot-code string 机器人 Code (必填)
```
#### 从群内移除机器人 — 将指定机器人从群聊中移除,需要群管理员或群主权限
```
Usage:
dws chat group members remove-bot [flags]
Example:
dws chat group members remove-bot --id <openConversationId> --bot-id <openBotId>
# 查询群 ID: dws chat search --query "群名"
# 查询群内机器人: dws chat group bots --group <openConversationId>
Flags:
--id string 群聊 openConversationId (必填)
--bot-id string 机器人 openBotId (必填)
```
#### 批量查看群成员详情 — 根据成员 openDingTalkId 列表批量查询群成员详情信息
```
Usage:
dws chat group members list-by-ids [flags]
Example:
dws chat group members list-by-ids --id <openConversationId> --users openDingTalkId1,openDingTalkId2
# 查询群 ID: dws chat search --query "群名"
# 查询 openDingTalkId: dws contact user search --query "姓名"
Flags:
--id string 群 ID / openConversationId (必填)
--users string 成员 openDingTalkId 列表,逗号分隔 (必填)
```
#### 更新群名称
```
Usage:
dws chat group rename [flags]
Example:
dws chat group rename --id <openconversation_id> --name "新群名"
Flags:
--id string 群 ID / openconversation_id (必填)
--name string 修改后的群名称 (必填)
```
#### 根据群号获取群聊信息 — 当用户只提供了数字群号而非 openConversationId 时,用此命令转换
```
Usage:
dws chat group get-by-group-id [flags]
Example:
dws chat group get-by-group-id --group-id 12345678
# 群号为数字类型的群ID
Flags:
--group-id int 群号 (必填,数字类型)
```
#### 转让群主 — 将群主身份转让给群内其他成员
```
Usage:
dws chat group transfer-owner [flags]
Example:
dws chat group transfer-owner --group <openConversationId> --new-owner <openDingTalkId>
dws chat group transfer-owner --group <openConversationId> --user <userId>
# 查询群 ID: dws chat search --query "群名"
# 查询人员: dws contact user search --keyword "姓名" --format json
Flags:
--group string 群聊 openConversationId (必填)
--new-owner string 新群主 openDingTalkId
--user string 新群主 userId
```
#### 获取群邀请链接 — 获取指定群聊的邀请加入链接
可选 --expires-seconds 指定链接有效期(秒),0 表示永久有效,不传则使用服务端默认值。
```
Usage:
dws chat group invite-url [flags]
Example:
dws chat group invite-url --group <openConversationId>
dws chat group invite-url --group <openConversationId> --expires-seconds 86400
dws chat group invite-url --group <openConversationId> --expires-seconds 0
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值
```
#### 分享群聊链接到会话 — 将指定群的邀请链接分享到另一个会话或单聊用户
--target 和 --receiver 二选一:--target 指定目标会话,--receiver 指定单聊用户。
```
Usage:
dws chat group share-invite [flags]
Example:
dws chat group share-invite --source <被分享群openConversationId> --target <目标会话openConversationId>
dws chat group share-invite --source <被分享群openConversationId> --receiver <接收者openDingTalkId>
dws chat group share-invite --source <openConversationId> --target <openConversationId> --expires-seconds 86400
# 查询群 ID: dws chat search --query "群名"
Flags:
--source string 被分享群的 openConversationId (必填)
--target string 接收分享消息的会话 openConversationId(与 --receiver 二选一)
--receiver string 接收分享消息的单聊用户 openDingTalkId(与 --target 二选一)
--expires-seconds int64 链接有效期(秒),0 表示永久有效,不传使用服务端默认值
--uuid string 消息幂等键(可选)
```
#### 退出群聊 — 当前用户退出指定群聊
```
Usage:
dws chat group quit [flags]
Example:
dws chat group quit --group <openConversationId>
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
```
#### 更新群头像 — 更新指定群聊的群头像
```
Usage:
dws chat group update-icon [flags]
Example:
dws chat group update-icon --group <openConversationId> --icon-media-id <mediaId>
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--icon-media-id string 群头像 mediaId (必填)
```
#### 更新群设置 — 更新指定群聊的设置项
--setting-key 指定设置项,--status 指定值(0=关闭,1=开启)。
支持的 settingKey:
authority、joinValidation、onlyAdminCanAtAll、searchable、addFriendForbidden、
toolbarStatus、pluginCustomizeVerify、onlyAdminCanDING、allMembersCanCreateMcsConf、
onlyAdminCanSetMsgTop、onlyAdminCanPinMsg、onlyAdminCanSendFile、
allMembersCanCreateCalendar、groupEmailDisabled、groupRedEnvelopeSwitch、
groupLiveAuthority、groupBillAuthority
```
Usage:
dws chat group update-settings [flags]
Example:
dws chat group update-settings --group <openConversationId> --setting-key searchable --status 1
dws chat group update-settings --group <openConversationId> --setting-key onlyAdminCanAtAll --status 0
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--setting-key string 群设置项 key (必填)
--status int 设置值: 0=关闭, 1=开启 (必填)
```
#### 设置用户在群内的群昵称 — 设置当前用户在指定群聊内的个人群昵称
```
Usage:
dws chat group update-nick [flags]
Example:
dws chat group update-nick --group <openConversationId> --nick "我的群昵称"
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--nick string 个人群昵称 (必填)
```
#### 设置群备注 — 设置当前用户对指定群聊的备注名称(仅自己可见)
```
Usage:
dws chat group update-alias [flags]
Example:
dws chat group update-alias --group <openConversationId> --alias-title "项目A群"
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--alias-title string 群备注标题 (必填)
```
#### 查看群内所有机器人 — 获取指定群聊中的所有机器人列表
```
Usage:
dws chat group bots [flags]
Example:
dws chat group bots --group <openConversationId>
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
```
#### 解散群聊 — 解散指定群聊,操作不可逆,需要群主权限
```
Usage:
dws chat group dismiss [flags]
Example:
dws chat group dismiss --group <openConversationId>
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
```
#### 设置新成员入群可查看历史消息选项 — 控制新加入成员可见的历史消息范围
```
Usage:
dws chat group set-history [flags]
Example:
dws chat group set-history --group <openConversationId> --option RECENT_100
dws chat group set-history --group <openConversationId> --option FORBIDDEN
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--option string 可见范围: FORBIDDEN | RECENT_100 | ALL (必填)
注意:
- FORBIDDEN:禁止查看历史消息(默认安全策略)
- RECENT_100:可查看最近 100 条消息(最常用)
- ALL:可查看全部历史消息(开放性最高)
```
#### 拉取我创建/管理的群 — 查询当前用户作为群主或管理员的群列表
> 重要限制:此命令只能拉取到用户作为「群主」或「管理员」的群,无法获取普通成员身份的群聊。如果用户要找的群不在结果中,必须改用 `dws chat search --query "关键词"` 按名称搜索。
可通过 --role 过滤角色:OWNER 仅群主、ADMIN 仅管理员,不传则返回全部。可通过 --limit 限制返回数量,不传则返回所有符合条件的群。
```
Usage:
dws chat group list-my-groups [flags]
Example:
dws chat group list-my-groups
dws chat group list-my-groups --role OWNER
dws chat group list-my-groups --role ADMIN --limit 10
Flags:
--role string 角色过滤: OWNER(仅群主) / ADMIN(仅管理员),不传返回全部
--limit int 最多返回群数量,不传返回全部
--exclude-muted 是否排除已设置免打扰的群聊(默认 false)
注意:
- 底层先拉取最近 1000 条会话,剔除单聊和话题圈后筛选出群主/管理员的群
- 内部群会校验 orgId 归属
- 不传 --role 时返回群主 + 管理员的所有群
```
#### 发布群公告 — 在指定群聊中发布群公告,支持 Markdown、定时发布
正文为 Markdown 格式。支持标题、加粗、斜体、删除线、行内代码、链接、代码块、有序/无序/任务列表、表格、引用、分割线、图片、段落、换行。
```
Usage:
dws chat group notice create [flags]
Example:
dws chat group notice create --group <openConversationId> --content "今晚 22 点系统维护,请提前保存工作内容"
dws chat group notice create --group <openConversationId> --content "# 重要通知\n请大家查收" --sticky --send-ding
dws chat group notice create --group <openConversationId> --content "明早九点例会" --run-at "2026-07-03T09:00:00+08:00"
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--content string 公告正文,Markdown 格式 (必填)
--sticky 是否吊顶置顶(默认 false)
--send-ding 是否发 DING 提醒(默认 false)
--run-at string 定时发布时间 ISO-8601(如 2026-07-03T09:00:00+08:00,传入则定时发布)
注意:
- 下划线/字体色/背景色/字号为编辑器专属能力,Markdown 无对应语法,无法通过本命令表达
- --run-at 建议带时区偏移,不带偏移时按北京时区处理
```
#### 修改群公告 — 整体替换指定群公告的内容
```
Usage:
dws chat group notice edit [flags]
Example:
dws chat group notice edit --group <openConversationId> --notice-id <dataId> --content "更新后的公告内容"
dws chat group notice edit --group <openConversationId> --notice-id <dataId> --content "更新后的公告内容" --sticky --send-ding
# 查询公告 ID: dws chat group notice list --group <openConversationId>
Flags:
--group string 群聊 openConversationId (必填)
--notice-id string 群公告 dataId (必填)
--content string 公告新正文,Markdown 格式 (必填)
--sticky 是否吊顶置顶(不传按 false 处理)
--send-ding 是否发 DING 提醒(默认 false)
注意:
- 修改会整体替换原公告正文,需传入完整的新内容
```
#### 查看群公告详情 — 查询单条群公告的详细信息
```
Usage:
dws chat group notice get [flags]
Example:
dws chat group notice get --group <openConversationId> --notice-id <dataId>
# 查询公告 ID: dws chat group notice list --group <openConversationId>
Flags:
--group string 群聊 openConversationId (必填)
--notice-id string 群公告 dataId (必填)
注意:
- 返回正文摘要、吊顶状态、发布者、已读人数/应收人数、点赞/评论数、是否可编辑、是否已读、是否定时公告等信息
```
#### 查看群公告列表 — 分页拉取指定群聊的群公告列表
```
Usage:
dws chat group notice list [flags]
Example:
dws chat group notice list --group <openConversationId>
dws chat group notice list --group <openConversationId> --limit 20 --cursor <nextPageCursor>
dws chat group notice list --group <openConversationId> --scheduled
# 查询群 ID: dws chat search --query "群名"
Flags:
--group string 群聊 openConversationId (必填)
--limit int 每页返回数量(默认 10,最大 100)
--cursor string 分页游标(首次不传,翻页传返回的 nextPageCursor)
--scheduled 是否查询定时公告列表(默认 false,查询已发布公告)
注意:
- 分页: hasMore=true 时用返回的 nextPageCursor 作为下次 --cursor
- 默认查询已发布公告,传 --scheduled 查询尚未到发布时间的定时公告
```
### group-role (群身份管理)
#### 查看群身份列表 — 拉取指定群聊的自定义群身份列表
```
Usage:
dws chat group-role list [flags]
Example:
dws chat group-role list --group <openConversationId>
Flags:
--group string 群聊 openConversationId (必填)
```
#### 添加群身份 — 在指定群中创建一个新的自定义群身份
```
Usage:
dws chat group-role add [flags]
Example:
dws chat group-role add --group <openConversationId> --name "管理员"
Flags:
--group string 群聊 openConversationId (必填)
--name string 群身份名称 (必填)
```
#### 更新群身份名称 — 修改指定群身份的名称
```
Usage:
dws chat group-role update [flags]
Example:
dws chat group-role update --group <openConversationId> --role-id <openRoleId> --name "新名称"
Flags:
--group string 群聊 openConversationId (必填)
--role-id string 群身份 openRoleId,由 group-role list 返回 (必填)
--name string 群身份新名称 (必填)
```
#### 删除群身份 — 删除指定群聊中的某个自定义群身份
```
Usage:
dws chat group-role remove [flags]
Example:
dws chat group-role remove --group <openConversationId> --role-id <openRoleId>
Flags:
--group string 群聊 openConversationId (必填)
--role-id string 群身份 openRoleId,由 group-role list 返回 (必填)
```
#### 设置用户群身份 — 覆盖指定用户在群中的全部群身份(传空则清除所有身份)
```
Usage:
dws chat group-role set-user [flags]
Example:
dws chat group-role set-user --group <openConversationId> --user <userId> --role-ids roleId1,roleId2
# 查询人员: dws contact user search --keyword "姓名" --format json
# 查询 role-id: dws chat group-role list --group <openConversationId>
Flags:
--group string 群聊 openConversationId (必填)
--user string 用户 userId(必填)
--role-ids string 群身份 openRoleId 列表,逗号分隔 (必填),传空字符串则清除该用户所有群身份
```
#### 移除用户的指定群身份 — 从用户身上移除指定的群身份(不影响其他群身份)
```
Usage:
dws chat group-role remove-user [flags]
Example:
dws chat group-role remove-user --group <openConversationId> --user <userId> --role-ids roleId1,roleId2
Flags:
--group string 群聊 openConversationId (必填)
--user string 用户 userId(必填)
--role-ids string 要移除的群身份 openRoleId 列表,逗号分隔 (必填)
```
#### 查询群成员的群身份 — 查询指定群成员当前持有的所有群身份
```
Usage:
dws chat group-role query-user [flags]
Example:
dws chat group-role query-user --group <openConversationId> --user <userId>
Flags:
--group string 群聊 openConversationId (必填)
--user string 用户 userId(必填)
```
### search (搜索群聊)
#### 根据关键词搜索群聊 — 分页返回匹配群聊列表
hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。
**注意:**
1. query 不要拆分得太细,应使用群名称中连续的核心词作为关键词(如群名"项目冲刺群"应搜"项目冲刺"而非拆成"项目"+"冲刺"分别搜索)。
2. 当搜索结果返回多个群聊时,应列出候选群让用户确认目标群聊,不要自行假定并直接进行后续操作。
```
Usage:
dws chat search [flags]
Example:
dws chat search --query "项目冲刺"
dws chat search --query "项目冲刺" --limit 20 --cursor 0
Flags:
--query string 搜索关键词 (必填)
--limit int 每页返回数量(默认 20)
--cursor string 分页游标(默认 "0",翻页传 nextCursor)
--exclude-muted 是否排除已设置免打扰的群聊(默认 false)
```
@@ -0,0 +1,13 @@
# 聊天能力索引
| 主题 | 详见 |
|---|---|
| 群创建/设置/成员/角色/搜索 | [`chat-group-commands.md`](./chat-group-commands.md) |
| 消息发送/回复/转发/撤回与会话文件 | [`chat-message-file-commands.md`](./chat-message-file-commands.md) |
| 会话查询、媒体下载、共同群 | [`chat-conversation-commands.md`](./chat-conversation-commands.md) |
| 机器人、会话分组、免打扰、红点与入群审批 | [`chat-admin-commands.md`](./chat-admin-commands.md) |
| 意图与工作流 | [`chat-intent.md`](./chat-intent.md) · [`chat-workflow.md`](./chat-workflow.md) · [`chat-composite.md`](./chat-composite.md) |
| 上下文、注意事项与相关产品 | [`chat-context.md`](./chat-context.md) · [`chat-notes.md`](./chat-notes.md) · [`chat-related.md`](./chat-related.md) |
| 表情/表情列表 | [`chat-emoji-list.md`](./chat-emoji-list.md) |
| 通用消息流程 | [`01-messaging.md`](./01-messaging.md) |
| 轻量流程 | [`lite-recipes.md`](./lite-recipes.md) |
@@ -0,0 +1,163 @@
# 意图判断
## 意图判断
用户说"我特别关注的人最近发了什么消息/关注的人最近聊了啥/星标联系人最近的动态" → `chat message list-focused`(零参数一行命令)
用户说"某人发给我的消息/指定发送者的消息/某人最近的消息" → `chat message list-by-sender --sender-user-id <userId>` 或 `--sender-open-dingtalk-id <openDingTalkId>`(跨单聊+群聊)
用户说"和某人的单聊聊天记录/拉某人单聊历史" → `chat message list --user <userId>` 或 `--open-dingtalk-id <openDingTalkId>`
用户说"某个群的聊天记录" → `chat message list --group <openConversationId>`
用户说"我最近所有消息/我今天的消息" → `chat message list-all --start <ISO> --end <ISO>`
用户说"@我的消息/提及我的" → `chat message list-mentions --start <ISO> --end <ISO>`
用户说"搜索消息里的关键词/包含XX的消息" → `chat message search-advanced --query "<关键词>"`(首选,严格超集)
用户说"我和某人的共同群" → `chat search-common --nicks "<昵称1>,<昵称2>"`
用户说"未读会话列表" → `chat message list-unread-conversations`
用户说"群里某条话题的回复" → `chat message list-topic-replies --group <id> --topic-id <id>`
用户说"回复话题/往话题里发消息" → 先用 `chat message list` 拉取获得 openConvThreadId,再 `chat message send --group <openConvThreadId> --text "..."`
用户说"置顶会话/查看置顶" → `chat list-top-conversations` 列会话 → 再 `chat message list --group <id>` 拉消息(两步)
用户说"置顶某条消息/把这条消息置顶/消息置顶" → `chat message set-top-msg`
用户说"取消置顶消息/取消消息置顶" → `chat message unset-top-msg`
用户说"建群/创建群聊" → `chat group create`
用户说"建外部群/创建外部群" → `chat group create --type EXTERNAL`
用户说"建普通群" → `chat group create --type NORMAL`
用户说"创建话题圈/建话题群" → `chat group create --thread`
用户说"建群并指定群主/让某某当群主" → `chat group create --owner <openDingTalkId>`
用户说"搜索群/找群" → `chat search`
用户说"我创建的群/我管理的群/我是群主的群/我当管理员的群" → `chat group list-my-groups`
用户说"群成员/看群里有谁" → `chat group members`
用户说"拉人进群/加群成员" → `chat group members add`
用户说"踢人/移除群成员" → `chat group members remove`
用户说"加机器人到群" → `chat group members add-bot`
用户说"批量查群成员详情/按ID查群成员/查看指定成员信息" → `chat group members list-by-ids`
用户说"改群名" → `chat group rename`
用户说"聊天记录/会话消息/拉取会话" → `chat message list`
用户说"某人发给我的消息/指定发送者/某人的消息" → `chat message list-by-sender`(用户未明确说"单聊"时优先使用,跨单聊/群聊)
用户说"拉取和某人的单聊记录/单聊消息" → `chat message list --user`(用户明确说"单聊"时使用)
用户说"@我的消息/at我的/提及我的" → `chat message list-mentions`
用户说"未读消息会话/未读会话列表/我的未读会话" → `chat message list-unread-conversations`
用户说"发群消息(以个人身份)" → `chat message send --group`
用户说"发单聊消息(以个人身份)" → `chat message send --user`(有 userId 时)或 `chat message send --open-dingtalk-id`(有 openDingTalkId 时)
用户说"机器人发消息/机器人群发" → `chat message send-by-bot`
用户说"撤回我发的消息/撤回消息" → `chat message recall`(通过 IM 接口撤回当前用户自己发出的消息,需要 openConversationId + openMessageId)
用户说"撤回机器人发的消息/机器人撤回消息" → `chat message recall-by-bot`(通过机器人接口撤回机器人发出的消息,需要 robot-code + processQueryKey)
用户说"Webhook 发消息/告警消息" → `chat message send-by-webhook`
用户说"话题回复/群话题消息回复/拉取话题回复" → `chat message list-topic-replies`
用户说"所有消息/全部会话消息/拉取全部消息/时间范围内消息/我的消息/我今天的消息/查我的钉钉消息/最近的消息" → `chat message list-all`
用户说"特别关注人的消息/关注的人的消息/星标联系人的消息" → `chat message list-focused`
用户说"消息已读未读/谁看了消息/查读状态/消息读取状态" → `chat message read-status`
用户说"查看我的机器人" → `chat bot search`
用户说"搜索消息/查找关键词/搜一下消息里的XX" → 优先使用 `chat message search-advanced`(推荐首选,严格超集);仅在简单关键词搜索且无其他维度需求时可用 `chat message search`
用户说"多维度搜索/按发送者搜索/按人搜消息/指定多个群搜索/@我的消息搜索" → `chat message search-advanced`(推荐首选,支持多维度组合搜索)
用户说"查询消息发送状态/消息发没发成功/消息状态" → `chat message query-send-status`
用户说"我和XX的共同群/我们都在哪些群/查共同群" → `chat search-common`
用户说"置顶会话/我的置顶/查看置顶会话" → `chat list-top-conversations`
用户说"置顶某条消息/把这条消息置顶" → `chat message set-top-msg`
用户说"取消置顶消息/取消消息置顶" → `chat message unset-top-msg`
用户说"查看会话分组/自定义分组" → `chat category list`
用户说"某个分组下的会话/分组会话列表" → `chat category list-conversations`
用户说"创建会话分组/新建分组/添加分组" → `chat category create`
用户说"删除会话分组/移除分组" → `chat category delete`
用户说"重命名分组/修改分组名称/更新分组名" → `chat category rename`
用户说"把会话加到分组/将群移入分组/添加会话到分组" → `chat category add-conv`
用户说"把会话从分组移出/将群从分组移除/从分组中删除会话" → `chat category remove-conv`
用户说"根据群号查群信息/群号查群/群号转openConversationId" → `chat group get-by-group-id`(当用户发消息时只提供了群号,用此工具将群号转为 openConversationId,再调用发消息接口)
用户说"查看群身份/群的自定义身份列表" → `chat group-role list`
用户说"创建/添加群身份" → `chat group-role add`
用户说"修改/更新群身份名称" → `chat group-role update`
用户说"删除群身份" → `chat group-role remove`
用户说"给某人设置群身份/设定用户的群身份" → `chat group-role set-user`
用户说"移除某人的群身份/撤销群身份" → `chat group-role remove-user`
用户说"查询某人的群身份/某人在群里有什么身份" → `chat group-role query-user`
用户说"转让群主/换群主/群主转让" → `chat group transfer-owner`
用户说"群邀请链接/入群链接/加群链接" → `chat group invite-url`
用户说"分享群链接/把群分享给某人/群链接发到某群" → `chat group share-invite`
用户说"创建智能分组/新建智能会话分组" → `chat category create-smart`
用户说"批量查消息/按ID查消息/根据消息ID查" → `chat message list-by-ids`
用户说"emoji回应/表情回应/给消息加表情" → `chat message add-emoji`
用户说"取消emoji回应/移除表情回应" → `chat message remove-emoji`
用户说"文字表情回应/添加文字表情" → `chat message add-text-emotion`
用户说"取消文字表情回应/移除文字表情" → `chat message remove-text-emotion`
用户说"创建文字表情/新建文字表情" → `chat message create-text-emotion`
用户说"免打扰/消息免打扰/静音/开启免打扰/关闭免打扰" → `chat mute`
用户说"引用回复/回复消息/引用消息回复" → `chat message reply`
用户说"转发消息/转发一条消息/把消息转发到另一个群" → `chat message forward`
用户说"合并转发/批量转发/合并转发多条消息" → `chat message combine-forward`
用户说"转发话题/转发话题消息/话题转发到另一个群" → `chat message forward-topic`
用户说"群机器人列表/群里有哪些机器人/查看群机器人" → `chat group bots`
用户说"从群里移除机器人/踢出机器人" → `chat group members remove-bot`
用户说"搜索机器人/找机器人/查机器人/帮我找XXX机器人" → `chat bot find`(全部可用机器人,额外返回 openDingTalkId 可发单聊)
用户说"给机器人发单聊/给机器人发消息/跟机器人聊天" → 必须先 `chat bot find`(拿 openDingTalkId)→ 再 `chat message send --open-dingtalk-id`(search 没有 openDingTalkId,无法发单聊)
用户说"我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" → `chat bot search`(仅我创建的机器人,无 openDingTalkId)
用户说"解散群/解散群聊" → `chat group dismiss`
用户说"设置历史消息/新成员看历史/新成员可见消息" → `chat group set-history`
用户说"置顶会话/取消置顶/会话置顶" → `chat set-top`(设置/取消置顶),`chat list-top-conversations`(查看置顶列表)
用户说"全员禁言/群禁言/解除禁言" → `chat group-mute`
用户说"禁言某人/指定成员禁言/解除某人禁言" → `chat group-mute-member`
用户说"设管理员/取消管理员/设置群管理员" → `chat group set-admin`
用户说"改群昵称/设置群昵称/我在群里的名字" → `chat group update-nick`
用户说"群备注/给群加备注/修改群备注" → `chat group update-alias`
用户说"隐藏会话/隐藏群聊/隐藏对话" → `chat hide`
用户说"关闭@所有人通知/屏蔽@所有人/不接收@all" → `chat mute-at-all`
用户说"开启@所有人通知/恢复@所有人提醒" → `chat mute-at-all --off`
用户说"关闭红包通知/屏蔽红包/不接收红包提醒" → `chat mute-red-envelope`
用户说"开启红包通知/恢复红包提醒" → `chat mute-red-envelope --off`
关键区分:
- `chat search` — 搜**群/会话名**返回 `openConversationId`,**不**搜消息内容;要搜消息内容请用 `chat message search-advanced`(首选)/ `chat message search` / `list-by-sender` / `list-all`,**勿混淆**
- `chat message list` — 拉取指定会话的消息(需指定 --group 或 --user),按时间点 + 方向翻页
- `chat message list --user` — list 的单聊模式,拉取与指定用户的单聊记录(用户明确说"单聊""私聊"时使用)
- `chat message list-by-sender` — 搜索指定发送者发给我的消息,跨所有会话(单聊+群聊均包含,用户只说"某人发的消息"时优先使用)
- `chat message list-mentions` — 拉取 @我 的消息(跨单聊/群聊,可选指定群)
- `chat message list-unread-conversations` — 拉取当前用户存在未读消息的会话列表(可选 `--count`)
- `chat message read-status` — 查询指定消息的已读/未读状态(仅消息发送者可查询自己发的消息,需指定 --group 和 --message-id,可选 --target-open-dingtalk-ids 查特定人)
- `chat message list-all` — 拉取当前用户所有会话的消息,按时间范围 + cursor 分页。只要用户没有指定某个具体的会话(如某个群名、某个人名),即使提到"单聊消息""群聊消息"等笼统范围,也应路由到此命令
- `chat message list-topic-replies` — 拉取群话题的回复消息列表
- `chat message list-focused` — 拉取特别关注人的消息,cursor 分页
- `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"时路由到此),cursor 分页
- `chat message send` — 以当前用户身份发消息(群聊或单聊),text 为位置参数;发图片/本地文件统一用 `--msg-type file --file-path <本地路径>`(任意扩展名一条命令直发);`--msg-type image --media-id` 仅作旧链路兼容(上游已持有 mediaId 时使用)
- `chat message search` — 按关键词搜索消息内容(跨所有会话,可选指定群)
- `chat search-common` — 搜索共同群,查询指定人共同所在的群聊(AND=所有人都在,OR=任一人在)
- `chat message send-by-bot` — 以**机器人**身份发消息(群聊或单聊),text 为 --text flag
- `chat message send-by-webhook` — 通过**自定义机器人 Webhook** 发群消息
- `chat message recall-by-bot` — 通过**机器人接口**撤回机器人发出的消息,需要 `--robot-code` + `--keys`(发送时返回的 processQueryKey);传 `--group` 为群聊撤回,不传为单聊撤回
- `chat message recall` — 通过 **IM 接口**撤回当前用户自己发出的消息,需要 `--conversation-id`(openConversationId)+ `--msg-id`(openMessageId,可通过 `chat message list` 获取);群聊单聊均通过 `--conversation-id` 区分
- `chat message query-send-status` — 查询个人发送的消息的发送状态(需 send 返回的 openTaskId)
- `chat message search-advanced` — 多维度搜索消息(支持关键词、发送者、@我、@指定人、多个会话等维度组合,与 `search` 的区别:`search` 仅支持关键词且必填,`search-advanced` 所有参数均可选)
- `chat message list-by-ids` — 根据消息 ID 批量查询消息(最多 50 条)
- `chat message add-emoji` / `remove-emoji` — 对消息添加/移除 emoji 表情回应
- `chat message add-text-emotion` / `remove-text-emotion` — 对消息添加/移除文字表情回应
- `chat message create-text-emotion` — 创建文字表情模板,返回 emotionId 供 add-text-emotion 使用
- `chat category list` — 获取用户自定义会话分组列表
- `chat category list-conversations` — 拉取指定分组下的会话列表
- `chat category create` — 创建用户自定义会话分组
- `chat category delete` — 删除用户自定义会话分组
- `chat category rename` — 更新用户自定义会话分组的名称
- `chat category add-conv` — 将会话移动到指定的自定义分组中
- `chat category remove-conv` — 将会话从指定的自定义分组中移出
- `chat mute` — 开启/关闭会话消息免打扰(默认开启,--off 关闭)
- `chat group transfer-owner` — 转让群主
- `chat group invite-url` — 获取群邀请链接
- `chat group share-invite` — 分享群聊链接到会话(--target 指定目标会话,--receiver 指定单聊用户,二选一)
- `chat category create-smart` — 创建智能会话分组(可指定群名称关键词和成员作为匹配规则)
- `chat message reply` — 引用回复消息(在群聊中引用某条消息并回复文字)
- `chat message forward` — 转发单条消息(将一条消息从源会话转发到目标会话)
- `chat set-top` — 设置/取消会话置顶(默认置顶,--off 取消)
- `chat message set-top-msg` — 置顶会话内某条消息(与 chat set-top 不同:后者是会话列表置顶,前者是消息置顶)
- `chat message unset-top-msg` — 取消会话内某条消息的置顶状态
- `chat group-mute` — 全员禁言/取消全员禁言(默认禁言,--off 取消)
- `chat group-mute-member` — 指定群成员禁言/取消禁言(需指定 --users 和 --mute-time)
- `chat group set-admin` — 设置/取消群管理员(默认设为管理员,--off 取消)
- `chat group update-nick` — 设置用户在群内的群昵称
- `chat group update-alias` — 设置群备注(仅自己可见)
- `chat hide` — 在会话列表中隐藏会话(支持单聊/群聊,收到新消息时重新出现)
- `chat mute-at-all` — 关闭/开启 @所有人消息提醒(默认关闭,--off 恢复)
- `chat mute-red-envelope` — 关闭/开启红包消息提醒(默认关闭,--off 恢复)
## openDingTalkId 获取方式
多个命令参数需要 openDingTalkId(如 --open-dingtalk-id、--at-open-dingtalk-ids、--sender-open-dingtalk-id),统一获取方式如下:
1. 若知道姓名:`dws contact user search --query "姓名"` → 直接从结果中获取 openDingTalkId
2. 若只有 userId:先 `dws contact user get --ids <userId>` 获取姓名 → 再 `dws contact user search --query "姓名"` 获取 openDingTalkId
openDingTalkId 为当前用户视角下的目标用户唯一标识,不可跨用户共享。
@@ -0,0 +1,625 @@
# 消息与会话文件命令
### message (会话消息管理)
#### 拉取会话消息内容 — 拉取指定群聊或单聊的会话消息内容
--group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。推荐使用 --direction 控制时间方向:newer 表示从给定时间往现在拉,older 表示从给定时间往以前拉。--forward 为旧兼容参数:true 等价 newer,false 等价 older。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。引用回复消息会带 `quotedMessage`;被引用的原消息是合并转发或图片时,类型与内容也会保留在引用上下文中。
```
Usage:
dws chat message list [flags]
Example:
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00"
dws chat message list --user <userId> --time "2025-03-01 00:00:00" --limit 50
dws chat message list --open-dingtalk-id <openDingTalkId> --time "2025-03-01 00:00:00" --limit 50
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00" --direction newer
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00" --direction older
Flags:
--direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐)
--forward 旧兼容参数: true 等价 --direction newer,false 等价 --direction older (default true)
--group string 群聊 openconversation_id(群聊时必填)
--limit int 返回数量,不传则不限制
--time string 开始时间,格式: yyyy-MM-dd HH:mm:ss (必填)
--user string 单聊用户 userId(单聊时与 --open-dingtalk-id 二选一)
--open-dingtalk-id string 单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景)
注意:
- --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id
- --user 和 --open-dingtalk-id 都是发起单聊消息拉取,区别在于用不同格式的用户标识:
- --user 传 userId(企业内部应用常用)
- --open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用)
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- 时间方向:优先使用 --direction newer/older;仅为兼容旧调用保留 --forward。两者同时传入且语义冲突时会报错
- 翻页:hasMore=true 时,用结果中的边界 createTime 作为下次 --time
- 处理引用回复时读取 `quotedMessage`,不要只看回复正文;合并转发与图片引用的原消息内容也在该上下文中
- 话题圈消息拉取流程:如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息。要获取完整的话题内容,需要两步操作:(1) 先通过 dws chat message list 拉取话题主消息(即话题帖子本身);(2) 再调用 dws chat message list-topic-replies --group <openConversationId> --topic-id <openConvThreadId> 分页拉取该话题下的所有回复消息。只有话题主消息 + 回复列表合在一起,才是一条话题的完整内容。
```
#### 以当前用户身份发送消息 — --group 群聊 / --user 或 --open-dingtalk-id 单聊
**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。**
--group 指定群聊 openConversationId 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。纯文本/Markdown 单聊传 --user 时直接走 userId 发送能力,不需要先手动查询 openDingTalkId。推荐使用 --text flag 传递消息内容(也支持位置参数)。可选 --title 作为消息标题。
若用户只提供了数字群号而非 openConversationId,需先调用 `chat group get-by-group-id` 将群号转为 openConversationId,再传入 --group。
--群聊时可选 --at-all @所有人,或 --at-open-dingtalk-ids 指定成员(仅群聊时生效)。
**发送图片或本地文件 — 一条命令直发**
收到本地路径就直接传给 `--file-path`,CLI 自动完成上传与发送,**无需任何前置工具调用**(不要再走 `dt_media_upload` / `extract_media_id.py` / `drive upload` 任何前置链路)。`.png/.jpg/.pdf/.mp4/.zip` 等任意类型一律:
```bash
# 群聊
dws chat message send --group <openConversationId> --msg-type file --file-path /local/path/to/file.png --format json
# 单聊(推荐 --open-dingtalk-id,userId 也可)
dws chat message send --open-dingtalk-id <openDingTalkId> --msg-type file --file-path /local/path/to/file.png --format json
```
> 单条命令完成上传 + 发送,没有"先 dt_media_upload 再 send"两步流程。带文字说明再额外发一条 `--text "..."` 即可。
```
Usage:
dws chat message send [flags] [<text>]
Example:
# 文本/Markdown
dws chat message send --group <openconversation_id> --text "hello"
dws chat message send --user <userId> --text "请查收"
dws chat message send --open-dingtalk-id <openDingTalkId> --text "请查收"
dws chat message send --group <openconversation_id> --title "周报提醒" --text "请大家本周五前提交周报"
# 幂等发送(24h 内相同 uuid 不重复投递)
dws chat message send --group <openconversation_id> --text "hello" --uuid "unique-id-123"
# 话题回复(拉消息返回的 openConvThreadId 作为 --group 传入)
dws chat message send --group <openConvThreadId> --text "回复话题内容"
# @ 群成员
dws chat message send --group <openconversation_id> --at-all "<@all> 请大家注意"
dws chat message send --group <openconversation_id> --at-open-dingtalk-ids odt1,odt2 "<@odt1> <@odt2> 请查收"
# 发送本地图片(直接传路径,CLI 自动上传并发送)
dws chat message send --group <openconversation_id> --msg-type file --file-path ./screenshot.png
# 发送本地文件(同一条命令,任意扩展名)
dws chat message send --group <openconversation_id> --msg-type file --file-path ./report.pdf
# 旧链路(仅当上游已用 dt_media_upload 拿到 mediaId 时使用)
dws chat message send --group <openconversation_id> --msg-type image --media-id <mediaId>
# 发送位置消息(需先通过 dt_media_upload 上传地图缩略图获取 mediaId)
dws chat message send --group <openconversation_id> --msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --map-thumbnail-url "@mediaId"
# 分享联系人名片
dws chat message send --group <openconversation_id> --msg-type profile --contact-id <openDingTalkId>
Flags:
--text string 消息内容(推荐使用,也可用位置参数)
--group string 群聊 openconversation_id(群聊时必填)
--user string 单聊接收人 userId(单聊时与 --open-dingtalk-id 二选一)
--open-dingtalk-id string 单聊接收人 openDingTalkId(单聊时与 --user 二选一)
--title string 消息标题(可选,默认「消息」)
--at-all @所有人(仅群聊时生效,可选,默认 false)
--at-open-dingtalk-ids string @指定成员的 openDingTalkId 列表,逗号分隔(仅群聊时生效,可选)
--media-id string 图片 mediaId(仅旧链路:`--msg-type image` 时使用;新场景请直接用 `--msg-type file --file-path`)
--msg-type string 消息类型: image/file/location/profile(推荐统一用 `file --file-path`;image+media-id 仅作旧链路兼容)
--dentry-id int64 旧链路兼容:文件 dentryId(与 --space-id 成对传入时跳过自动上传)
--space-id int64 旧链路兼容:空间 ID(与 --dentry-id 成对传入时跳过自动上传)
--file-name string 旧链路兼容:文件名
--file-type string 旧链路兼容:文件类型/扩展名
--file-path string 本地文件路径(msgType=file 时可直接上传发送;旧链路中作为 content.filePath)
--file-size int64 旧链路兼容:文件大小,单位字节
--uuid string 幂等 UUID,相同 uuid 在 24h 内不会重复发送(可选)
--latitude string 纬度,如 30.271321(msgType=location 时必填)
--longitude string 经度,如 120.007878(msgType=location 时必填)
--location-name string 地址名称,如 阿里集团-钉钉(msgType=location 时必填)
--map-thumbnail-url string 地图缩略图 mediaId,格式 @mediaId(msgType=location 时必填,需先通过 dt_media_upload 上传图片获取)
--contact-id string 要分享的联系人 openDingTalkId(msgType=profile 时必填)
注意:
- --text 和位置参数二选一,--text 优先
- --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id
- **话题回复**:话题圈拉消息(`chat message list`)返回的 openConvThreadId 可直接作为 --group 的值传入,即可往该话题内发送回复消息。注意 openConvThreadId 仅从消息列表接口的返回值中获取,禁止自行拼接或猜测
- 纯文本/Markdown 单聊发送时 `--user` 和 `--open-dingtalk-id` 都可用;传 `--user` 时直接走 userId 发送能力
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- --at-all 和 --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;当设置--at-all时,消息内容中一定要包含对应的占位符<@all>;当设置--at-open-dingtalk-ids openDingTalkId1,openDingTalkId2时,消息内容中一定要包含对应格式的占位符<@openDingTalkId1> <@openDingTalkId2>
- **换行符**:消息内容按 Markdown 渲染,换行有两层要求,缺一不可:
1. 必须使用**真实换行符**(Unicode `U+000A`),而非字面量字符串 `\n`(反斜杠 + 字母 n)。程序或大模型构造参数时,须确保已正确反转义;否则全部内容会渲染在同一行
2. Markdown 规范下**单个换行不产生换行效果**。需要换行时请使用:段落分隔(连续两个真实换行符 `\n\n`)、行尾两个空格 + 真实换行符(硬换行 `<br>`),或直接写 HTML 的 `<br>` 标签
- **发图片/文件统一走 `--msg-type file --file-path <本地路径>`**(任意扩展名 png/jpg/pdf/mp4/zip…),CLI 内部一条命令完成上传 + 发送,无需 `dt_media_upload` / `extract_media_id` / `drive upload` 等前置步骤
- 旧链路兼容:仅当上游已经通过 `dt_media_upload` 拿到 `@lQL...` 形式的 mediaId 时,才使用 `--msg-type image --media-id`;新代码与新指引一律用 file-path 路径
- 富媒体消息的单聊优先使用 `--open-dingtalk-id`;传 `--user` 时 CLI 会尝试解析成 openDingTalkId 后发送
- --uuid 用于幂等发送,传入相同 uuid 在 24h 内不会重复投递消息(可选,群聊和单聊均支持)
- **发送位置消息**:`--msg-type location --latitude <纬度> --longitude <经度> --location-name <地址名称> --map-thumbnail-url @mediaId`;地图缩略图需先通过 `dt_media_upload` 上传图片获取 mediaId
- **分享联系人名片**:`--msg-type profile --contact-id <openDingTalkId>`;将指定联系人的名片分享到群聊或单聊
```
#### 查询消息发送状态 — 查询以当前用户身份发送的消息的发送状态
查询以当前用户身份发送的消息的发送状态。需要传入发送消息时返回的 openTaskId。
```
Usage:
dws chat message query-send-status [flags]
Example:
dws chat message query-send-status --open-task-id <openTaskId>
# openTaskId 由 dws chat message send 返回
Flags:
--open-task-id string 消息发送任务 ID (必填)
注意:
- openTaskId 由 `dws chat message send` 发送消息成功后返回
- 用于确认消息是否已成功发送或获取发送失败的原因
```
#### 撤回消息 — 撤回自己或他人的消息
撤回消息。支持撤回自己发送的消息,也支持在群聊中以群主/管理员身份撤回他人的消息。与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户消息,`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey)。
```
Usage:
dws chat message recall [flags]
Example:
dws chat message recall --conversation-id <openConversationId> --msg-id <openMessageId>
# 群主/管理员撤回他人消息:直接传目标消息 ID 即可
# 查询会话 ID: dws chat search --query "群名"
# 消息 ID 可通过 dws chat message list 获取
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat)
--msg-id string 消息 openMessageId (必填)
注意:
- --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id)
- 消息 ID 可通过 `dws chat message list` 命令获取
- 支持撤回自己发出的消息,也支持群主/管理员撤回群内他人的消息
- 与 `recall-by-bot` 的区别:本命令通过 IM 接口撤回用户消息(需要 openConversationId + openMessageId),`recall-by-bot` 通过机器人接口撤回机器人发出的消息(需要 robot-code + processQueryKey)
```
#### 机器人发送消息(--group 群聊 / --users 单聊)
**重要:该接口会真实发送消息到目标会话,不可用于测试或试探性调用。调用前必须确认消息内容和接收对象无误。**
群聊:传 --group 指定群;单聊:传 --users 指定用户列表,二者只能选其一,不能同时指定。--text 支持 Markdown。群聊时可选 --at-user-ids @指定成员。
如果用户明确要求"用机器人/机器人身份/robot"发送,必须使用本命令,严禁改用 `chat message send` 以当前用户身份发送。
**重要**:机器人发群消息前,必须确认该机器人已在目标群中。若机器人不在群内会报错"机器人不存在",需先执行 `dws chat group members add-bot --id <openConversationId> --robot-code <robot-code>` 将机器人加入群聊后再发送。
```
Usage:
dws chat message send-by-bot [flags]
Example:
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> --title "日报" --text "## 今日完成..."
dws chat message send-by-bot --robot-code <robot-code> --users userId1,userId2 --title "提醒" --text "请提交周报"
dws chat message send-by-bot --robot-code <robot-code> --open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "请提交周报"
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> --at-user-ids userId1,userId2 --title "提醒" --text "@userId1 @userId2 请查收本周报告"
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> --at-open-dingtalk-ids openDingtalkId1,openDingtalkId2 --title "提醒" --text "@openDingtalkId1 @openDingtalkId2 请查收本周报告"
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> --at-all --title "通知" --text "请所有人注意"
Flags:
--group string 群聊 openConversationId(群聊时必填)
--robot-code string 机器人 Code (必填)
--text string 消息内容 Markdown (必填)
--title string 消息标题 (必填)
--users string 用户 userId 列表,逗号分隔,最多20个(单聊时必填)
--open-dingtalk-ids string 用户 openDingtalkId 列表,逗号分隔(单聊时可替代 --users,可选)
--at-user-ids string @指定成员的 userId 列表,逗号分隔(仅群聊时生效,可选)
--at-open-dingtalk-ids string @指定成员的 openDingtalkId 列表,逗号分隔(仅群聊时生效,可选)
--at-all @所有人(可选),服务端接收字符串 true/false
注意:
- 用户明确要求机器人发送时,必须使用 `chat message send-by-bot`;严禁使用 `chat message send` 以用户身份代发
- --group 与 --users/--open-dingtalk-ids 互斥,必须且只能指定其一
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- --at-user-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @userId 对应文本
- --at-open-dingtalk-ids 仅在 --group 群聊时生效,单聊时无效;设置时 --text 中需包含 @openDingtalkId 对应文本
- --at-all @所有人,仅群聊时生效;只需带上 --at-all flag 即可,服务端会自动处理
- userId 获取方式:`dws contact user search --query "姓名"` 搜人获取 userId
- **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`:
1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行
2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`<br>` 做硬换行
```
#### 机器人撤回消息(--group 群聊 / 不传为单聊)
群聊:传 --group 与 --keys;单聊:仅传 --keys。--keys 为发送时返回的 processQueryKey 列表,逗号分隔。
```
Usage:
dws chat message recall-by-bot [flags]
Example:
dws chat message recall-by-bot --robot-code <robot-code> --group <openconversation_id> --keys <process-query-key>
dws chat message recall-by-bot --robot-code <robot-code> --keys key1,key2
Flags:
--group string 群聊 openConversationId(群聊撤回时必填)
--keys string 消息 processQueryKey 列表,逗号分隔 (必填)
--robot-code string 机器人 Code (必填)
```
#### 自定义机器人 Webhook 发送群消息
@ 人时需在 --text 中包含 @userId 或 @手机号,否则 @ 不生效;@所有人时需在 --text 中包含 @10 并带上 --at-all。
```
Usage:
dws chat message send-by-webhook [flags]
Example:
dws chat message send-by-webhook --token <webhook-token> --title "告警" --text "CPU 超 90% @10" --at-all
dws chat message send-by-webhook --token <webhook-token> --title "test" --text "hi @118785" --at-users 118785
Flags:
--at-all @ 所有人(需在 --text 中包含 @10)
--at-mobiles string @ 指定手机号,逗号分隔
--at-users string @ 指定用户,逗号分隔(需在 text 中包含 @userId)
--text string 消息内容 (必填)
--title string 消息标题 (必填)
--token string Webhook Token (必填)
注意:
- **换行符**:--text 按 Markdown 渲染,换行规则同 `chat message send`:
1. 必须使用**真实换行符**(`U+000A`),而非字面量 `\n`,否则全部内容会渲染在同一行
2. 单个换行不产生换行效果,需用空行(`\n\n`)做段落分隔,或行尾两空格 + 换行/`<br>` 做硬换行
```
#### 拉取群话题回复消息列表
查询指定群聊中某条话题消息的全部回复。--group 指定群会话 ID,--topic-id 指定话题 ID(由 dws chat message list 返回)。
```
Usage:
dws chat message list-topic-replies [flags]
Example:
dws chat message list-topic-replies --group <openconversation_id> --topic-id <topicId>
dws chat message list-topic-replies --group <openconversation_id> --topic-id <topicId> --time "2025-03-01 00:00:00" --limit 20
dws chat message list-topic-replies --group <openconversation_id> --topic-id <topicId> --time "2025-03-01 00:00:00" --direction newer
Flags:
--group string 群会话 openconversationId (必填)
--topic-id string 话题 ID,由 dws chat message list 返回 (必填)
--time string 开始时间,格式: yyyy-MM-dd HH:mm:ss(可选)
--limit int 返回数量(默认 50)
--direction string 时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐,默认 older)
--forward 旧兼容参数: true 等价 --direction newer,false 等价 --direction older(默认 false)
```
#### 拉取指定时间范围内当前用户的所有会话消息 — 分页拉取当前登录用户在指定时间范围内的所有会话消息
--start 和 --end 限定时间范围,--limit 指定每页数量,--cursor 传分页游标(首页传 "0",后续从响应中的 nextCursor 获取)。服务端按 cursor 分页返回,hasMore=true 时用返回的 nextCursor 值作为下次 --cursor 继续翻页。
```
Usage:
dws chat message list-all [flags]
Example:
dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50
dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 --cursor "abc123token"
Flags:
--start string 起始时间,格式: yyyy-MM-dd HH:mm:ss (必填)
--end string 结束时间,格式: yyyy-MM-dd HH:mm:ss (必填)
--limit int 每页返回数量(默认 50)
--cursor string 分页游标(首页传 "0",后续从响应中的 nextCursor 获取)
注意:
- 四个参数每次请求都会传递给服务端,cursor 首页传 "0"
- 与 chat message list 的区别:list 拉取指定单个会话(群聊或单聊)的消息,list-all 拉取当前用户所有会话的消息
- 翻页:hasMore=true 时,用响应中的 nextCursor 值作为下次 --cursor 参数继续翻页
- 时间格式统一为 yyyy-MM-dd HH:mm:ss
- 权限/权益错误不是空结果;应把返回的 `friendly_hint` 与 `action_url` 展示给用户,不要继续盲目翻页
```
#### 收藏消息 — 为当前用户收藏指定会话中的一条消息
```
Usage:
dws chat message add-favorite [flags]
Example:
dws chat message add-favorite --open-message-id <openMessageId> --open-conversation-id <openConversationId>
Flags:
--open-message-id string 消息 openMessageId (必填)
--open-conversation-id string 消息所在会话 openConversationId (必填)
```
#### 取消收藏消息 — 移除当前用户的收藏标记,不删除原消息
```
Usage:
dws chat message remove-favorite [flags]
Example:
dws chat message remove-favorite --open-message-id <openMessageId> --open-conversation-id <openConversationId>
Flags:
--open-message-id string 消息 openMessageId (必填)
--open-conversation-id string 消息所在会话 openConversationId (必填)
```
#### 查询收藏消息 — 分页查询当前用户收藏的消息
```
Usage:
dws chat message list-favorites [flags]
Example:
dws chat message list-favorites
dws chat message list-favorites --cursor 20 --size 20
Flags:
--cursor int 数字分页游标,默认 0;翻页时传上次返回的 nextCursor
--size int 一次拉取的收藏数量,默认 20,范围 1-100
注意:
- 首次请求可省略分页参数,CLI 会自动使用 cursor=0、size=20
- hasMore=true 时,将 nextCursor 作为下一次的 --cursor
```
#### 拉取指定发送者的消息 — 搜索特定人发送给我的消息(包含单聊和群聊)
> 推荐优先使用 `chat message search-advanced --user/--users`(userId)或 `--sender-ids`(openDingTalkId),它还能叠加关键词/群/at 等过滤条件。本命令保留给需要旧 list-by-sender 返回结构的场景。
搜索特定人发送给我的消息,返回结果包含单聊和群聊标识。--sender-user-id 指定发送者 userId,--sender-open-dingtalk-id 指定发送者 openDingTalkId,二者互斥。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。
```
Usage:
dws chat message list-by-sender [flags]
Example:
dws chat message list-by-sender --sender-user-id <userId> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
dws chat message list-by-sender --sender-open-dingtalk-id <openDingTalkId> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
dws chat message list-by-sender --sender-user-id <userId> --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --limit 20 --cursor 0
dws chat message list-by-sender --sender-open-dingtalk-id <openDingTalkId> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor <nextCursor>
Flags:
--sender-user-id string 发送者 userId(与 --sender-open-dingtalk-id 二选一)
--sender-open-dingtalk-id string 发送者 openDingTalkId(与 --sender-user-id 二选一,适用于无法获取 userId 的场景)
--start string 开始时间,ISO-8601 格式 (必填)
--end string 结束时间,ISO-8601 格式 (必填)
--limit int 每页返回数量(默认 50)
--cursor string 分页游标(默认 "0",翻页传 nextCursor)
注意:
- --sender-user-id 和 --sender-open-dingtalk-id 二者互斥,必须且只能指定其一:
- --sender-user-id 传 userId(企业内部应用常用)
- --sender-open-dingtalk-id 传 openDingTalkId(三方应用或跨组织场景常用,无法获取 userId 时使用)
- openDingTalkId 获取方式见下方「openDingTalkId 获取方式」小节
- 不需要指定单聊/群聊,返回结果自带会话类型标识
- 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
```
#### 拉取 @我 的消息 — 搜索时间范围内 @我 的消息
> 推荐使用 `chat message search-advanced --at-me`,它还能叠加关键词/群/发送者等过滤条件。本命令适用于仅需拉取 @我 消息的简单场景。
搜索时间范围内 @我 的消息,可选指定群聊。返回结果包含单聊和群聊标识。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。
```
Usage:
dws chat message list-mentions [flags]
Example:
dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
dws chat message list-mentions --start "2026-04-01T00:00:00+08:00" --end "2026-04-14T00:00:00+08:00" --limit 20 --cursor 0
dws chat message list-mentions --group <openconversation_id> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor <nextCursor>
Flags:
--group string 群聊 openconversation_id(可选,不传则查全部)
--start string 开始时间,ISO-8601 格式 (必填)
--end string 结束时间,ISO-8601 格式 (必填)
--limit int 每页返回数量(默认 50)
--cursor string 分页游标(默认 "0",翻页传 nextCursor)
注意:
- --group 可选,不传则查询所有会话中 @我 的消息;传入则只查指定群聊
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
```
#### 拉取特别关注人的消息
拉取当前用户特别关注人的消息。分页参数 --limit 指定每页数量,--cursor 传分页游标(首次不传或传 0)。返回结果中 hasMore=true 时用 nextCursor 作为下次 --cursor 继续翻页。
```
Usage:
dws chat message list-focused [flags]
Example:
dws chat message list-focused --limit 50
dws chat message list-focused --limit 20 --cursor <nextCursor>
Flags:
--limit int 每页返回数量(默认 50)
--cursor int64 分页游标(首次不传或传 0,翻页传 nextCursor)
注意:
- 首次调用不传 --cursor 或传 0,后续翻页传 nextCursor
```
#### 获取未读会话列表
获取当前用户有未读消息的会话信息。可选通过 `--count` 限制返回条数,不传则使用服务端默认值。
```
Usage:
dws chat message list-unread-conversations [flags]
Example:
dws chat message list-unread-conversations
dws chat message list-unread-conversations --count 20
Flags:
--count int 返回未读会话条数(可选)
--exclude-muted 是否排除已设置免打扰的会话(默认 false)
```
#### 查询消息的已读/未读状态
查询指定会话中消息的已读/未读状态(仅消息发送者可查询自己发出的消息)。--conversation-id 指定会话 openConversationId(群聊或单聊均可),--message-id 指定消息 ID(由 dws chat message list 返回的 openMessageId,必须是当前用户发送的消息)。目标用户 userId 使用 --user/--users;目标用户 openDingTalkId 使用 --target-open-dingtalk-ids;不传目标用户则返回所有接收者的状态。
```
Usage:
dws chat message read-status [flags]
Example:
dws chat message read-status --conversation-id <openConversationId> --message-id <openMessageId>
dws chat message read-status --conversation-id <openConversationId> --message-id <openMessageId> --user userId1,userId2
dws chat message read-status --conversation-id <openConversationId> --message-id <openMessageId> --users userId1,userId2
dws chat message read-status --conversation-id <openConversationId> --message-id <openMessageId> --target-open-dingtalk-ids openDingTalkId1,openDingTalkId2
Flags:
--conversation-id string 会话 openConversationId (必填,群聊或单聊均可)
--message-id string 消息 openMessageId,由 chat message list 返回 (必填,必须是当前用户发送的消息)
--user string 目标用户 userId,支持逗号分隔(可选,不传则查所有接收者)
--users string 目标用户 userId 列表,逗号分隔(可选,不传则查所有接收者)
--target-open-dingtalk-ids string 目标用户 openDingTalkId 列表,逗号分隔(可选,不传则查所有接收者)
注意:
- 仅消息发送者可查询自己发出的消息的已读/未读状态,查询他人发的消息会报错
- --conversation-id 的别名: --group, --id, --chat (均可替代 --conversation-id)
- --message-id 从 dws chat message list 返回的消息列表中获取(字段名 openMessageId)
- --user / --users 传目标用户 userId
- --target-open-dingtalk-ids 不传时返回该消息所有接收者的已读状态;传入则只返回指定 openDingTalkId 用户的状态
```
#### 按关键词搜索消息 — 在当前用户的会话中按关键词搜索消息
> 推荐优先使用 `chat message search-advanced`,它是本命令的严格超集:query 可选(非必填)、支持多个会话(非单个)、还能叠加发送者/at 等维度过滤。
按关键词搜索消息内容。--query 指定搜索关键词(必填)。可选 --group 限定搜索某个会话,不传则搜索所有会话。时间参数 --start/--end(ISO-8601)限定搜索时间范围。分页参数 --limit(默认 100)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。
```
Usage:
dws chat message search [flags]
Example:
dws chat message search --query "changefree" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 50 --cursor 0
dws chat message search --query "codereview" --group <openconversation_id> --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 100 --cursor 0
dws chat message search --query "链接" --start "2026-04-15T00:00:00+08:00" --end "2026-04-16T00:00:00+08:00" --limit 100 --cursor <nextCursor>
Flags:
--query string 搜索关键词 (必填)
--group string 群聊 openconversation_id(可选,不传则搜索所有会话)
--start string 开始时间,ISO-8601 格式 (必填)
--end string 结束时间,ISO-8601 格式 (必填)
--limit int 每页返回数量(默认 100)
--cursor string 分页游标(默认 "0",翻页传 nextCursor)
注意:
- --group 可选,不传则搜索所有会话中的消息;传入则只搜索指定会话
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
- 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
```
#### 多维度搜索消息(推荐首选) — 支持按关键词、发送者、@我、@指定人、指定会话、时间范围、消息类型、会话类型等多维度搜索
> 推荐:这是消息搜索的首选接口。它可以完全替代 `chat message search`(query 可选 vs 必填,支持多个会话 vs 单个),大部分替代 `chat message list-by-sender`(通过 --user/--users 按 userId 搜索发送者,或通过 --sender-ids 按 openDingTalkId 搜索)和 `chat message list-mentions`(通过 --at-me 搜索@我的消息)。仅在拉取「特别关注人」消息时需要退回 `list-focused`。
支持按关键词、发送者、@我、@指定人、指定会话、时间范围、消息类型、会话类型等多维度搜索消息。发送者 userId 使用 --user/--users;发送者或 @ 人的 openDingTalkId 使用 --sender-ids/--at-ids。所有参数均为可选,至少指定一个搜索条件。
```
Usage:
dws chat message search-advanced [flags]
Example:
dws chat message search-advanced --query "周报" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00"
dws chat message search-advanced --user <userId> --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00"
dws chat message search-advanced --users <userId1>,<userId2> --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00"
dws chat message search-advanced --sender-ids <openDingTalkId1>,<openDingTalkId2> --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00"
dws chat message search-advanced --at-me --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00"
dws chat message search-advanced --at-ids <openDingTalkId1>,<openDingTalkId2> --conversation-ids <openConversationId1>,<openConversationId2> --limit 50 --cursor 0
dws chat message search-advanced --conversation-ids <单聊openConversationId> --query "合同" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00"
dws chat message search-advanced --message-type file --search-conv-type group_chat --query "附件"
dws chat message search-advanced --only-robot-messages --query "通知"
# 查询群 ID: dws chat search --query "群名"
# 查询单聊会话 ID: dws chat conversation-info --user <userId>
# 查询人员: dws contact user search --keyword "姓名" --format json
Flags:
--query string 搜索关键词(可选)
--user string 发送者 userId,支持逗号分隔(可选)
--users string 发送者 userId 列表,逗号分隔(可选)
--sender-ids string 发送者 openDingTalkId 列表,逗号分隔(可选)
--at-me 只搜索 @我 的消息(可选,默认 false)
--at-ids string @指定人的 openDingTalkId 列表,逗号分隔(可选)
--conversation-ids string 会话 openConversationId 列表,逗号分隔(可选,群聊或单聊均可,不传则搜索所有会话)
--message-type string 消息类型筛选(可选,支持 file/image/video/audio/link)
--search-conv-type string 会话类型筛选(可选,single_chat=单聊, group_chat=群聊)
--start string 开始时间,ISO-8601 格式(可选)
--end string 结束时间,ISO-8601 格式(可选)
--cursor string 分页游标(默认 "0")
--limit int 每页返回数量(默认 100)
--only-robot-messages 仅搜索机器人消息(可选,默认 false)
--conversation-ids 的别名: --groups
注意:
- 所有参数均为可选,但至少需要指定一个搜索条件
- --user / --users 传发送者 userId
- --sender-ids 和 --at-ids 传 openDingTalkId
- --conversation-ids 可指定多个会话 ID(群聊或单聊均可),逗号分隔,不传则搜索所有会话
- 群聊 openConversationId 通过 `dws chat search --query "群名"` 获取
- 单聊 openConversationId 通过 `dws chat conversation-info --user <userId>` 或 `--open-dingtalk-id <openDingTalkId>` 获取
- 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00"、"2026-03-10" 等
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
- 替代关系:完全替代 search(严格超集);大部分替代 list-by-sender(--user 覆盖按 userId 搜索发送者,--sender-ids 覆盖按 openDingTalkId 搜索)和 list-mentions(--at-me 覆盖核心功能);不能替代 list-focused(「特别关注」是独立维度)
```
#### 根据消息 ID 批量查询消息
```
Usage:
dws chat message list-by-ids [flags]
Example:
dws chat message list-by-ids --msg-ids msgId1,msgId2,msgId3
# 最多传 50 条消息 ID
Flags:
--msg-ids string 消息 ID 列表,逗号分隔,最多 50 条 (必填)
```
#### 表情回应选择策略
> 贴表情时,优先查 [chat-emoji-list.md](chat-emoji-list.md) 中的默认表情名称(共 199 个,如「赞」「鼓掌」「感谢」等):
> - 命中 → 使用 `add-emoji --emoji <name>`(直接贴 emoji)
> - 未命中 → 先 `create-text-emotion` 创建文字表情获取 emotionId,再 `add-text-emotion` 贴文字表情
#### 对消息添加 emoji 表情回应
```
Usage:
dws chat message add-emoji [flags]
Example:
dws chat message add-emoji --conversation-id <openConversationId> --msg-id <openMsgId> --emoji "赞"
dws chat message add-emoji --conversation-id <openConversationId> --msg-id <openMsgId> --emoji "鼓掌"
# --emoji 的值必须是 chat-emoji-list.md 中的 name(中文名),如:赞、鼓掌、感谢、微笑 等
# 查询会话 ID: dws chat search --query "群名"
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat)
--msg-id string 消息 openMsgId (必填)
--emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md)
```
#### 移除消息的 emoji 表情回应
```
Usage:
dws chat message remove-emoji [flags]
Example:
dws chat message remove-emoji --conversation-id <openConversationId> --msg-id <openMsgId> --emoji "赞"
# 查询会话 ID: dws chat search --query "群名"
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat)
--msg-id string 消息 openMsgId (必填)
--emoji string emoji 表情名称,必须是默认表情列表中的 name 值 (必填,参见 chat-emoji-list.md)
```
#### 对消息添加文字表情回应(当默认表情列表中没有所需表情时使用)
```
Usage:
dws chat message add-text-emotion [flags]
Example:
dws chat message add-text-emotion --conversation-id <openConversationId> --msg-id <openMsgId> --emotion-id <emotionId> --emotion-name "赞" --text "nice" --background-id im_bg_5
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat)
--msg-id string 消息 openMsgId (必填)
--emotion-id string 表情 ID (必填,通过 create-text-emotion 或已知表情获取)
--emotion-name string 表情名称 (必填)
--text string 文字内容 (必填)
--background-id string 背景 ID (必填)
```
#### 移除消息的文字表情回应
```
Usage:
dws chat message remove-text-emotion [flags]
Example:
dws chat message remove-text-emotion --conversation-id <openConversationId> --msg-id <openMsgId> --emotion-id <emotionId> --emotion-name "赞" --text "nice" --background-id <backgroundId>
Flags:
--conversation-id string 会话 openConversationId (必填,支持单聊/群聊,别名: --group / --id / --chat)
--msg-id string 消息 openMsgId (必填)
--emotion-id string 表情 ID (必填)
--emotion-name string 表情名称 (必填)
--text string 文字内容 (必填)
--background-id string 背景 ID (必填)
```
#### 创建文字表情(获取 emotionId)— 当 chat-emoji-list.md 中没有所需表情时,先创建再贴
```
Usage:
dws chat message create-text-emotion [flags]
Example:
dws chat message create-text-emotion --emotion-name "赞" --text "nice"
dws chat message create-text-emotion --emotion-name "感谢" --text "感谢" --background-id im_bg_5
Flags:
--emotion-name string 表情名称 (必填)
--text string 文字内容 (必填)
--background-id string 背景 ID(可选,不传则由服务端默认分配)
注意:
- 创建后返回 emotionId,可用于 add-text-emotion 命令
- 如果已有合适的表情,无需创建新的
```
#### 批量拉取消息的表情回复和文字回复
根据消息 ID 列表批量查询消息的表情回复和文字回复信息。
```
Usage:
dws chat message list-emotion-replies [flags]
Example:
dws chat message list-emotion-replies --msg-ids msgId1,msgId2,msgId3
# 消息 ID 可通过 dws chat message list 获取
Flags:
--msg-ids string 消息 ID 列表,逗号分隔 (必填)
注意:
- 支持批量查询多条消息的表情和文字回复
- 消息 ID 可通过 `dws chat message list` 命令获取
```
@@ -0,0 +1,67 @@
# 注意事项
## 注意事项
- **发消息前参数审查(必须执行)**:
- 发消息(`chat message send`、`send-by-bot`、`send-by-webhook`、`send-card`、`reply`、`forward`)是严肃操作,一旦发错人/发错群会导致严重问题,因此在执行发送之前,agent 必须对所有参数进行内部审查
- 审查方式:将即将发送的**全部参数**(收件人/群、消息内容、@对象、消息类型等)与用户的**原始需求**逐一对比,确认每个参数都能从原始需求中找到明确依据
- 如果存在任何不明确、有歧义或原始需求中未提及的参数(例如:用户没说发给谁、没说发到哪个群、消息内容与用户意图有出入、不确定是否需要 @某人等),**必须先向用户确认**,严禁自行假设或补全
- 典型需要确认的场景:用户只说了"发个消息"但没指定群/人;用户的描述可匹配多个群或多个联系人;消息文本由 agent 组织而非用户原文提供时需确认措辞
- uuid 幂等参数(发消息最佳实践):
- 发消息时建议始终带上 `--uuid` 参数,传入用户自行生成的唯一标识(如 UUID v4),用于幂等控制
- 如果发送失败需要重试,重试时 `--uuid` 必须与首次发送保持一致,服务端据此去重,避免重复发消息
- 如果不传 `--uuid`,每次调用都视为新消息,重试可能导致消息重复发送
- 此参数适用于 `chat message send`(群聊和单聊均支持)
- `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取
- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;纯文本/Markdown 单聊传 `--user` 时直接走 userId 发送能力;`--at-all`、`--at-open-dingtalk-ids` 仅在 `--group` 群聊时生效;**发图片/本地文件统一 `--msg-type file --file-path <本地路径>`**(任意扩展名 png/jpg/pdf/mp4/zip… 一条命令直发,CLI 内部完成上传);`--msg-type image --media-id` 仅作旧链路兼容
- `chat message list-all` 的四个参数(--start、--end、--limit、--cursor)每次请求都必须传递;翻页时用响应中的 nextCursor 值作为下次 --cursor
- `chat message list` 的 `--group`、`--user`、`--open-dingtalk-id` 三者互斥,必须且只能指定其一
- `chat message list-by-sender` 不需要指定单聊/群聊,返回结果自带会话类型标识;`--sender-user-id`(userId)与 `--sender-open-dingtalk-id`(openDingTalkId)二选一;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor`
- `chat message list-mentions` 可选 `--group` 指定群聊,不传则查全部;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`/`--cursor`
- `chat message list-unread-conversations` 获取当前用户未读会话列表,可选 `--count` 指定返回条数
- `chat message search` 按关键词搜索消息内容,`--query` 必填,可选 `--group` 限定搜索某个会话;时间用 `--start`/`--end`(ISO-8601),分页用 `--limit`(默认 100)/`--cursor`
- `chat message read-status` 查询指定消息的已读/未读状态,仅消息发送者可查询自己发出的消息;`--group`、`--message-id` 必填;目标用户 userId 用 `--user`/`--users`,openDingTalkId 用 `--target-open-dingtalk-ids`,不传则查所有接收者
- `chat search-common` 搜索共同群,`--nicks` 传人员昵称(逗号分隔),`--match-mode` AND/OR 控制匹配逻辑,分页用 `--limit`(默认 20)/`--cursor`
- `chat list-top-conversations` 拉取置顶会话列表,分页用 `--limit`(默认 1000)/`--cursor`;用户询问"置顶会话"时路由到此命令
- `--user` 和 `--open-dingtalk-id` 本质上都是发起单聊操作,只是用户标识格式不同:userId 为企业内部应用常用标识,openDingTalkId 为三方应用或跨组织场景下的用户标识,服务端对两种 ID 的解析逻辑不同
- `--time` 格式: `yyyy-MM-dd HH:mm:ss`,为拉取消息的起始时间点;拉取方向优先使用 `--direction newer|older`:`newer` 从给定时间往现在拉,`older` 从给定时间往以前拉;`--forward` 为旧兼容参数,`true` 等价 `newer`,`false` 等价 `older`;`--limit` 控制数量
- `chat search` 挂在 `chat` 下(非 `chat group` 下),路径为 `dws chat search`
- `send-by-bot` 群聊传 `--group`,单聊传 `--users` 或 `--open-dingtalk-ids`,与 `--group` 互斥且必选其一;群聊时可选 `--at-user-ids` @指定成员(传 userId 列表)或 `--at-open-dingtalk-ids` @指定成员(传 openDingtalkId 列表),content 中需包含对应 @标识;`--at-all` @所有人;群聊场景如果返回"机器人不存在"错误,需先通过 `chat group members add-bot --group <openConversationId> --robot-code <robot-code>` 将机器人邀请进群后再发送
- `recall-by-bot` 群聊传 `--group` + `--keys`,单聊仅传 `--keys`(不传 `--group` 即为单聊撤回)
- `send-by-webhook` 支持 `--at-all`、`--at-mobiles`、`--at-users` 进行 @ 操作,但需在 `--text` 中包含 `@userId` 或 `@手机号` 才能生效;`--at-all` @所有人时需在 `--text` 中包含 `@10`
- `chat group-role` 系列命令用于管理群的自定义身份标签:`list` 查列表,`add` 创建,`update` 改名,`remove` 删除;`set-user` 覆盖某人全部身份(传空 --role-ids 则清除),`remove-user` 仅移除指定身份,`query-user` 查询某人当前身份;用户用 `--user <userId>`
- 消息**换行符**(`send` / `send-by-bot` / `send-by-webhook` 的 `--text`)有两层要求:(1) 必须是**真实换行符** `U+000A`,不是字面量 `\n`;(2) Markdown 规范下单换行不生效,需用空行 `\n\n`(段落分隔)或行尾两空格 + 换行 / `<br>`(硬换行)
- `chat group transfer-owner` 转让群主,需传 --group(openConversationId);新群主 userId 用 `--user`,openDingTalkId 用 `--new-owner`
- `chat group invite-url` 获取群邀请链接,需传 --group(openConversationId),可选 --expires-seconds 指定有效期(秒,0=永久)
- `chat group share-invite` 分享群聊链接到会话,需传 --source(被分享群 openConversationId),--target(目标会话)和 --receiver(单聊用户)二选一,可选 --expires-seconds 和 --uuid
- `chat category create-smart` 创建智能会话分组,需传 --name(分组名称),可选 --keywords(群名称关键词)和 --members(群内成员 openDingTalkId)
- `chat group quit` 退出群聊,需传 --group(openConversationId)
- `chat group update-icon` 更新群头像,需传 --group(openConversationId)和 --icon-media-id(mediaId)
- `chat group update-settings` 更新群设置,需传 --group(openConversationId)、--setting-key(设置项 key)、--status(0=关闭 1=开启)
- `chat message send-card` 创建并推送流式卡片,群聊传 --group,单聊传 --receiver,二者互斥;不传 content,后续通过 update-card 更新内容
- `chat message update-card` 流式更新卡片内容,需传 --biz-id(创建卡片返回的业务 ID)、--content、--flow-status
- `chat message list-by-ids` 根据消息 ID 批量查询,--msg-ids 逗号分隔,最多 50 条
- `chat message add-emoji` / `remove-emoji` 需传 --group(openConversationId)、--msg-id(openMsgId)、--emoji(表情名称)
- `chat message add-text-emotion` / `remove-text-emotion` 需传 --group、--msg-id、--emotion-id、--emotion-name、--text、--background-id,六个参数全部必填
- `chat message create-text-emotion` 创建文字表情模板,返回 emotionId;--background-id 可选,不传由服务端默认分配
- `chat category list` 无需参数;`category list-conversations` 需传 --category-id(通过 category list 获取)
- `chat mute` 默认开启免打扰,传 --off 关闭;--conversation-id / --id / --chat 三个别名均可用于传入会话 ID
- `chat message reply` 引用回复消息(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段)、--ref-msg-id(被引用消息 openMessageId)、--ref-sender(被引用消息发送者 openDingTalkId)、--text(回复内容);目前回复类型仅支持 text
- `chat message forward` 转发单条消息(**源/目标会话均支持单聊/群聊**,常见组合:群→群、群→单、单→群、单→单),需传 --src-conversation-id(源会话 openConversationId)、--msg-id(源消息 openMessageId)、--dest-conversation-id(目标会话 openConversationId)
- `chat message forward-topic` 转发话题消息,需传 --src-msg-id(源消息 openMessageId)、--src-conversation-id(源会话 openConversationId)、--src-thread-id(话题 ID,格式: convThread + 加密后的convThreadId)、--dest-conversation-id(目标会话 openConversationId)
- `chat set-top` 设置/取消会话置顶(单聊/群聊均可),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段),默认置顶,传 --off 取消
- `chat message set-top-msg` 置顶会话内某条消息,需传 --open-conversation-id(openConversationId)、--msg-id(openMessageId);与 chat set-top 区别:后者是会话列表置顶,前者是消息置顶
- `chat message unset-top-msg` 取消置顶会话内某条消息,需传 --open-conversation-id(openConversationId)、--msg-id(openMessageId)
- `chat message reply` 以当前用户身份引用回复,与 `chat message send` 的用户身份发送语义一致
- **如何获取 openConversationId**(如果上层已有则直接使用,不必再查):
- 群聊:`dws chat search --query "群名"`
- 单聊:`dws chat conversation-info --user <userId>` 或 `dws chat conversation-info --open-dingtalk-id <openDingTalkId>`(人员信息可通过 `dws contact user search --keyword "姓名" --format json` 获取)
- `chat group-mute` 全员禁言/取消全员禁言,需传 --group(openConversationId),默认禁言,传 --off 取消
- `chat group-mute-member` 指定群成员禁言,需传 --group、--user/--users(userId,逗号分隔)、--mute-time(毫秒,仅禁言时必填,支持 300000/3600000/86400000/604800000/2592000000),传 --off 解除禁言
- `chat group set-admin` 设置/取消群管理员,需传 --group(openConversationId)、--user/--users(userId,逗号分隔),默认设为管理员,传 --off 取消
- `chat group update-nick` 设置用户在群内的群昵称,需传 --group(openConversationId)、--nick(群昵称)
- `chat group update-alias` 设置群备注,需传 --group(openConversationId)、--alias-title(备注名称),仅自己可见
- `chat hide` 隐藏会话,需传 --conversation-id(openConversationId,支持单聊/群聊),隐藏后不显示在列表中,收到新消息时重新出现
- `chat mute-at-all` 关闭/开启 @所有人消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收
- `chat mute-red-envelope` 关闭/开启红包消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收
- `chat group members list-by-ids` 根据成员 openDingTalkId 批量查询群成员详情,需传 --id(openConversationId)、--users(openDingTalkId 列表,逗号分隔)
@@ -0,0 +1,32 @@
# 相关产品
## 相关产品
- [contact](../../dingtalk-contact/references/contact.md) — 搜索同事/好友,获取 userId 用于 --user、send-by-bot --users、send-by-bot --at-user-ids、list-by-sender --sender-user-id;获取 openDingTalkId 用于 message send 的 --at-open-dingtalk-ids、--open-dingtalk-id、send-by-bot --open-dingtalk-ids、send-by-bot --at-open-dingtalk-ids、list-by-sender 的 --sender-open-dingtalk-id
- [drive](../../dingtalk-drive/references/drive.md) — 云盘文件管理;chat 纯文件发送优先用 `chat message send --msg-type file --file-path`
### text translate (文本翻译)
#### 翻译文本内容 — 将指定文本翻译成目标语言
```
Usage:
dws chat text translate [flags]
Example:
dws chat text translate --query "你好世界" --to en_US
dws chat text translate --query "Hello World" --to zh_CN
dws chat text translate --query "Bonjour" --to ja_JP
Flags:
--query string 待翻译的文本内容 (必填)
--to string 目标语言代码 (必填,默认 en_US)
```
支持的目标语言代码:
en_US, zh_CN, zh_TW, zh_HK, ja_JP, ko_KR, vi_VN, th_TH,
id_ID, ms_MY, es_419, fr_FR, pt_BR, tr_TR, ru_RU, de_DE,
hi_IN, hu_HU, pl_PL, sv_SE, fi_FI, cs_CZ, ar_SA, tl_PH,
he_IL, nl_NL, lo_LA, it_IT
注意:
- `--to` 参数必须使用上述支持的语言代码,大小写敏感
- 默认目标语言为 en_US(美式英语)
- 返回结果为翻译后的纯文本内容
@@ -0,0 +1,9 @@
# 自动化脚本
## 自动化脚本
| 脚本 | 场景 | 用法 |
|------|------|------|
| [chat_export_messages.py](../scripts/chat_export_messages.py) | 导出群聊消息到 JSON 文件 | `python chat_export_messages.py --query "项目冲刺" --time "2026-03-10 00:00:00"` |
| [chat_history_with_user.py](../scripts/chat_history_with_user.py) | 查询与某人的单聊聊天记录 | `python chat_history_with_user.py --name "张三" --time "2026-03-10 00:00:00"` |
| [extract_media_id.py](../scripts/extract_media_id.py) | **旧链路** — 仅当上游已通过 `dt_media_upload` 拿到 URL 时使用,从该 URL 提取 mediaId | `python extract_media_id.py "<URL>"`(输出如 @lQLPxxx,用于 `--msg-type image --media-id`;新场景请直接用 `--msg-type file --file-path`) |
@@ -0,0 +1,35 @@
# 核心工作流
## 核心工作流
```bash
# 1. 搜索群 — 提取 openconversation_id
dws chat search --query "项目冲刺" --format json
# 2. 拉取群消息
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00" --format json
# 2b. 拉取未读会话列表
dws chat message list-unread-conversations --count 20 --format json
# 3. 以个人身份发送群消息
dws chat message send --group <openconversation_id> --title "周报提醒" "请大家本周五前提交周报" --format json
# 4. 以个人身份单聊(通过 userId)
dws chat message send --user <userId> "你好" --format json
# 4b. 以个人身份单聊(通过 openDingTalkId,三方应用等无法获取 userId 时使用)
dws chat message send --open-dingtalk-id <openDingTalkId> "你好" --format json
# 5. 机器人发群消息(Markdown)
dws chat message send-by-bot --robot-code <robot-code> \
--group <openconversation_id> --title "日报" --text "## 今日完成..." --format json
# 6. 机器人单聊发消息
dws chat message send-by-bot --robot-code <robot-code> \
--users userId1,userId2 --title "提醒" --text "请提交周报" --format json
# 7. Webhook 发告警
dws chat message send-by-webhook --token <webhook-token> \
--title "告警" --text "CPU 超 90% @10" --at-all --format json
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
# chat 局部意图消歧
本文件从单 Skill `intent-guide.md` 拆分而来,仅保留与本产品相关的跨产品消歧规则。
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|---|---|---|---|---|
| "通知群里的人都来开会" | 个人身份群发 | `chat message send` | `chat message send-by-bot` | 以个人身份向群发消息 |
| "让机器人每天推送日报" | 机器人定时推送 | `chat message send-by-bot` | `chat message send` | 需要机器人身份定期发送 |
| "CPU 超过 90% 自动告警" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 系统告警场景,需自定义 Webhook |
| "拉取一下上周项目群的聊天记录" | 拉取会话消息 | `chat message list` | — | 拉取指定群聊的消息列表 |
| "看看张三发给我的消息" | 按发送者查询消息 | `chat message list-by-sender` | `chat message list --user` | 用户未明确说"单聊"时优先用 list-by-sender(跨单聊/群聊) |
| "拉取和张三的单聊记录" | 拉取单聊消息 | `chat message list --user` | `chat message list-by-sender` | 用户明确说"单聊"时用 list --user |
| "谁@了我/查看提及我的消息" | 查询@我的消息 | `chat message list-mentions` | `chat message list-all` | 都是跨会话时间范围查询,但 list-mentions 只返回@我的消息 |
| "查看我今天的所有消息" | 全量会话消息 | `chat message list-all` | `chat message list` | 用户未指定具体会话时用 list-all(跨所有会话),指定了具体群或人时用 list |
| "搜一下消息里的changefree链接" | 消息搜索 | `chat message search-advanced`(首选) | `chat search` | 推荐首选 search-advanced,它是 search 的严格超集(keyword 可选、支持多群、可叠加发送者/at 维度) |
| "按发送者搜索/指定多个群搜索/多维度搜消息" | 多维度搜索消息 | `chat message search-advanced`(首选) | `chat message search` | 推荐首选,支持关键词、发送者、@我、多个会话等维度组合 |
| "消息发没发成功/查询消息发送状态" | 查询消息发送状态 | `chat message query-send-status` | — | 需要 send 返回的 openTaskId |
| "撤回我发的消息/撤回消息/群主撤回消息/管理员撤回他人消息" | 撤回消息 | `chat message recall` | `chat message recall-by-bot` | recall 撤回个人消息或群主/管理员撤回他人消息,recall-by-bot 撤回机器人消息 |
| "未读消息会话/未读会话列表/我的未读会话" | 未读会话列表 | `chat message list-unread-conversations` | `chat message read-status` | list-unread-conversations 查哪些会话有未读;read-status 查具体消息的已读状态 |
| "谁看了这条消息/消息已读未读/查读状态" | 查询消息已读状态 | `chat message read-status` | `chat message list-unread-conversations` | read-status 查具体消息的已读人员;list-unread-conversations 查未读会话列表 |
| "查看消息的表情回复/拉取消息回复/消息的文字回复" | 批量拉取消息表情回复和文字回复 | `chat message list-emotion-replies` | — | 根据消息 ID 批量查询表情回复和文字回复 |
| "我和风雷的共同群/我们都在哪些群" | 搜索共同群 | `chat search-common` | `chat search` | search-common 按人员搜共同群,chat search 按群名搜索 |
| "查看会话分组/自定义分组" | 获取会话分组 | `chat category list` | — | 获取用户自定义的会话分组列表 |
| "某个分组下有哪些会话" | 分组下会话列表 | `chat category list-conversations` | — | 需先通过 category list 获取分组 ID |
| "创建会话分组/新建分组" | 创建自定义分组 | `chat category create` | — | 需指定 --title 分组名称 |
| "删除会话分组/移除分组" | 删除自定义分组 | `chat category delete` | — | 需指定 --category-id |
| "重命名分组/修改分组名称" | 更新分组名称 | `chat category rename` | — | 需指定 --category-id 和 --title |
| "把会话加到分组/将群移入分组" | 会话加入分组 | `chat category add-conv` | `chat category remove-conv` | add-conv 加入;remove-conv 移出 |
| "把会话从分组移出/从分组中删除" | 会话移出分组 | `chat category remove-conv` | `chat category add-conv` | remove-conv 移出;add-conv 加入 |
| "创建智能分组/新建智能会话分组/按关键词分组" | 创建智能会话分组 | `chat category create-smart` | `chat category create` | create-smart 按关键词/成员智能匹配;create 手动创建普通分组 |
| "分享群链接/把群分享给某人/群链接发到某群" | 分享群聊链接到会话 | `chat group share-invite` | `chat group invite-url` | share-invite 直接分享到目标会话/用户;invite-url 只获取链接不发送 |
| "根据群号查群信息/群号转openConversationId" | 群号查群聊信息 | `chat group get-by-group-id` | `chat search` | 已知数字群号时直接查;用户发消息只给了群号时,先用此工具将群号转为 openConversationId |
| "我创建的群/我管理的群/我是群主的群/我当管理员的群" | 拉取我创建/管理的群 | `chat group list-my-groups` | `chat search` | list-my-groups 按角色(群主/管理员)过滤当前用户的群;chat search 按关键词搜全部群 |
| "引用消息回复/回复那条消息" | 引用回复消息 | `chat message reply` | `chat message send` | reply 引用指定消息回复;send 普通发消息不引用 |
| "转发消息/把消息转到另一个群" | 转发单条消息 | `chat message forward` | `chat message send` | forward 转发已有消息到目标会话;send 是发新消息 |
| "置顶会话/取消置顶" | 设置/取消会话置顶 | `chat set-top` | `chat list-top-conversations` | set-top 设置或取消置顶;list-top-conversations 查看置顶列表 |
| "全员禁言/群禁言" | 全员禁言或解除 | `chat group-mute` | `chat group-mute-member` | group-mute 全员禁言或解除;group-mute-member 指定成员禁言 |
| "禁言某人/指定成员禁言" | 指定成员禁言 | `chat group-mute-member` | `chat group-mute` | group-mute-member 指定成员禁言/解禁;group-mute 全员禁言 |
| "设管理员/取消管理员" | 设置群管理员 | `chat group set-admin` | `chat group invite` | set-admin 设置或取消管理员角色;invite 是邀请入群 |
| "退群/退出群聊/离开群" | 退出群聊 | `chat group quit` | `chat group members remove` | quit 是当前用户自己退群;members remove 是管理员踢别人 |
| "改群头像/更新群头像" | 更新群头像 | `chat group update-icon` | `chat group rename` | update-icon 改群头像;rename 改群名称 |
| "改群设置/群设置开关/群权限/入群许可/禁止私聊" | 更新群设置 | `chat group update-settings` | `chat group set-admin` | update-settings 改群功能开关;set-admin 是管理员角色操作 |
| "改群昵称/设置群昵称/我在群里的名字" | 设置个人群昵称 | `chat group update-nick` | `chat group rename` | update-nick 改自己的群昵称;rename 改群名称 |
| "群备注/给群加备注/修改群备注" | 设置群备注 | `chat group update-alias` | `chat group rename` | update-alias 设置仅自己可见的备注;rename 改群名称全员可见 |
| "隐藏会话/隐藏群聊/隐藏对话" | 隐藏会话 | `chat hide` | `chat mute` | hide 隐藏会话不显示;mute 是免打扰但仍显示 |
| "关闭@所有人通知/屏蔽@all/不接收@所有人" | 关闭 @所有人提醒 | `chat mute-at-all` | `chat mute` | mute-at-all 仅屏蔽 @所有人;mute 是整个会话免打扰 |
| "关闭红包通知/屏蔽红包/不接收红包提醒" | 关闭红包提醒 | `chat mute-red-envelope` | `chat mute` | mute-red-envelope 仅屏蔽红包;mute 是整个会话免打扰 |
| "解散群/解散群聊" | 解散群聊 | `chat group dismiss` | `chat group quit` | dismiss 是群主解散整个群(不可逆);quit 是当前用户自己退群 |
| "新成员看历史/历史消息可见范围" | 设置新成员可见历史消息 | `chat group set-history` | `chat group update-settings` | set-history 控制新成员入群后可见历史消息范围;update-settings 是其他群功能开关 |
| "群里有哪些机器人/查看群机器人/列出群机器人" | 查看群内机器人列表 | `chat group bots` | `chat group members` | bots 只列机器人;members 列普通群成员 |
| "从群里移除机器人/踢机器人" | 移除群内机器人 | `chat group members remove-bot` | `chat group members add-bot` | remove-bot 通过 openBotId 移除;add-bot 通过 robotCode 添加 |
| "批量查群成员详情/按ID查群成员/查看指定成员信息" | 批量查询群成员详情 | `chat group members list-by-ids` | `chat group members` | list-by-ids 根据 openDingTalkId 列表查询指定成员详情;members 分页查询全部成员列表 |
| "标记未读/设为未读/会话标未读" | 标记会话为未读 | `chat mark-unread` | `chat clear-red-point` | mark-unread 标记为未读;clear-red-point 清除红点(相反操作) |
| "清除红点/已读/取消未读" | 清除会话红点 | `chat clear-red-point` | `chat mark-unread` | clear-red-point 清除单个会话红点;mark-unread 标记未读(相反操作) |
| "全部已读/一键清除所有未读/红点清零" | 清除所有会话红点 | `chat clear-all-red-point` | `chat clear-red-point` | clear-all-red-point 清除所有会话红点;clear-red-point 只清单个会话 |
| "所有会话/全部会话列表/我的会话" | 分页获取全部会话 | `chat list-all-conversations` | `chat list-top-conversations` | list-all-conversations 返回所有会话;list-top-conversations 仅返回置顶会话 |
| "清空聊天记录/删除会话消息/清空消息" | 清空会话聊天记录 | `chat clear-messages` | `chat clear-red-point` | clear-messages 清空聊天记录;clear-red-point 仅清除未读红点 |
| "标记已读/消息已读/读了" | 标记消息已读 | `chat mark-read` | `chat clear-red-point` | mark-read 标记指定消息及之前的消息为已读;clear-red-point 仅清除红点不标记消息 |
| "我加入的所有群/拉取全部群列表/我的群" | 分页拉取所有群 | `chat group list-all` | `chat group list-my-groups` | list-all 返回用户加入的所有群;list-my-groups 仅返回用户作为群主/管理员的群 |
| "入群申请记录/谁申请入群/群申请列表/查看入群审批" | 拉取入群验证记录 | `chat group list-join-validations` | `chat group list-all` | list-join-validations 拉取入群验证/审批记录;list-all 拉取群列表 |
| "通过入群申请/拒绝入群/审批入群/同意加群" | 审批入群验证 | `chat group audit-join-validation` | `chat group list-join-validations` | audit-join-validation 执行审批操作;list-join-validations 仅查询记录 |
| "发群公告/在群里发公告/群公告置顶" | 发布群公告 | `chat group notice create` | `blackboard create` | notice create 是群维度公告(发到指定群聊);blackboard 是企业公告(面向全员,不可撤回) |
| "改群公告/修改群公告/更新群公告" | 修改群公告 | `chat group notice edit` | `chat group notice create` | edit 整体替换已有公告内容(需 dataId);create 发布新公告 |
| "查群公告/看群公告/群公告列表/群里有什么公告" | 查看群公告列表 | `chat group notice list` | `blackboard list` | notice list 查指定群的公告;blackboard list 查企业公告 |
| "群公告详情/公告已读人数/谁读了公告" | 查看群公告详情 | `chat group notice get` | `chat group notice list` | get 返回单条公告详情(含已读人数、点赞/评论数);list 分页拉公告列表 |
| "搜索机器人/找机器人/查机器人/帮我找XXX机器人" | 搜索全部可用机器人 | `chat bot find` | `chat bot search` | find 返回全部可用机器人(含他人/官方),额外返回 openDingTalkId(可用于给机器人发单聊消息);search 仅返回我创建的 |
| "给机器人发单聊/给机器人发消息/跟机器人聊天" | 给机器人发单聊消息 | `chat bot find` → `chat message send --open-dingtalk-id` | `chat bot search` | 必须先用 find 拿 openDingTalkId(search 没有此字段),再用 send --open-dingtalk-id 发单聊 |
| "我创建的机器人/我的机器人/我自己的机器人/查看我的机器人" | 搜索我创建的机器人 | `chat bot search` | `chat bot find` | search 仅返回当前用户自己创建的机器人(返回 robotCode + robotName,无 openDingTalkId);find 返回全部可用机器人 |
| "合并转发/批量转发/合并转发多条消息" | 合并转发多条消息 | `chat message combine-forward` | `chat message forward` | combine-forward 合并多条为一条转发;forward 转发单条消息 |
| "转发话题/转发话题消息/话题转发到另一个群" | 转发话题消息 | `chat message forward-topic` | `chat message forward` | forward-topic 专用于转发话题消息(需要话题ID);forward 转发普通单条消息 |
| "发卡片消息/推送流式卡片" | 创建并推送流式卡片 | `chat message send-card` | `chat message send` | send-card 发流式卡片;send 发普通文本/Markdown 消息 |
| "更新卡片/流式更新卡片" | 流式更新卡片内容 | `chat message update-card` | `chat message send-card` | update-card 更新已有卡片;send-card 创建新卡片 |
| "钉住消息/Pin消息/置顶消息到会话" | 钉住消息 | `chat message set-pin-msg` | `chat set-top` | set-pin-msg 钉住单条消息(Pin);set-top 置顶整个会话 |
| "取消钉住/Unpin消息" | 取消钉住消息 | `chat message unset-pin-msg` | `chat message set-pin-msg` | unset-pin-msg 取消钉住;set-pin-msg 设置钉住 |
| "查看钉住的消息/Pin列表/钉住消息列表" | 拉取钉住消息列表 | `chat message list-pin-msg` | `chat message list` | list-pin-msg 只返回被钉住的消息;list 拉取全部消息 |
| "置顶某条消息/把这条消息置顶" | 置顶消息 | `chat message set-top-msg` | `chat set-top` | set-top-msg 置顶会话内某条消息;set-top 置顶整个会话在列表中 |
| "取消置顶消息/取消消息置顶" | 取消置顶消息 | `chat message unset-top-msg` | `chat message set-top-msg` | unset-top-msg 取消置顶;set-top-msg 设置置顶 |
| "DING消息/查DING/DING历史" | 查询 DING 消息列表 | `ding message list` | `chat message list` | ding 是独立顶层命令;ding message list 查 DING 消息;chat message list 查普通聊天消息 |
| "DING接收状态/谁收到了DING" | DING 接收状态 | `ding message receiver-status` | `chat message read-status` | ding 是独立顶层命令;receiver-status 查 DING 接收;chat message read-status 查普通消息已读 |
| "发DING/DING通知" | 发送 DING 消息 | `ding message send` | `chat message send` | DING 是钉钉的强提醒(应用内/短信/电话),独立顶层命令;普通群消息用 chat |
| "撤回DING" | 撤回 DING 消息 | `ding message recall` | `chat message recall` | DING 撤回独立命令;chat recall 是撤回普通聊天消息 |
| "搜一下智能化方案/最近 OKR 相关邮件/最近发版相关消息" | 搜企业知识内容 | `aisearch enterprise` | `doc search` / `mail search` / `chat message search` | 跨文档、消息、日程、听记、邮件等企业内容语义检索走 enterprise;具体 `queries/types/time-range` 抽槽见 `aisearch.md` |
| "我发给某人的消息/邮件/文档/今天我干了什么" | 搜行为记录 | `aisearch behavior` | `chat` / `mail` / `doc` / `report` | 关注“谁对什么做过什么”,走 behavior;具体 `behavior-type/direction/chat-scope` 抽槽见 `aisearch.md` |
| "把这段文字翻译成英文/translate this" | 通用文本翻译 | `chat text translate` | `doc` / `aisearch` | 纯文本翻译,不是文档编辑或语义搜索 |
| "帮我把这个文档翻译成日文" | 文档内容翻译 | 先 `doc get` 再 `chat text translate` | `chat text translate` 直接传文件 | translate 仅支持纯文本,需先提取文档内容 |
@@ -0,0 +1,7 @@
# chat Lite Recipe
本文件从单 Skill `lite-recipes.md` 拆分而来,仅保留与本产品相关的轻量流程。
## #1 消息沟通
所有消息沟通相关的命令详情、参数说明、意图路由和复合工作流,请查阅 [chat.md](./chat-commands.md)。

Some files were not shown because too many files have changed in this diff Show More