auto backup 2026-07-02 11:00
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": 1,
|
||||
"registry": "https://clawhub.ai",
|
||||
"slug": "baidu-text-translate",
|
||||
"ownerHandle": "baidu-translate",
|
||||
"installedVersion": "1.0.1",
|
||||
"installedAt": 1782698862709,
|
||||
"fingerprint": "c53ed58ebd184b8f85b7c237b04b63d7b9544a84dc2e4a127b1a656d2e418104"
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
name: baidu-text-translate
|
||||
description: Use this skill whenever you need to translate text with the trans-cli tool (Baidu Translation AI API). Covers JSON output contract, Baidu-specific language codes (jp/kor/fra/spa/ara — not ISO defaults), exit code semantics, error decision tree, first-time API key setup, QUOTA_EXCEEDED / AUTH_FAILED recovery, environment self-diagnosis via `trans doctor`, and listing supported languages via `trans languages`. Trigger on any of: running `trans text`, shell pipelines with translation, trans-cli errors, API key configuration, `trans doctor` checks, or looking up language codes.
|
||||
homepage: https://fanyi.baidu.com
|
||||
metadata: {"clawdbot":{"emoji":"🌐","requires":{"bins":["trans"],"env":["TRANS_API_KEY"]},"install":[{"id":"npm","kind":"npm","package":"@bdtrans/trans-cli","bins":["trans"],"label":"Install trans-cli (npm)"}]}}
|
||||
---
|
||||
|
||||
# baidu_text_translate — Agent Reference
|
||||
|
||||
`trans text` wraps the Baidu AI Translation API. Use `--json` — it separates
|
||||
success (stdout) from errors (stderr) and gives you structured fields to act on.
|
||||
|
||||
```bash
|
||||
trans text --json "你好世界" # auto-detect → English
|
||||
trans text --json --from zh --to jp "你好" # explicit languages
|
||||
trans text --json --to fra --reference "使用正式语气" "你好" # custom instruction
|
||||
echo "早上好" | trans text --json # stdin pipe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Diagnosis
|
||||
|
||||
Before translating, verify the environment with `trans doctor`:
|
||||
|
||||
```bash
|
||||
trans doctor # human-readable output
|
||||
trans doctor --json # JSON output for Agent parsing
|
||||
```
|
||||
|
||||
**JSON contract** (stdout):
|
||||
```json
|
||||
{
|
||||
"checks": [
|
||||
{"name":"api_key", "ok":true, "source":"env/config", "message":"configured"},
|
||||
{"name":"connectivity","ok":true, "latency_ms":243, "message":"reachable"},
|
||||
{"name":"account", "ok":true, "message":"valid"}
|
||||
],
|
||||
"all_ok": true
|
||||
}
|
||||
```
|
||||
|
||||
| Exit | `all_ok` | Meaning |
|
||||
|------|----------|---------|
|
||||
| 0 | `true` | All checks passed — ready to translate |
|
||||
| 2 | `false` | At least one check failed — inspect `checks[].ok` |
|
||||
|
||||
**When a check fails**, `help_url` in the failing check points to the fix:
|
||||
- `api_key.ok:false` → set `TRANS_API_KEY` (see Configuration below)
|
||||
- `account.ok:false` → key invalid/expired or quota exhausted
|
||||
|
||||
Run `trans doctor --json` as the first step when diagnosing any trans-cli failure.
|
||||
|
||||
---
|
||||
|
||||
## JSON Contract
|
||||
|
||||
**Success** (stdout, exit 0):
|
||||
```json
|
||||
{"from":"zh","to":"en","source":"你好","translated":"Hello"}
|
||||
```
|
||||
|
||||
| Field | Note |
|
||||
|-------|------|
|
||||
| `from` | API-detected language — may differ from `--from auto` |
|
||||
| `to` | Target language |
|
||||
| `source` | Original input |
|
||||
| `translated` | Result |
|
||||
|
||||
**Error** (stderr, exit ≠ 0):
|
||||
```json
|
||||
{"code":"AUTH_FAILED","message":"…","help_url":"https://…","raw_code":"52001"}
|
||||
```
|
||||
|
||||
`help_url` and `raw_code` are omitted when not applicable.
|
||||
|
||||
---
|
||||
|
||||
## Error Decision Tree
|
||||
|
||||
| Exit | `code` | Cause | Action |
|
||||
|------|--------|-------|--------|
|
||||
| 0 | — | OK | Use `translated` |
|
||||
| 1 | `INVALID_INPUT` | Empty text | Fix input, don't retry |
|
||||
| 1 | `INVALID_LANGUAGE` | Wrong lang code (e.g. `ja` → should be `jp`) | Fix `--to`/`--from`, see Language Codes below |
|
||||
| 2 | `CONFIG_MISSING` | `TRANS_API_KEY` not set | Stop — run `trans doctor --json` then guide user to set up API key |
|
||||
| 2 | `AUTH_FAILED` | Key invalid/expired (`raw_code` 52001–52003) | Stop — run `trans doctor --json` then guide user to API key page |
|
||||
| 3 | `QUOTA_EXCEEDED` | Balance = 0 | Stop — guide user to recharge (see below) |
|
||||
| 3 | `RATE_LIMITED` | QPS exceeded | Wait 1 s, retry once |
|
||||
| 3 | `API_ERROR` | Unexpected API error | Retry once; if persists, report `raw_code` + `message` |
|
||||
| 4 | `NETWORK_ERROR` | No connectivity | Retry twice with 2 s gap; if persists, report |
|
||||
|
||||
Exit 2 requires human intervention — the agent cannot fix auth or config.
|
||||
Exit 1 means the agent passed bad arguments — fix the call, not the environment.
|
||||
|
||||
---
|
||||
|
||||
## Language Codes
|
||||
|
||||
Baidu does **not** follow ISO 639. When unsure of a code, query locally:
|
||||
|
||||
```bash
|
||||
trans languages # full list (200+ languages)
|
||||
trans languages --filter jp # search by code
|
||||
trans languages --filter 日语 # search by name
|
||||
trans languages --json # JSON output for Agent parsing
|
||||
```
|
||||
|
||||
The six most common ISO traps:
|
||||
|
||||
| Baidu (correct) | ISO (wrong for Baidu) | Language |
|
||||
|-----------------|----------------------|----------|
|
||||
| `jp` | `ja` | Japanese |
|
||||
| `kor` | `ko` | Korean |
|
||||
| `fra` | `fr` | French |
|
||||
| `spa` | `es` | Spanish |
|
||||
| `ara` | `ar` | Arabic |
|
||||
| `vie` | `vi` | Vietnamese |
|
||||
|
||||
Other common codes that match ISO: `zh` `cht` `en` `ru` `de` `pt` `it` `nl` `hi` `th`
|
||||
Use `auto` for source-language auto-detection.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Priority: `--api-key flag > TRANS_API_KEY env > ~/.trans-cli/config.json`
|
||||
|
||||
Config file field: `api_key`.
|
||||
|
||||
```bash
|
||||
trans config init # create empty config skeleton (~/.trans-cli/config.json)
|
||||
trans config init --force # overwrite existing config
|
||||
trans config set api_key <KEY> # write api_key (creates file if absent)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting an API Key (First-Time Setup)
|
||||
|
||||
Guide the user through these steps — the agent cannot do this on their behalf:
|
||||
|
||||
1. Open the Baidu Translation Open Platform and sign in: <https://fanyi-api.baidu.com/>
|
||||
2. Go to Developer Center: <https://fanyi-api.baidu.com/manage/developer>
|
||||
3. Click "Use Now" → select "General Translation" → complete identity verification
|
||||
4. Get your API Key: <https://fanyi-api.baidu.com/manage/apiKey>
|
||||
5. Write to config: `trans config set api_key <KEY>`
|
||||
or set the environment variable `TRANS_API_KEY`
|
||||
6. Verify: `trans doctor --json` — `all_ok:true` means success
|
||||
|
||||
---
|
||||
|
||||
## Recharging (QUOTA_EXCEEDED)
|
||||
|
||||
Recharge portal: <https://fanyi-api.baidu.com/manage/account>
|
||||
Takes effect immediately — no restart needed. Auto-renewal can be enabled on the same page.
|
||||
Usage details: <https://fanyi-api.baidu.com/api/trans/user/usage>
|
||||
|
||||
---
|
||||
|
||||
## Non-Obvious Behaviours
|
||||
|
||||
- Multiple positional args join with a space: `trans text hello world` → `"hello world"`
|
||||
- When both stdin and positional args are given, positional args win silently
|
||||
- The `from` field in JSON is the API's detected language, not the `--from` flag value
|
||||
- `trans doctor` checks are short-circuit: if `api_key` fails, connectivity/account are skipped
|
||||
- `--reference` is optional; omitting it sends no extra field to the API
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"ownerId": "kn7fhw0h9fm3z24r13j111w56583yfp7",
|
||||
"slug": "baidu-text-translate",
|
||||
"version": "1.0.1",
|
||||
"publishedAt": 1777433285140
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
## Description: <br>
|
||||
Translate text through trans-cli with the Baidu Translation AI API, including JSON output, Baidu language codes, API key setup, diagnostics, and error handling. <br>
|
||||
|
||||
This skill is ready for commercial/non-commercial use. <br>
|
||||
|
||||
## Publisher: <br>
|
||||
[baidu-translate](https://clawhub.ai/user/baidu-translate) <br>
|
||||
|
||||
### License/Terms of Use: <br>
|
||||
MIT-0 <br>
|
||||
|
||||
|
||||
## Use Case: <br>
|
||||
Developers and agents use this skill to translate text, diagnose trans-cli setup, choose Baidu-specific language codes, and recover from common Baidu Translation API errors. <br>
|
||||
|
||||
### Deployment Geography for Use: <br>
|
||||
Global <br>
|
||||
|
||||
## Known Risks and Mitigations: <br>
|
||||
Risk: Translated text is sent to Baidu through the external trans-cli package and Baidu Translation API. <br>
|
||||
Mitigation: Install only if the external package and Baidu text processing are acceptable for the intended data, and avoid sending sensitive text unless approved. <br>
|
||||
Risk: The skill requires a Baidu API key through TRANS_API_KEY or trans-cli configuration. <br>
|
||||
Mitigation: Prefer a secure environment variable or secret manager; keep ~/.trans-cli/config.json permissions restrictive and rotate exposed keys. <br>
|
||||
Risk: Baidu language codes differ from common ISO codes and can cause invalid-language failures. <br>
|
||||
Mitigation: Use trans languages or the documented Baidu code table before issuing translation commands. <br>
|
||||
|
||||
|
||||
## Reference(s): <br>
|
||||
- [ClawHub Skill Page](https://clawhub.ai/baidu-translate/baidu-text-translate) <br>
|
||||
- [Baidu Translate](https://fanyi.baidu.com) <br>
|
||||
- [Baidu Translation Open Platform](https://fanyi-api.baidu.com/) <br>
|
||||
- [Baidu Translation Developer Center](https://fanyi-api.baidu.com/manage/developer) <br>
|
||||
- [Baidu Translation API Key Management](https://fanyi-api.baidu.com/manage/apiKey) <br>
|
||||
- [Baidu Translation Account Recharge](https://fanyi-api.baidu.com/manage/account) <br>
|
||||
- [Baidu Translation Usage Details](https://fanyi-api.baidu.com/api/trans/user/usage) <br>
|
||||
|
||||
|
||||
## Skill Output: <br>
|
||||
**Output Type(s):** [text, markdown, shell commands, configuration, guidance] <br>
|
||||
**Output Format:** [Markdown guidance with inline shell commands and JSON examples] <br>
|
||||
**Output Parameters:** [1D] <br>
|
||||
**Other Properties Related to Output:** [May guide use of trans text --json, trans doctor --json, trans languages, and TRANS_API_KEY configuration.] <br>
|
||||
|
||||
## Skill Version(s): <br>
|
||||
1.0.1 (source: server release evidence) <br>
|
||||
|
||||
## Ethical Considerations: <br>
|
||||
Users should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>
|
||||
@@ -0,0 +1,178 @@
|
||||
Copyright 2026 Alibaba Group
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,17 @@
|
||||
DingTalk Workspace CLI (dws)
|
||||
Copyright 2026 Alibaba Group
|
||||
|
||||
This product includes software developed at
|
||||
DingTalk (https://www.dingtalk.com/).
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
name: dws
|
||||
description: 管理钉钉产品能力(AI表格/日历/通讯录/群聊与机器人/待办/审批/考勤/日志/DING消息/开放平台文档/钉钉文档/钉钉云盘/AI听记/邮箱/在线电子表格/知识库等)。当用户需要操作表格数据、管理日程会议、查询通讯录、管理群聊、机器人发消息、创建待办、提交审批、查看考勤、提交日报周报(钉钉日志模版)、读写钉钉文档、上传下载云盘文件、查询听记纪要、收发邮件、读写在线电子表格(axls)、管理钉钉知识库时使用。
|
||||
cli_version: ">=1.0.15"
|
||||
---
|
||||
|
||||
# 钉钉全产品 Skill
|
||||
|
||||
通过 `dws` 命令管理钉钉产品能力。
|
||||
|
||||
## 严格禁止 (NEVER DO)
|
||||
- 不要使用 dws 命令以外的方式操作(禁止 curl、HTTP API、浏览器)
|
||||
- 不要编造 UUID、ID 等标识符,必须从命令返回中提取
|
||||
- 不要猜测字段名/参数值,操作前必须先查询确认
|
||||
|
||||
## 严格要求 (MUST DO)
|
||||
- 所有命令必须加 `--format json` 以获取可解析输出
|
||||
- 危险操作必须先向用户确认,用户同意后才加 `--yes` 执行
|
||||
- 单次批量操作不超过 30 条记录
|
||||
- 所有命令必须**严格遵循**对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开)
|
||||
|
||||
|
||||
## 产品总览
|
||||
|
||||
| 产品 | 用途 | 参考文件 |
|
||||
|-------------------|------------------------------------------------------|----------------------------------------------------------------|
|
||||
| `aitable` | AI表格:Base/数据表/字段/记录/视图/附件/图表/仪表盘/导入导出/模板搜索 | [aitable.md](./references/products/aitable.md) |
|
||||
| `attendance` | 考勤:打卡记录/排班查询/考勤规则/汇总统计 | [attendance.md](./references/products/attendance.md) |
|
||||
| `calendar` | 日历:日程/参与者/会议室/闲忙查询/时间建议 | [calendar.md](./references/products/calendar.md) |
|
||||
| `chat` | 群聊与机器人:搜索群/建群/群成员管理/改群名/机器人群发/单聊/撤回/Webhook/机器人搜索 | [chat.md](./references/products/chat.md) |
|
||||
| `contact` | 通讯录:用户查询(当前用户/搜索/详情/手机号)/部门查询(搜索/成员列表) | [contact.md](./references/products/contact.md) |
|
||||
| `devdoc` | 开放平台文档:搜索开发文档 | [devdoc.md](./references/products/devdoc.md) |
|
||||
| `ding` | DING消息:发送/撤回(应用内/短信/电话) | [ding.md](./references/products/ding.md) |
|
||||
| `doc` | 钉钉文档:搜索/浏览/读写/块级编辑/评论/文件创建/复制/移动/重命名 | [doc.md](./references/products/doc.md) |
|
||||
| `drive` | 钉钉云盘:文件列表/元数据/文件夹/上传(两步)/下载 | [drive.md](./references/products/drive.md) |
|
||||
| `minutes` | AI听记:听记列表/摘要/关键词/转写/待办/思维导图/发言人/热词/上传 | [minutes.md](./references/products/minutes.md) |
|
||||
| `oa` | OA审批:待办/我发起的/表单模板/详情/审批流水/同意/拒绝/撤销 | [oa.md](./references/products/oa.md) |
|
||||
| `report` | 日志:按模版创建/收件箱/已发送/模版查看/详情/已读统计 | [report.md](./references/products/report.md) |
|
||||
| `mail` | 邮箱:邮箱地址查询/邮件搜索(KQL)/邮件详情/发送邮件 | [mail.md](./references/products/mail.md) |
|
||||
| `sheet` | 在线电子表格(axls):工作表 CRUD/区域读写/行列增删/合并/查找替换/筛选视图/导出(两步)/图片 | [sheet.md](./references/products/sheet.md) |
|
||||
| `todo` | 待办:创建(含优先级/截止时间/循环)/查询/修改/标记完成/删除 | [todo.md](./references/products/todo.md) |
|
||||
| `wiki` | 知识库:空间创建/详情/列表/搜索 + 成员管理 | [wiki.md](./references/products/wiki.md) |
|
||||
|
||||
## 意图判断决策树
|
||||
|
||||
用户提到"表格/多维表/AI表格/记录/数据/视图/图表/仪表盘" → `aitable`
|
||||
用户提到"考勤/打卡/排班" → `attendance`
|
||||
用户提到"日程/日历/会议室/约会/时间建议" → `calendar`
|
||||
用户提到"群聊/建群/群成员/群管理/机器人发消息/Webhook/机器人群发/机器人单聊/通知" → `chat`
|
||||
用户提到"通讯录/同事/部门/组织架构" → `contact`
|
||||
用户提到"开发/API/调用错误 文档" → `devdoc`
|
||||
用户提到"DING/紧急消息/电话提醒" → `ding`
|
||||
用户提到"钉钉文档/云文档/知识库/读写文档/块级编辑/文档评论/文档复制移动" → `doc`
|
||||
用户提到"云盘/文件存储/文件上传下载/文件夹" → `drive`
|
||||
用户提到"听记/AI听记/会议纪要/转写/摘要/思维导图/发言人/热词" → `minutes`
|
||||
用户提到"邮箱/邮件/发邮件/收邮件/搜邮件/查邮件" → `mail`
|
||||
用户提到"审批/请假/报销/出差/加班/同意/拒绝/撤销审批" → `oa`
|
||||
用户提到"日志/日报/周报/日志统计/写日报/提交周报/发日志/填日志" → `report`
|
||||
用户提到"在线电子表格/钉钉表格/axls/工作表/单元格读写/合并单元格/筛选视图/导出 xlsx" → `sheet`
|
||||
用户提到"待办/TODO/任务提醒/循环待办" → `todo`
|
||||
用户提到"知识库/wiki/团队空间/知识库成员管理" → `wiki`
|
||||
|
||||
关键区分: aitable(数据表格) vs todo(待办任务)
|
||||
关键区分: report(钉钉日志/日报周报) vs todo(待办任务)
|
||||
关键区分: chat send-by-bot(机器人身份发消息) vs send-by-webhook(自定义机器人Webhook告警)
|
||||
关键区分: doc(钉钉文档/富文本协同) vs drive(钉钉云盘/二进制文件)
|
||||
关键区分: oa tasks(审批 taskId,审批/拒绝用) vs oa list-pending(收件箱 processInstanceId,查看用)
|
||||
|
||||
|
||||
> 更多易混淆场景见 [intent-guide.md](./references/intent-guide.md)
|
||||
|
||||
## 危险操作确认
|
||||
|
||||
以下操作为不可逆或高影响操作,执行前**必须先向用户展示操作摘要并获得明确同意**,同意后才加 `--yes` 执行。
|
||||
|
||||
| 产品 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| `aitable` | `base delete` | 删除整个 AI 表格,含全部数据表和记录 |
|
||||
| `aitable` | `table delete` | 删除数据表(含全部字段/视图/记录) |
|
||||
| `aitable` | `field delete` | 删除字段(该列所有值同步清空) |
|
||||
| `aitable` | `view delete` | 删除视图 |
|
||||
| `aitable` | `record delete` | 删除记录(支持批量) |
|
||||
| `aitable` | `chart delete` / `dashboard delete` | 删除图表/仪表盘 |
|
||||
| `calendar` | `event delete` | 删除日程,所有参与者同步取消 |
|
||||
| `calendar` | `participant delete` | 移除日程参与者 |
|
||||
| `calendar` | `room delete` | 取消会议室预定 |
|
||||
| `chat` | `group members remove` | 移除群成员 |
|
||||
| `chat` | `message recall-by-bot` | 撤回机器人已发消息 |
|
||||
| `doc` | `block delete` | 删除文档块(不可恢复) |
|
||||
| `ding` | `message recall` | 撤回已发 DING 消息 |
|
||||
| `oa` | `approval revoke` | 撤销自己发起的审批实例 |
|
||||
| `oa` | `approval reject` | 拒绝待审批(需加明确理由) |
|
||||
| `todo` | `task delete` | 删除待办 |
|
||||
| `minutes` | `replace-text` | 全文批量替换转写与摘要 |
|
||||
|
||||
### 确认流程
|
||||
```
|
||||
Step 1 → 展示操作摘要(操作类型 + 目标对象 + 影响范围)
|
||||
Step 2 → 用户明确回复确认(如 "确认" / "好的")
|
||||
Step 3 → 加 --yes 执行命令
|
||||
```
|
||||
|
||||
## 核心流程
|
||||
作为一个智能助手,你的首要任务是**理解用户的真实、完整的意图**,而不是简单地执行命令。在选择 `dws` 的产品命令前,必须严格遵循以下四步流程:
|
||||
|
||||
1. 意图分类:首先,判断用户指令的核心 动词/动作 属于哪一类。这比关注名词更重要。
|
||||
2. 歧义处理与信息追问:如果用户指令模糊或包含多个产品的关键字,严禁猜测。必须主动向用户追问以澄清意图。这是你作为智能助手而非命令执行器的核心价值。
|
||||
3. 精准产品映射:在完成前两步,意图已经清晰后,参考产品总览和意图判断决策树 来选择产品。
|
||||
4. 充分阅读产品参考文件,通过编写代码或直接调用指令实现用户意图。
|
||||
|
||||
## 命令发现(flag / 参数以 binary 为准)
|
||||
|
||||
产品参考文档(`references/products/*.md`)里的 flag 列表是**便于理解用途的参考**,不是权威契约。参数名称、默认值、必填约束随服务发现动态变化,**以下两个命令的输出才是调用的事实源**:
|
||||
|
||||
```bash
|
||||
# 1) 人读视图:看 Usage / Example / Flags
|
||||
dws <command-path> --help
|
||||
# 例:dws calendar event list --help
|
||||
|
||||
# 2) 机读视图:JSON Schema + flag 别名映射 + 必填字段
|
||||
dws schema # 列出所有产品及工具
|
||||
dws schema <product>.<canonical_name> # 规范路径(如 calendar.list_suggested_event_times)
|
||||
dws schema "<product> <group> <cli_name>" # CLI 路径(如 "calendar event list")
|
||||
dws schema <path> --jq '.tool.flag_overlay' # 只看 flag 别名
|
||||
dws schema <path> --jq '.tool.required' # 只看必填字段
|
||||
```
|
||||
|
||||
**何时用哪条路径:**
|
||||
- 只需看某个命令怎么调用 → `dws <cmd> --help`
|
||||
- 构造 `--params` / `--json` 时不确定字段类型、必填、别名 → `dws schema <path>`
|
||||
- 参考文档和 `--help` 冲突时 → **以 `--help` / `dws schema` 为准**,文档视为过期
|
||||
|
||||
`dws schema` 输出的 `flag_overlay[key].alias` 就是实际生效的 flag 名(如 `attendeeUserIds → --attendee-user-ids`);`parameters[key]` 是原始 JSON Schema;`required` 是必填字段数组;`sensitive: true` 表示写/删操作,须先向用户确认再加 `--yes`。
|
||||
|
||||
## 错误处理
|
||||
1. 遇到错误,加 `--verbose` 重试一次
|
||||
2. 若 stderr 出现 `RECOVERY_EVENT_ID=<event_id>`,优先按 [recovery-guide.md](./references/recovery-guide.md) 执行 recovery 闭环
|
||||
3. 仍然失败,报告完整错误信息给用户,禁止自行尝试替代方案
|
||||
4. 认证失败时,参考 [global-reference.md](./references/global-reference.md) 中的认证章节处理
|
||||
5. 各产品高频错误及排查流程见 [error-codes.md](./references/error-codes.md)
|
||||
|
||||
|
||||
## 详细参考 (按需读取)
|
||||
|
||||
- [references/products/](./references/products/) — 各产品命令详细参考(flag 细节以 `--help` / `dws schema` 为准)
|
||||
- [references/intent-guide.md](./references/intent-guide.md) — 意图路由指南(易混淆场景对照)
|
||||
- [references/global-reference.md](./references/global-reference.md) — 全局标志、认证、输出格式
|
||||
- [references/field-rules.md](./references/field-rules.md) — AI表格字段类型规则
|
||||
- [references/error-codes.md](./references/error-codes.md) — 错误码 + 调试流程
|
||||
- [references/recovery-guide.md](./references/recovery-guide.md) — recovery 闭环、`RECOVERY_EVENT_ID`、`execute/finalize` 规范
|
||||
- [scripts/](./scripts/) — 各产品批量操作脚本(AI表格/日历/机器人消息/通讯录/考勤/日志/待办等)
|
||||
@@ -0,0 +1,95 @@
|
||||
# 错误码说明
|
||||
全产品错误参考 + 调试流程。Agent 遇到错误时查阅此文档。
|
||||
|
||||
## 错误返回格式
|
||||
|
||||
```json
|
||||
{"success": false, "code": "InvalidParameter", "message": "baseId is required"}
|
||||
{"success": false, "code": "AUTH_TOKEN_EXPIRED", "message": "Token验证失败"}
|
||||
{"success": false, "code": "PermissionDenied", "message": "无权限访问该资源"}
|
||||
```
|
||||
|
||||
## 错误分类与 Agent 行为
|
||||
|
||||
### 可自行修复
|
||||
- 参数缺失 / 格式错误 / ID 无效 → 检查参数后修正重试
|
||||
|
||||
### 需用户介入
|
||||
- 权限不足 / 资源不存在 / 配额超限 → 报告完整错误信息给用户,不要自行尝试替代方案
|
||||
|
||||
## 通用错误
|
||||
- 请求超时 — 网络慢或服务端响应慢 → `--timeout 60` 重试
|
||||
- 网络连接失败 — 无法连接 MCP Server → 用最简命令验证: `dws contact user get-self --format json`
|
||||
- stderr 出现 `RECOVERY_EVENT_ID=<event_id>` — runtime 失败已被 CLI 捕获 → 优先执行 `dws recovery execute --event-id <event_id> --format json`
|
||||
|
||||
## Recovery 闭环
|
||||
|
||||
- `dws recovery plan --last|--event-id <event_id> --format json`:读取失败快照并生成恢复计划
|
||||
- `dws recovery execute --last|--event-id <event_id> --format json`:生成带 `doc_search`、`probe_results`、`agent_task` 的分析包
|
||||
- `dws recovery finalize --event-id <event_id> --outcome recovered|failed|handoff --execution-file execution.json --format json`:回写恢复结果
|
||||
|
||||
执行 `finalize` 时:
|
||||
|
||||
- `execution-file` 至少应包含 `actions`、`attempts`、`result`、`error_summary`
|
||||
- 兼容旧格式:`action` + 数值型 `attempts`
|
||||
- 若 recovery bundle 已进入 unknown/agent 路线,不要把 `human_actions` 视为最终结论;必须结合完整 bundle 判断
|
||||
|
||||
更多字段解释见 [recovery-guide.md](./recovery-guide.md)。
|
||||
|
||||
---
|
||||
|
||||
## aitable 高频错误
|
||||
|
||||
> 参数体系: `baseId / tableId / fieldId / recordId`。CLI flag 用 kebab-case(`--base-id`),JSON 内用 camelCase(`baseId`)。
|
||||
|
||||
- 参数缺失 / 无效请求 — 还在用旧参数 `dentryUuid` / `--doc` / `--sheet` → 改用 `--base-id` / `--table-id` / `--field-id` / `--record-ids`
|
||||
- 参数传了但服务端没收到 — flag 用了 camelCase(如 `--baseId`)→ flag 用 kebab-case: `--base-id <ID>`
|
||||
- `record query --filters` 无结果 — 单选/多选过滤用了 option name 而非 id → 先 `field get` 读取 options,用 option id 过滤
|
||||
- record create/update 失败 — `cells` key 用了字段名(应为 fieldId);特殊字段格式错误 → 先 `table get` 拿字段目录;url 传 `{"text":"..","link":".."}`
|
||||
- 更新选项后历史数据异常 — 更新 options 没传完整列表 / 没保留原 id → 先 `field get` 取完整配置,保留已有 option 的 id
|
||||
- `cannot delete the last table` — 该表是 Base 最后一张表 → 先新建表再删旧表,或用 `base delete`
|
||||
- `formula` 类型 `not supported yet` — 部分字段类型暂不支持 API 创建 → 复杂字段拆开单独创建,先建基础结构
|
||||
|
||||
**排查链路**: `base list` → `base get`(→tableId) → `table get`(→fieldId) → `record query`(→recordId)。别跳步,别猜 ID。
|
||||
|
||||
**批量上限**: record 100 条 / field 15 个 / table·field 详情 10 个。
|
||||
|
||||
---
|
||||
|
||||
## approval 高频错误
|
||||
|
||||
- approve/reject 缺少 taskId — 未先获取审批任务 → 先 `approval tasks --instance-id <ID>` 获取 taskId
|
||||
- list-initiated 缺少 processCode — 未查询审批表单 → 先 `approval list-forms` 获取 processCode
|
||||
- 撤销审批失败 — 非本人发起的审批 → `revoke` 只能撤销自己发起的审批
|
||||
|
||||
---
|
||||
|
||||
## chat 高频错误
|
||||
|
||||
- 参数互斥报错 — `--group` 与 `--users` 同时传入 → 群聊用 `--group`,单聊用 `--users`,二者互斥
|
||||
- 群不存在 — openConversationId 不正确 → `chat search --query "群名"` 获取正确 ID
|
||||
- 机器人无法添加到群 — 当前用户非群管理员 → 报告给用户,需群管理员操作
|
||||
- Webhook Token 无效 — token 不正确或已失效 → 确认 Webhook Token 来源正确
|
||||
- 添加/移除群成员失败 — userId 不正确或无权限 → 先 `contact user search` 确认 userId,需当前用户为群管理员
|
||||
|
||||
---
|
||||
|
||||
## calendar 高频错误
|
||||
|
||||
- 时间格式错误 — 未使用 ISO-8601 格式 → 标准格式: `2026-03-10T14:00:00+08:00`
|
||||
- 会议室搜索报错 / 返空 — 企业会议室超 100 条未分组查询 → 先 `room list-groups` → 按 `--group-id` 逐组搜索
|
||||
- 参与者 / 会议室添加失败 — eventId 不正确 → 先 `event list` 或 `event create` 获取正确 eventId
|
||||
|
||||
---
|
||||
|
||||
## contact 高频错误
|
||||
|
||||
- `dept list-children` 报错 — `--id` 传了非整数值 → deptId 必须为整数,从 `dept search` 获取
|
||||
|
||||
---
|
||||
|
||||
## 通用排查三步法
|
||||
|
||||
1. **确认 ID** — 从最顶层资源逐级获取,不猜 ID、不跳步
|
||||
2. **确认参数** — flag 用 kebab-case,JSON 用 camelCase;特殊字段查产品参考文档确认格式
|
||||
3. **确认限制** — 检查批量上限和已知约束(各产品注意事项见对应产品参考文档)
|
||||
@@ -0,0 +1,105 @@
|
||||
# 易混淆操作与字段规则
|
||||
|
||||
## 易混淆操作 (高风险场景必读)
|
||||
|
||||
| 用户说的 | 正确命令 | 不是这个 |
|
||||
|---------|----------|---------|
|
||||
| "创建一个新表格 (Base)" | `base create` | 不是 `table create` |
|
||||
| "在表格里加一个数据表" | `table create` | 不是 `base create` |
|
||||
| "看看表格里有哪些表" | `base get` | 不是 `field get` |
|
||||
| "看看表里有哪些列" | `field get` / `table get` | 不是 `base get` |
|
||||
| "搜索表格" (找 Base) | `base search` | 不是 `record query` |
|
||||
| "搜索记录" (查表内数据) | `record query` | 不是 `base search` |
|
||||
| "删掉这个数据表" | `table delete` | 不是 `record delete` |
|
||||
| "删掉这条数据" | `record delete` | 不是 `table delete` |
|
||||
| "删掉这个列" | `field delete` | 不是 `record delete` |
|
||||
| "改字段类型" | 先 `field delete` 再 `field create` | `field update` **不能改类型** |
|
||||
| "移动字段/调整字段顺序" | **不支持**,需在钉钉界面手动拖拽 | 没有 `reorder`/`move` 命令 |
|
||||
|
||||
## field 子命令总览
|
||||
|
||||
> ⚠️ field 有且仅有以下 **4 个** 子命令,没有 `list`、`reorder`、`move`:
|
||||
|
||||
| 子命令 | 用途 |
|
||||
|-------|------|
|
||||
| `field get` | 获取字段详情(含完整 config/options)。**不是 `field list`** |
|
||||
| `field create` | **创建字段(支持通过 config.options 设置选项)** |
|
||||
| `field update` | 更新字段名称或配置(**不能改类型**) |
|
||||
| `field delete` | 删除字段(不可逆) |
|
||||
|
||||
## 字段创建时设置 config(重要)
|
||||
|
||||
创建 singleSelect/multipleSelect 字段时,**必须在 `--fields` 的 `config.options` 中设置选项**:
|
||||
|
||||
```bash
|
||||
# 创建带选项的单选字段
|
||||
dws aitable field create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--fields '[{"fieldName":"优先级","type":"singleSelect","config":{"options":[{"name":"高"},{"name":"中"},{"name":"低"}]}}]' \
|
||||
--format json
|
||||
|
||||
# 建表时也可以直接带选项字段
|
||||
dws aitable table create --base-id <BASE_ID> --name "任务表" \
|
||||
--fields '[{"fieldName":"任务","type":"text"},{"fieldName":"状态","type":"singleSelect","config":{"options":[{"name":"待办"},{"name":"进行中"},{"name":"已完成"}]}}]' \
|
||||
--format json
|
||||
```
|
||||
|
||||
> ⚠️ **不要混淆**:
|
||||
> - **字段创建**(field create / table create 的 --fields):通过 `config.options` 设置选项,创建时就指定
|
||||
> - **记录写入**(record create / record update 的 --records):只能写入已存在的选项名称
|
||||
|
||||
## 主字段约束(table create 必读)
|
||||
|
||||
> ⚠️ `table create` 的 `--fields` 中,**第一个字段自动成为主字段**。
|
||||
> 主字段只能是 **text** 类型,不能是 attachment、checkbox、formula 等。
|
||||
|
||||
**实际影响**:当用户要求创建的字段不适合做主字段时(如附件、复选框),必须:
|
||||
1. 先放一个 text 字段作为第一个字段(主字段)
|
||||
2. 再放用户要求的字段
|
||||
3. **告知用户**为何多了一个字段
|
||||
|
||||
```bash
|
||||
# 例: 用户要求只创建附件字段 → 附件不能做主字段,必须先加 text 主字段
|
||||
dws aitable table create --base-id <BASE_ID> --name "产品图片" \
|
||||
--fields '[{"fieldName":"名称","type":"text"},{"fieldName":"产品图片","type":"attachment"}]' \
|
||||
--format json
|
||||
```
|
||||
|
||||
## 只读字段 (不可写入)
|
||||
|
||||
以下类型的字段不可写入, 执行 `field get` / `table get` 后识别并跳过:
|
||||
- 创建时间 / 修改时间 (系统自动)
|
||||
- 创建人 / 修改人 (系统自动)
|
||||
- 自动编号
|
||||
- 公式字段
|
||||
- 引用字段
|
||||
|
||||
## 记录写入格式(record create / record update)
|
||||
|
||||
> 以下是 **记录写入** 时 cells 的格式,不是字段创建的格式。
|
||||
|
||||
| 类型 | 写入格式 | 读取返回格式 |
|
||||
|------|----------|-------------|
|
||||
| text | `"fldXXX":"文本值"` | `"fldXXX":"文本值"` |
|
||||
| number | `"fldXXX":123` | `"fldXXX":123` |
|
||||
| singleSelect | `"fldXXX":"选项名"` (必须是已存在的选项) | `"fldXXX":{"id":"x","name":"选项名"}` |
|
||||
| multipleSelect | `"fldXXX":["选项1","选项2"]` (必须是已存在的选项) | `"fldXXX":[{"id":"x","name":"选项1"}]` |
|
||||
| date | `"fldXXX":"2026-03-04"` 或 Unix ms | `"fldXXX":1709510400000` (ms) |
|
||||
| user | `"fldXXX":[{"userId":"123"}]` | `"fldXXX":{"uid":"123"}` |
|
||||
| attachment | `[{"fileToken":"<token>"}]` — 需先通过 `attachment upload` 获取,见下方 ⬇️ | `[{"url":"...","filename":"...","size":N}]` |
|
||||
|
||||
## ⚠️ 附件上传完整流程(必读!)
|
||||
|
||||
> **不要**使用钉盘 (drive) 上传来替代此流程!钉盘 fileId **无法**写入 attachment 字段。
|
||||
|
||||
附件字段写入使用 `upload_attachment.py` 脚本,**2 步**完成:
|
||||
|
||||
```bash
|
||||
# 步骤 1: 一键上传文件(脚本内部自动完成 prepare + PUT to OSS)
|
||||
python3 scripts/upload_attachment.py <BASE_ID> /path/to/photo.png
|
||||
# 输出: { "fileToken": "ft_xxx", "fileName": "photo.png", "size": 1024 }
|
||||
|
||||
# 步骤 2: 在 record create/update 中使用 fileToken
|
||||
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]' --format json
|
||||
```
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# 全局参考
|
||||
|
||||
## 认证
|
||||
|
||||
```bash
|
||||
# 首次: OAuth 设备流登录 (钉钉扫码授权)
|
||||
dws auth login
|
||||
|
||||
# 查看状态
|
||||
dws auth status
|
||||
|
||||
# 退出
|
||||
dws auth logout
|
||||
|
||||
# 重置本地凭证 (Token 解密失败时使用)
|
||||
dws auth reset
|
||||
```
|
||||
|
||||
登录后自动管理 token 刷新,日常使用无需重复登录。
|
||||
|
||||
| Token | 有效期 | 说明 |
|
||||
|-------|--------|------|
|
||||
| Access Token | 2 小时 | 调用 API 的凭证,过期自动刷新 |
|
||||
| Refresh Token | 30 天 | 换新 Access Token,使用后轮转 |
|
||||
|
||||
30 天内使用一次即自动续期。
|
||||
|
||||
### 认证失败处理
|
||||
- 命令返回 `AUTH_TOKEN_EXPIRED` / `USER_TOKEN_ILLEGAL` / "Token验证失败" → 执行 `dws auth login` 重新登录
|
||||
|
||||
### Headless 环境 (CI/CD)
|
||||
|
||||
```bash
|
||||
# 通过环境变量配置认证(无需交互式登录)
|
||||
export DWS_CLIENT_ID=<your-app-key>
|
||||
export DWS_CLIENT_SECRET=<your-app-secret>
|
||||
dws auth login
|
||||
|
||||
# 或使用 --device 设备流登录(远程服务器/Docker)
|
||||
dws auth login --device
|
||||
```
|
||||
refresh_token 单设备独占,远程刷新后源设备凭证失效。
|
||||
|
||||
## Recovery
|
||||
|
||||
当 runtime/MCP 命令失败且 stderr 额外输出 `RECOVERY_EVENT_ID=<event_id>` 时,说明 CLI 已经持久化了失败快照,可进入 recovery 闭环:
|
||||
|
||||
```bash
|
||||
dws recovery plan --event-id <event_id> --format json
|
||||
dws recovery execute --event-id <event_id> --format json
|
||||
dws recovery finalize --event-id <event_id> --outcome recovered|failed|handoff --execution-file execution.json --format json
|
||||
```
|
||||
|
||||
- `plan` / `execute` 也支持 `--last`,但 `--last` 与 `--event-id` 互斥
|
||||
- recovery 文件保存在 `DWS_CONFIG_DIR/recovery/`
|
||||
- CLI 会自动清理 30 天前的 recovery 文件和事件记录
|
||||
- recovery 自己发起的文档检索与只读 probe 不会再创建新的 recovery 事件
|
||||
|
||||
更多闭环要求见 [recovery-guide.md](./recovery-guide.md)。
|
||||
|
||||
|
||||
## 全局标志
|
||||
|
||||
| 标志 | 短名 | 说明 | 默认 |
|
||||
|------|:---:|------|------|
|
||||
| `--format` | `-f` | 输出格式: json / table / raw | json |
|
||||
| `--jq` | | jq 表达式过滤输出 (如: `.items[] \| .name`) | 无 |
|
||||
| `--fields` | | 筛选输出字段 (逗号分隔, 如: name,id,status) | 无 |
|
||||
| `--verbose` | `-v` | 详细日志 | false |
|
||||
| `--debug` | | 调试日志 | false |
|
||||
| `--yes` | `-y` | 跳过确认提示 | false |
|
||||
| `--dry-run` | | 预览操作不执行 | false |
|
||||
| `--timeout` | | HTTP 超时 (秒) | 30 |
|
||||
| `--mock` | | Mock 数据 (开发用) | false |
|
||||
| `--client-id` | | 覆盖 OAuth Client ID | 无 |
|
||||
| `--client-secret` | | 覆盖 OAuth Client Secret | 无 |
|
||||
|
||||
## 输出格式
|
||||
|
||||
### --format json (机器可读, 默认)
|
||||
|
||||
```json
|
||||
{"success": true, "body": {...}}
|
||||
```
|
||||
|
||||
### --format table (人类可读)
|
||||
|
||||
```
|
||||
已创建 AI 表格 "项目管理" (UUID: abc123)
|
||||
|
||||
下一步:
|
||||
dws aitable base get --base-id abc123
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `DWS_CONFIG_DIR` | 覆盖默认配置目录 |
|
||||
| `DWS_SERVERS_URL` | 自定义服务发现端点 |
|
||||
| `DWS_CLIENT_ID` | 覆盖 OAuth Client ID (DingTalk AppKey) |
|
||||
| `DWS_CLIENT_SECRET` | 覆盖 OAuth Client Secret (DingTalk AppSecret) |
|
||||
|
||||
凭证优先级: `--token` > `DWS_CLIENT_ID`/`DWS_CLIENT_SECRET` > OAuth 加密存储 (.data)
|
||||
@@ -0,0 +1,114 @@
|
||||
# 意图路由指南
|
||||
|
||||
当用户请求难以判断归属哪个产品时,参考本指南。
|
||||
|
||||
## 易混淆场景快速对照表
|
||||
|
||||
| 用户说... | 真实意图 | 应该用 | 不要用 | 理由 |
|
||||
|-----------|----------|--------|--------|------|
|
||||
| "搜一下 OAuth2 接入文档" | 搜索开发文档 | `devdoc` | — | 搜索开放平台技术文档,不是钉钉内部内容 |
|
||||
| "帮我建一个项目跟踪表" | 创建数据表格 | `aitable` | `todo` | 涉及结构化数据/行列操作,不是个人待办 |
|
||||
| "帮我记一下明天要做的事" | 创建个人待办 | `todo` | `aitable` | 个人待办提醒,非数据表 |
|
||||
| "帮我建一个明天下午的日程" | 日历日程 | `calendar` | — | 日历日程管理(可含参与者/会议室)|
|
||||
| "帮我看看收到的日报" | 日志收件箱 | `report` | `todo` | 钉钉日志系统(日报/周报),不是待办 |
|
||||
| "帮我创建一个待办提醒" | 个人待办 | `todo` | `report` | 个人任务提醒,不是日志汇报 |
|
||||
| "帮我提交请假审批" | 发起审批 | `oa` | — | 审批流程,不是待办或日志 |
|
||||
| "帮我建一个项目群" | 创建群聊 | `chat group create` | — | 群聊管理,不是日历日程 |
|
||||
| "把张三拉进群" | 添加群成员 | `chat group members add` | — | 先查 userId,再添加 |
|
||||
| "让机器人在群里发个通知" | 机器人群发 | `chat message send-by-bot` | `chat message send-by-webhook` | 企业内部机器人发消息,需 robotCode |
|
||||
| "通过 Webhook 发告警到群里" | Webhook 告警 | `chat message send-by-webhook` | `chat message send-by-bot` | 自定义机器人 Webhook,需 token |
|
||||
| "给张三发一条机器人单聊消息" | 机器人单聊 | `chat message send-by-bot --users` | — | 机器人批量单聊,先查 userId |
|
||||
|
||||
---
|
||||
|
||||
## 典型场景详解
|
||||
|
||||
### 1. aitable vs todo — 表格数据 vs 待办任务
|
||||
|
||||
**用 `aitable` 的场景**:
|
||||
- "创建一个表格记录团队成员信息" — 结构化数据,有行列
|
||||
- "在表格里加一列'状态'字段" — 字段/列操作
|
||||
- "查一下表格里所有优先级为高的记录" — 数据筛选和查询
|
||||
- "用项目管理模板建一个表" — 模板创建
|
||||
- 用户提到"多维表"、"Base"、"数据表"、"记录"
|
||||
|
||||
**用 `todo` 的场景**:
|
||||
- "帮我记一下这周要做的事" — 个人任务管理
|
||||
- "创建一个待办提醒" — 任务提醒
|
||||
|
||||
**判断关键**:有没有行列/字段/记录概念?有→ `aitable`;个人任务清单 → `todo`
|
||||
|
||||
---
|
||||
|
||||
### 2. devdoc — 开发文档搜索
|
||||
|
||||
**用 `devdoc` 的场景**:
|
||||
- "API 调用报错 403 怎么解决" — 开发调试问题
|
||||
- "搜一下 OAuth2 接入文档" — 开放平台技术文档
|
||||
- "CLI 命令出错了怎么办" — CLI 使用错误
|
||||
- 用户提到"开发"、"API"、"调用错误"
|
||||
|
||||
---
|
||||
|
||||
### 3. report vs todo — 日志 vs 待办
|
||||
|
||||
**用 `report` 的场景**:
|
||||
- "帮我看看收到的日报" — 日志收件箱
|
||||
- "帮我写/提交今天的日报(钉钉日志模版)" — 先 `report template list` / `template detail`,再 `report create`
|
||||
- "有什么日志模版" — 查看模版
|
||||
- "看看这个日志的已读统计" — 阅读状态
|
||||
- "我发过的日志有哪些" — 已发送列表 (`report sent`)
|
||||
- 用户提到"日报"、"周报"、"日志"
|
||||
|
||||
**用 `todo` 的场景**:
|
||||
- "记一下这周要做的事" — 个人任务管理
|
||||
|
||||
**判断关键**:钉钉日志系统(日报/周报模版,含按模版创建汇报)→ `report`;任务清单→ `todo`
|
||||
|
||||
---
|
||||
|
||||
### 4. chat 内部 — 两种消息发送方式
|
||||
|
||||
**用 `chat message send-by-bot` 的场景**:
|
||||
- "让机器人在群里发一条通知" — **机器人身份**发群消息
|
||||
- "给张三发一条机器人单聊消息" — 机器人批量单聊
|
||||
|
||||
**用 `chat message send-by-webhook` 的场景**:
|
||||
- "通过 Webhook 发告警到群里" — 自定义机器人 Webhook
|
||||
- 用户有 Webhook Token
|
||||
|
||||
**判断关键**:企业内部机器人→ `send-by-bot`(需 robotCode);有 Webhook Token→ `send-by-webhook`
|
||||
|
||||
---
|
||||
|
||||
## 跨产品工作流路由
|
||||
|
||||
以下场景需要多个产品配合完成,注意上下文传递顺序。
|
||||
|
||||
### 创建日程并邀请同事(contact → calendar)
|
||||
|
||||
用户说"约张三明天下午开会":
|
||||
|
||||
```bash
|
||||
# 1. 搜索同事 userId
|
||||
dws contact user search --query "张三" --format json
|
||||
|
||||
# 2. 创建日程
|
||||
dws calendar event create --title "会议" \
|
||||
--start "2026-03-15T14:00:00+08:00" --end "2026-03-15T15:00:00+08:00" --format json
|
||||
|
||||
# 3. 添加参与者
|
||||
dws calendar participant add --event <EVENT_ID> --users <USER_ID> --format json
|
||||
```
|
||||
|
||||
### 创建待办并指派(contact → todo)
|
||||
|
||||
用户说"给张三建个待办":
|
||||
|
||||
```bash
|
||||
# 1. 搜索同事 userId
|
||||
dws contact user search --query "张三" --format json
|
||||
|
||||
# 2. 创建待办
|
||||
dws todo task create --title "任务内容" --executors <USER_ID> --format json
|
||||
```
|
||||
@@ -0,0 +1,925 @@
|
||||
# AI表格 (aitable) 命令参考
|
||||
|
||||
## 文档地址 (URI)
|
||||
|
||||
所有 AI 表格操作完成后,可通过以下 URI 直接访问对应文档:
|
||||
|
||||
| 资源 | URI 格式 |
|
||||
|------|----------|
|
||||
| Base 文档 | `https://alidocs.dingtalk.com/i/nodes/{baseId}` |
|
||||
| 模板预览 | `https://docs.dingtalk.com/table/template/{templateId}` |
|
||||
|
||||
> 💡 **操作后请返回文档 URI**:每次执行 base list/search/create/get 操作后,从返回数据中提取 `baseId`,拼接为 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户,方便直接点击打开。
|
||||
|
||||
## 命令总览
|
||||
|
||||
### base (Base 管理)
|
||||
|
||||
#### 获取 AI 表格列表
|
||||
```
|
||||
Usage:
|
||||
dws aitable base list [flags]
|
||||
Example:
|
||||
dws aitable base list
|
||||
dws aitable base list --limit 5 --cursor NEXT_CURSOR
|
||||
Flags:
|
||||
--cursor string 分页游标,首次不传
|
||||
--limit int 每页数量,默认 10,最大 10
|
||||
```
|
||||
|
||||
返回 baseId 与 baseName。
|
||||
|
||||
> 📎 **操作后返回文档链接**:遍历返回的每个 base,拼接 `https://alidocs.dingtalk.com/i/nodes/{baseId}` 返回给用户。
|
||||
|
||||
> ⚠️ **重要**:`base list` 仅返回**最近访问过**的 Base,**不是全部 Base**。
|
||||
> 如需查找表格,**请优先使用 `base search`**;`base list` 仅作为浏览最近表格的辅助手段。
|
||||
> 如果刚创建完,直接使用 `create` 返回的 `baseId` 即可。
|
||||
|
||||
#### 搜索 AI 表格
|
||||
```
|
||||
Usage:
|
||||
dws aitable base search [flags]
|
||||
Example:
|
||||
dws aitable base search --query "项目管理"
|
||||
Flags:
|
||||
--cursor string 分页游标,首次不传
|
||||
--query string Base 名称关键词,建议至少 2 个字符 (必填)
|
||||
```
|
||||
|
||||
#### 获取 AI 表格信息
|
||||
```
|
||||
Usage:
|
||||
dws aitable base get [flags]
|
||||
Example:
|
||||
dws aitable base get --base-id <BASE_ID>
|
||||
Flags:
|
||||
--base-id string Base 唯一标识 (必填)
|
||||
```
|
||||
|
||||
> 💡 **用户提供 URL 时**:如果用户给出了链接如 `https://alidocs.dingtalk.com/i/nodes/ABC123`,请提取末尾的 `ABC123` 作为 `--base-id` 传入。详见下方「URL → baseId 提取」章节。
|
||||
|
||||
返回 baseName、tables、dashboards 的 summary 信息(不含字段与记录详情)。
|
||||
后续如需 tableId,优先从这里读取。
|
||||
|
||||
> 📎 **文档地址**:`https://alidocs.dingtalk.com/i/nodes/{baseId}`
|
||||
|
||||
#### 创建 AI 表格
|
||||
```
|
||||
Usage:
|
||||
dws aitable base create [flags]
|
||||
Example:
|
||||
dws aitable base create --name "项目跟踪"
|
||||
Flags:
|
||||
--name string Base 名称,1-50 字符 (必填)
|
||||
--template-id string 模板 ID (可选,可通过 template search 获取)
|
||||
```
|
||||
|
||||
> 💡 **创建后直接使用返回的 `baseId`**,无需再调用 `base list` 或 `base search` 查找。
|
||||
> 后续可直接 `base get --base-id <返回的baseId>` 获取 tableId,或 `table create --base-id <返回的baseId>` 创建数据表。
|
||||
>
|
||||
> 📎 **文档地址**:`https://alidocs.dingtalk.com/i/nodes/{返回的baseId}`
|
||||
|
||||
#### 更新 AI 表格
|
||||
```
|
||||
Usage:
|
||||
dws aitable base update [flags]
|
||||
Example:
|
||||
dws aitable base update --base-id <BASE_ID> --name "新名称"
|
||||
Flags:
|
||||
--base-id string 目标 Base ID (必填)
|
||||
--desc string 备注文本
|
||||
--name string 新名称,1-50 字符 (必填)
|
||||
```
|
||||
|
||||
#### 删除 AI 表格
|
||||
```
|
||||
Usage:
|
||||
dws aitable base delete [flags]
|
||||
Example:
|
||||
dws aitable base delete --base-id <BASE_ID> --yes
|
||||
Flags:
|
||||
--base-id string 待删除 Base ID (必填)
|
||||
--reason string 删除原因
|
||||
```
|
||||
|
||||
高风险操作,不可逆。
|
||||
|
||||
### table (数据表管理)
|
||||
|
||||
#### 获取数据表
|
||||
```
|
||||
Usage:
|
||||
dws aitable table get [flags]
|
||||
Example:
|
||||
dws aitable table get --base-id <BASE_ID>
|
||||
dws aitable table get --base-id <BASE_ID> --table-ids tbl1,tbl2
|
||||
Flags:
|
||||
--base-id string 所属 Base ID (必填)
|
||||
--table-ids string Table ID 列表,逗号分隔,单次最多 10 个
|
||||
```
|
||||
|
||||
返回 tableId、tableName、description、fields 目录、views 目录。不传 table-ids 返回全部表。
|
||||
|
||||
> 📎 **文档地址**:`https://alidocs.dingtalk.com/i/nodes/{baseId}`
|
||||
|
||||
#### 创建数据表
|
||||
```
|
||||
Usage:
|
||||
dws aitable table create [flags]
|
||||
Example:
|
||||
dws aitable table create --base-id <BASE_ID> --name "任务表" \
|
||||
--fields '[{"fieldName":"任务名称","type":"text"},{"fieldName":"优先级","type":"singleSelect","config":{"options":[{"name":"高"},{"name":"中"},{"name":"低"}]}}]'
|
||||
Flags:
|
||||
--base-id string 目标 Base ID (必填)
|
||||
--fields string 初始字段 JSON 数组,至少 1 个,单次最多 15 个 (必填)
|
||||
--name string 表格名称,1-100 字符 (必填)
|
||||
```
|
||||
|
||||
> 💡 **创建后直接使用返回的 `tableId`**,无需再调 `table get` 查找。
|
||||
> 后续可直接 `field create --table-id <返回的tableId>` 补充字段,或 `record create` 写入数据。
|
||||
>
|
||||
> 📎 **文档地址**:`https://alidocs.dingtalk.com/i/nodes/{baseId}`
|
||||
|
||||
#### 更新数据表
|
||||
```
|
||||
Usage:
|
||||
dws aitable table update [flags]
|
||||
Example:
|
||||
dws aitable table update --base-id <BASE_ID> --table-id <TABLE_ID> --name "新表名"
|
||||
Flags:
|
||||
--base-id string 所属 Base ID (必填)
|
||||
--name string 新表名 (必填)
|
||||
--table-id string 目标 Table ID (必填)
|
||||
```
|
||||
|
||||
#### 删除数据表
|
||||
```
|
||||
Usage:
|
||||
dws aitable table delete [flags]
|
||||
Example:
|
||||
dws aitable table delete --base-id <BASE_ID> --table-id <TABLE_ID> --yes
|
||||
Flags:
|
||||
--base-id string 目标 Base ID (必填)
|
||||
--reason string 删除原因
|
||||
--table-id string 将被删除的 Table ID (必填)
|
||||
```
|
||||
|
||||
不可逆。若为 Base 中最后一张表,删除会失败。
|
||||
|
||||
### field (字段管理)
|
||||
|
||||
#### 获取字段详情
|
||||
```
|
||||
Usage:
|
||||
dws aitable field get [flags]
|
||||
Example:
|
||||
dws aitable field get --base-id <BASE_ID> --table-id <TABLE_ID>
|
||||
dws aitable field get --base-id <BASE_ID> --table-id <TABLE_ID> --field-ids fld1,fld2
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--field-ids string 字段 ID 列表,逗号分隔,单次最多 10 个
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
返回字段的完整配置(含 options 等)。在 table get 拿到字段目录后,按需展开少量字段的完整配置。
|
||||
|
||||
#### 创建字段
|
||||
```
|
||||
Usage:
|
||||
dws aitable field create [flags]
|
||||
Example:
|
||||
# 单字段模式
|
||||
dws aitable field create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--name "状态" --type "singleSelect" --config '{"options":[{"name":"待办"},{"name":"进行中"},{"name":"已完成"}]}'
|
||||
|
||||
# 批量模式
|
||||
dws aitable field create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--fields '[{"fieldName":"状态","type":"singleSelect","config":{"options":[{"name":"待办"}]}}]'
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--name string 单字段名称(与 --type 配合使用,替代 --fields)
|
||||
--type string 单字段类型(参考 table create 字段类型)
|
||||
--config string 单字段配置 JSON(可选,如 options)
|
||||
--fields string 批量新增字段 JSON 数组,单次最多 15 个(与 --name/--type 二选一)
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
允许部分成功,返回结果逐项标明成功/失败状态。
|
||||
`--name/--type/--config` 为单字段模式;`--fields` 为批量模式;两种模式二选一。
|
||||
|
||||
#### 更新字段
|
||||
```
|
||||
Usage:
|
||||
dws aitable field update [flags]
|
||||
Example:
|
||||
dws aitable field update --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID> --name "新字段名"
|
||||
dws aitable field update --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID> --config '{"options":[{"name":"A"},{"name":"B"}]}'
|
||||
Flags:
|
||||
--ai-config string AI 字段配置 JSON(AI 字段类型专用,可选)
|
||||
--base-id string Base ID (必填)
|
||||
--config string 字段配置 JSON (不修改时省略)
|
||||
--field-id string Field ID (必填)
|
||||
--name string 新字段名称 (不修改时省略)
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
不可变更字段类型。更新 singleSelect/multipleSelect 的 options 时需传入完整列表,已有选项应回传原 id。
|
||||
|
||||
#### 删除字段
|
||||
```
|
||||
Usage:
|
||||
dws aitable field delete [flags]
|
||||
Example:
|
||||
dws aitable field delete --base-id <BASE_ID> --table-id <TABLE_ID> --field-id <FIELD_ID> --yes
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--field-id string 待删除字段 ID (必填)
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
不可逆。禁止删除主字段和最后一个字段。
|
||||
|
||||
### record (记录管理)
|
||||
|
||||
#### 查询记录
|
||||
```
|
||||
Usage:
|
||||
dws aitable record query [flags]
|
||||
Example:
|
||||
dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID>
|
||||
dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --record-ids rec1,rec2
|
||||
dws aitable record query --base-id <BASE_ID> --table-id <TABLE_ID> --query "关键词" --limit 50
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--cursor string 分页游标,首次不传
|
||||
--field-ids string 返回字段 ID 列表,逗号分隔,单次最多 100 个
|
||||
--filters string 结构化过滤条件 JSON
|
||||
--query string 全文关键词搜索
|
||||
--limit int 单次最大记录数,默认 100,最大 100
|
||||
--record-ids string 指定记录 ID 列表,逗号分隔,单次最多 100 个
|
||||
--sort string 排序条件 JSON 数组
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
两种模式: 按 ID 取(传 record-ids,忽略 filters/sort)或条件查(filters+sort+cursor 分页)。
|
||||
|
||||
> ⚠️ **排序参数规范(关键)**:`--sort` 需要传 JSON 数组,排序方向字段必须是 `direction`(`asc` 或 `desc`),**不要使用 `order`**。
|
||||
>
|
||||
> 正确示例:`--sort '[{"fieldId":"wm8ns9bw2vmucb45xj3ix","direction":"desc"}]'`
|
||||
|
||||
filters 结构:`{"operator":"and|or","operands":[{"operator":"<op>","operands":["<fieldId>","<value>"]}]}`
|
||||
|
||||
> 💡 **singleSelect/multipleSelect 过滤**:filters 中可传 option id 或 option name,但建议优先用 **option id**(通过 `field get` 获取),更可靠。
|
||||
> 写入时(record create/update)可直接传 option name。
|
||||
|
||||
> 💡 **减少响应体积**:字段较多时,用 `--field-ids` 仅返回需要的字段,可显著减少返回数据量。
|
||||
|
||||
#### 新增记录
|
||||
```
|
||||
Usage:
|
||||
dws aitable record create [flags]
|
||||
Example:
|
||||
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"cells":{"fldTextId":"文本内容","fldNumId":123}}]'
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--records string 记录列表 JSON 数组,单次最多 100 条 (必填)
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
> ⚠️ **常见错误(严格避免)**:
|
||||
> - **参数名是 `--records`**,不是 `--data`
|
||||
> - **cells 的 key 必须是 fieldId**(如 `fldXXX`),**不是字段名称**(如 `"课程名称"`)
|
||||
> - 必须先 `table get` 获取 fieldId,再写入记录
|
||||
|
||||
```bash
|
||||
# 正确流程:先获取 fieldId
|
||||
dws aitable table get --base-id <BASE_ID> --table-id <TABLE_ID> --format json
|
||||
# 从返回中提取 fieldId(如 fldABC123)
|
||||
|
||||
# 再用 fieldId 写入记录
|
||||
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"cells":{"fldABC123":"Python入门"}}]' --format json
|
||||
```
|
||||
|
||||
#### 更新记录
|
||||
```
|
||||
Usage:
|
||||
dws aitable record update [flags]
|
||||
Example:
|
||||
dws aitable record update --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"recordId":"recXXX","cells":{"fldStatusId":"已完成"}}]'
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--records string 待更新记录 JSON 数组,单次最多 100 条 (必填)
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
只需传入需修改的字段,未传入的保持原值。每条记录必须含 recordId 和 cells。
|
||||
|
||||
#### 删除记录
|
||||
```
|
||||
Usage:
|
||||
dws aitable record delete [flags]
|
||||
Example:
|
||||
dws aitable record delete --base-id <BASE_ID> --table-id <TABLE_ID> --record-ids rec1,rec2 --yes
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--record-ids string 待删除记录 ID 列表,逗号分隔,最多 100 条 (必填)
|
||||
--table-id string Table ID (必填)
|
||||
```
|
||||
|
||||
不可逆。调用前建议先 record query 确认目标记录。
|
||||
|
||||
### attachment (附件管理)
|
||||
|
||||
> 🛑 **STOP — 不要使用钉盘 (drive) 上传!** 钉盘 fileId 无法写入 attachment 字段。必须使用以下流程。
|
||||
|
||||
#### 准备附件上传
|
||||
```
|
||||
Usage:
|
||||
dws aitable attachment upload [flags]
|
||||
Example:
|
||||
dws aitable attachment upload --base-id <BASE_ID> --file-name report.xlsx --size 204800
|
||||
dws aitable attachment upload --base-id <BASE_ID> --file-name photo.png --size 1024 --mime-type image/png
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--file-name string 文件名,必须含扩展名 (必填)
|
||||
--size int 文件大小(字节),>0 (必填)
|
||||
--mime-type string MIME type(不传时根据扩展名推断)
|
||||
```
|
||||
|
||||
#### 附件上传完整流程(推荐:使用脚本,2 步完成)
|
||||
|
||||
```bash
|
||||
# 步骤 1: 使用脚本一键上传(内部自动完成 prepare + PUT)
|
||||
python3 scripts/upload_attachment.py <BASE_ID> /path/to/report.pdf
|
||||
# 输出: { "fileToken": "ft_xxx", "fileName": "report.pdf", "size": 204800 }
|
||||
|
||||
# 步骤 2: 在 record create/update 中使用 fileToken 写入
|
||||
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]' --format json
|
||||
```
|
||||
|
||||
> ⚠️ `uploadUrl` 有时效性(`expiresAt`),脚本会自动在获取后立即上传。
|
||||
|
||||
### view (视图管理)
|
||||
|
||||
视图是同一张数据表的筛选/排序/分组/可见字段组合的备用呈现。record query 可以通过视图收敛查询范围。
|
||||
|
||||
#### 获取视图
|
||||
```
|
||||
Usage:
|
||||
dws aitable view get [flags]
|
||||
Example:
|
||||
dws aitable view get --base-id <BASE_ID> --table-id <TABLE_ID>
|
||||
dws aitable view get --base-id <BASE_ID> --table-id <TABLE_ID> --view-ids viw1,viw2
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--table-id string Table ID (必填)
|
||||
--view-ids string View ID 列表,逗号分隔,单次最多 10 个
|
||||
```
|
||||
|
||||
返回视图配置(过滤、排序、可见字段、分组)。不传 view-ids 返回该表全部视图。
|
||||
|
||||
#### 创建视图
|
||||
```
|
||||
Usage:
|
||||
dws aitable view create [flags]
|
||||
Example:
|
||||
dws aitable view create --base-id <BASE_ID> --table-id <TABLE_ID> --name "进行中看板" --view-type kanban
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--config string 视图配置 JSON:过滤/排序/分组/可见字段(可选,创建后可再 update)
|
||||
--name string 视图名称 (必填)
|
||||
--table-id string Table ID (必填)
|
||||
--view-type string 视图类型:grid/gallery/kanban/gantt/calendar/form 等 (必填)
|
||||
```
|
||||
|
||||
> ⚠️ 视图类型参数是 `--view-type`,不是 `--type`。
|
||||
|
||||
#### 更新视图
|
||||
```
|
||||
Usage:
|
||||
dws aitable view update [flags]
|
||||
Example:
|
||||
dws aitable view update --base-id <BASE_ID> --table-id <TABLE_ID> --view-id <VIEW_ID> --name "新视图名"
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--config string 视图配置 JSON:过滤/排序/分组/可见字段
|
||||
--name string 新视图名
|
||||
--table-id string Table ID (必填)
|
||||
--view-id string 目标 View ID (必填)
|
||||
```
|
||||
|
||||
调整过滤、排序、分组、可见字段时使用。不重建视图即可替换配置。
|
||||
|
||||
#### 删除视图
|
||||
```
|
||||
Usage:
|
||||
dws aitable view delete [flags]
|
||||
Example:
|
||||
dws aitable view delete --base-id <BASE_ID> --table-id <TABLE_ID> --view-id <VIEW_ID> --yes
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--table-id string Table ID (必填)
|
||||
--view-id string 待删除 View ID (必填)
|
||||
```
|
||||
|
||||
不可逆。若是主视图或最后一个视图,删除会失败。
|
||||
|
||||
### import (数据导入)
|
||||
|
||||
把外部 Excel/CSV 批量写入某张数据表,按两步走:先 upload 拿 importId,再 data 触发导入。
|
||||
|
||||
#### 准备导入上传
|
||||
```
|
||||
Usage:
|
||||
dws aitable import upload [flags]
|
||||
Example:
|
||||
dws aitable import upload --base-id <BASE_ID> --file-name sales.xlsx --file-size 204800
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--file-name string 文件名,必须含扩展名 (必填)
|
||||
--file-size int 文件大小(字节),>0 (必填)
|
||||
```
|
||||
|
||||
> ⚠️ 参数是 `--file-size`,不是 `--size`。返回 uploadUrl(PUT 上传)与 importId。
|
||||
|
||||
#### 触发导入
|
||||
```
|
||||
Usage:
|
||||
dws aitable import data [flags]
|
||||
Example:
|
||||
dws aitable import data --import-id <IMPORT_ID> --timeout 60
|
||||
Flags:
|
||||
--import-id string import upload 返回的 importId (必填)
|
||||
--timeout int 等待导入完成的超时秒数,默认 30
|
||||
```
|
||||
|
||||
把上一步上传到暂存区的文件正式导入为记录;超时未完成时返回处理状态,按 importId 再次查询即可。
|
||||
|
||||
### dashboard (仪表盘)
|
||||
|
||||
仪表盘 = 多个图表的布局容器;图表绑定数据表/视图。创建仪表盘之前通常先查 `dashboard config-example`,拿到 JSON 骨架,再把图表塞进 `--config` 的布局里。
|
||||
|
||||
#### 仪表盘配置示例
|
||||
```
|
||||
Usage:
|
||||
dws aitable dashboard config-example [flags]
|
||||
Example:
|
||||
dws aitable dashboard config-example --format json
|
||||
```
|
||||
|
||||
返回仪表盘 `--config` 的 JSON 示例,供 create/update 复制裁剪。
|
||||
|
||||
#### 获取仪表盘
|
||||
```
|
||||
Usage:
|
||||
dws aitable dashboard get [flags]
|
||||
Example:
|
||||
dws aitable dashboard get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID>
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
```
|
||||
|
||||
返回布局和 `charts[].chartId`,可直接喂给 `chart get`。
|
||||
|
||||
#### 创建仪表盘
|
||||
```
|
||||
Usage:
|
||||
dws aitable dashboard create [flags]
|
||||
Example:
|
||||
dws aitable dashboard create --base-id <BASE_ID> --config '<JSON>'
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--config string 仪表盘配置 JSON(名称、布局、图表列表都在里面) (必填)
|
||||
```
|
||||
|
||||
> ⚠️ 没有独立的 `--name`;名称和布局一起放在 `--config` JSON 中。
|
||||
|
||||
#### 更新仪表盘
|
||||
```
|
||||
Usage:
|
||||
dws aitable dashboard update [flags]
|
||||
Example:
|
||||
dws aitable dashboard update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --config '<JSON>'
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--config string 更新后的仪表盘配置 JSON (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
```
|
||||
|
||||
调整布局、增删图表一律改 `--config`。
|
||||
|
||||
#### 删除仪表盘
|
||||
```
|
||||
Usage:
|
||||
dws aitable dashboard delete [flags]
|
||||
Example:
|
||||
dws aitable dashboard delete --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --yes
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
--reason string 删除原因
|
||||
```
|
||||
|
||||
不可逆。
|
||||
|
||||
#### 查看仪表盘分享状态
|
||||
```
|
||||
Usage:
|
||||
dws aitable dashboard share get [flags]
|
||||
Example:
|
||||
dws aitable dashboard share get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID>
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
```
|
||||
|
||||
> ⚠️ 可能返回 404(资源不存在或未开通外链),按可重试错误处理,不要误判为参数拼错。
|
||||
|
||||
#### 更新仪表盘分享
|
||||
```
|
||||
Usage:
|
||||
dws aitable dashboard share update [flags]
|
||||
Example:
|
||||
dws aitable dashboard share update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --enabled true
|
||||
Flags:
|
||||
--allow-back-to-doc 是否允许从分享页返回原文档(可选)
|
||||
--base-id string Base ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
--enabled 是否开启外链分享 (必填)
|
||||
--share-type string 分享类型(权限/可见范围等,可选)
|
||||
```
|
||||
|
||||
开启后返回 shareUrl;关闭后原链接失效。
|
||||
|
||||
### chart (图表)
|
||||
|
||||
图表挂在某个仪表盘下,绑定数据表(可进一步绑定视图)。新建前通常先查 `chart widgets-example`。
|
||||
|
||||
#### 图表组件示例
|
||||
```
|
||||
Usage:
|
||||
dws aitable chart widgets-example [flags]
|
||||
Example:
|
||||
dws aitable chart widgets-example --format json
|
||||
```
|
||||
|
||||
返回图表 widget 的 JSON 示例,供 `chart create/update --config` 参考。
|
||||
|
||||
#### 获取图表
|
||||
```
|
||||
Usage:
|
||||
dws aitable chart get [flags]
|
||||
Example:
|
||||
dws aitable chart get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID>
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--chart-id string Chart ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
```
|
||||
|
||||
#### 创建图表
|
||||
```
|
||||
Usage:
|
||||
dws aitable chart create [flags]
|
||||
Example:
|
||||
dws aitable chart create --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --config '<JSON>'
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--config string 图表配置 JSON:数据源、维度、度量、样式 (必填)
|
||||
--dashboard-id string 挂载到的 Dashboard ID (必填)
|
||||
--layout string 图表布局 JSON(位置、尺寸,可选)
|
||||
```
|
||||
|
||||
> ⚠️ 名称和类型都在 `--config` JSON 里,没有独立 `--name` / `--type`。
|
||||
|
||||
#### 更新图表
|
||||
```
|
||||
Usage:
|
||||
dws aitable chart update [flags]
|
||||
Example:
|
||||
dws aitable chart update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --config '<JSON>'
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--chart-id string Chart ID (必填)
|
||||
--config string 图表配置 JSON (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
--layout string 图表布局 JSON(位置、尺寸,可选)
|
||||
```
|
||||
|
||||
#### 删除图表
|
||||
```
|
||||
Usage:
|
||||
dws aitable chart delete [flags]
|
||||
Example:
|
||||
dws aitable chart delete --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --yes
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--chart-id string 待删除 Chart ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
--reason string 删除原因
|
||||
```
|
||||
|
||||
不可逆。
|
||||
|
||||
#### 查看图表分享状态
|
||||
```
|
||||
Usage:
|
||||
dws aitable chart share get [flags]
|
||||
Example:
|
||||
dws aitable chart share get --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID>
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--chart-id string Chart ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
```
|
||||
|
||||
返回 `enabled` 与 `shareUrl`,用来判断是否已经对外分享。
|
||||
|
||||
#### 更新图表分享
|
||||
```
|
||||
Usage:
|
||||
dws aitable chart share update [flags]
|
||||
Example:
|
||||
dws aitable chart share update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --enabled true
|
||||
Flags:
|
||||
--allow-back-to-doc 是否允许从分享页返回原文档(可选)
|
||||
--base-id string Base ID (必填)
|
||||
--chart-id string Chart ID (必填)
|
||||
--dashboard-id string Dashboard ID (必填)
|
||||
--enabled 是否开启外链分享 (必填)
|
||||
--share-type string 分享类型(权限/可见范围等,可选)
|
||||
```
|
||||
|
||||
### export (数据导出)
|
||||
|
||||
把数据表/视图/整张 Base 导出为 Excel/CSV,下发下载链接;常见是异步任务,首次调用可能只返回 `taskId`,需要按 `taskId` 继续轮询直到拿到 `downloadUrl`。
|
||||
|
||||
#### 导出数据
|
||||
```
|
||||
Usage:
|
||||
dws aitable export data [flags]
|
||||
Example:
|
||||
# 第一步:创建任务(按 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
|
||||
Flags:
|
||||
--base-id string Base ID (必填)
|
||||
--scope string 导出范围:all / table / view
|
||||
--table-id string Table ID(scope=table/view 时必填)
|
||||
--view-id string View ID(scope=view 时必填)
|
||||
--format string 导出格式:excel / csv,默认 excel
|
||||
--task-id string 轮询已有任务的 taskId
|
||||
--timeout-ms int 单次调用服务端等待时间(毫秒)
|
||||
```
|
||||
|
||||
参数约束:
|
||||
|
||||
- `scope=all`:只需 `--base-id`
|
||||
- `scope=table`:必须同时传 `--table-id`
|
||||
- `scope=view`:必须同时传 `--table-id + --view-id`
|
||||
|
||||
### template (模板搜索)
|
||||
|
||||
#### 搜索模板
|
||||
```
|
||||
Usage:
|
||||
dws aitable template search [flags]
|
||||
Example:
|
||||
dws aitable template search --query "项目管理"
|
||||
Flags:
|
||||
--cursor string 分页游标,首次不传
|
||||
--limit int 每页返回数量,默认 10,最大 30
|
||||
--query string 模板名称关键词 (必填)
|
||||
```
|
||||
|
||||
返回 templateId 可用于 `base create --template-id`。
|
||||
|
||||
> 📎 **模板预览地址**:`https://docs.dingtalk.com/table/template/{templateId}`
|
||||
|
||||
## 复杂操作
|
||||
|
||||
### 仪表盘 / 图表(建议顺序)
|
||||
|
||||
```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
|
||||
|
||||
# 3) 写入/更新都用 --config JSON(名称、布局、图表类型都在里面)
|
||||
dws aitable dashboard create --base-id <BASE_ID> --config '<JSON>' --format json
|
||||
dws aitable chart update --base-id <BASE_ID> --dashboard-id <DASHBOARD_ID> --chart-id <CHART_ID> --config '<JSON>' --format json
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- `dashboard get` 返回的 `charts[].chartId` 可直接给 `chart get` 使用。
|
||||
- `dashboard share get` 可能返回 `404`(资源不存在或未开通),需按可重试错误处理,不要误判为参数拼错。
|
||||
- `chart share get` 可正常返回 `enabled/shareUrl`,用于分享状态判断。
|
||||
- dashboard/chart 的 create/update 只有 `--config`,没有独立的 `--name` / `--type`,名称与布局都在 JSON 里。
|
||||
|
||||
### 导入外部数据(两步走)
|
||||
|
||||
```bash
|
||||
# 1) 先上传文件拿 importId
|
||||
dws aitable import upload --base-id <BASE_ID> --file-name sales.xlsx --file-size 204800 --format json
|
||||
# 把本地文件 PUT 到返回的 uploadUrl(过期前必须上传完成)
|
||||
|
||||
# 2) 触发导入
|
||||
dws aitable import data --import-id <IMPORT_ID> --timeout 60 --format json
|
||||
```
|
||||
|
||||
要点:`--file-size` 单位字节,必须与实际文件一致;`--timeout` 单位秒,不是毫秒。
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"表格/多维表/AI表格":
|
||||
- 查看/查找/列表 → `base search`(优先)或 `base list`(仅浏览最近访问)
|
||||
- 搜索 → `base search`
|
||||
- 详情 → `base get`
|
||||
- 创建 → `base create`
|
||||
- 修改 → `base update`
|
||||
- 删除 → `base delete` [危险]
|
||||
|
||||
用户说"数据表/子表/table":
|
||||
- 查看 → `table get`
|
||||
- 创建 → `table create`
|
||||
- 重命名 → `table update`
|
||||
- 删除 → `table delete` [危险]
|
||||
|
||||
用户说"字段/列/column":
|
||||
- 查看 → `field get`
|
||||
- 添加 → `field create`
|
||||
- 修改 → `field update`
|
||||
- 删除 → `field delete` [危险]
|
||||
|
||||
用户说"记录/行/数据/row":
|
||||
- 查看/搜索 → `record query`(先 `table get` 获取 fieldId)
|
||||
- 添加/写入 → `record create`(先 `table get` 必须!)
|
||||
- 修改/更新 → `record update`(需 recordId,先 `record query`)
|
||||
- 删除 → `record delete` [危险](需 recordId)
|
||||
|
||||
用户说"视图/筛选视图/看板/画廊":
|
||||
- 查看 → `view get`
|
||||
- 新建 → `view create`(`--view-type` 指定视图类型)
|
||||
- 修改(过滤/排序/分组/可见字段) → `view update`
|
||||
- 删除 → `view delete` [危险]
|
||||
|
||||
用户说"导入/上传 Excel/CSV":
|
||||
- 外部文件导入 → `import upload` → PUT 文件 → `import data`(两步)
|
||||
|
||||
用户说"导出/下载表格数据":
|
||||
- → `export data`(按 scope=all/table/view 传参,异步轮询)
|
||||
|
||||
用户说"仪表盘/dashboard":
|
||||
- 查看布局 → `dashboard get`
|
||||
- 查看模板 → `dashboard config-example`
|
||||
- 新建/修改布局 → `dashboard create` / `dashboard update`(配置都在 `--config` JSON)
|
||||
- 删除 → `dashboard delete` [危险]
|
||||
- 对外分享 → `dashboard share get` / `dashboard share update`
|
||||
|
||||
用户说"图表/chart":
|
||||
- 查看 → `chart get`(需要 dashboardId)
|
||||
- 查看组件模板 → `chart widgets-example`
|
||||
- 新建/修改 → `chart create` / `chart update`(配置都在 `--config` JSON)
|
||||
- 删除 → `chart delete` [危险]
|
||||
- 对外分享 → `chart share get` / `chart share update`
|
||||
|
||||
用户说"附件/文件字段" → `attachment upload`(不要用钉盘 drive)
|
||||
|
||||
用户说"模板" → `template search`
|
||||
|
||||
关键区分: base=表格文件, table=数据表, field=列, record=行, view=视图, dashboard=仪表盘, chart=图表
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```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`、`dashboards[].dashboardId` | --table-id / --dashboard-id |
|
||||
| `table get` | `fields[].fieldId`、`views[].viewId` | record 操作的 cells key;field get/update/delete;view 操作 |
|
||||
| `view get` | `viewId` | record query 按视图收敛;view update/delete |
|
||||
| `record query` | `recordId` | record update/delete |
|
||||
| `dashboard get` | `charts[].chartId` | chart get/update/delete/share |
|
||||
| `import upload` | `importId`、`uploadUrl` | PUT 文件后调 import data |
|
||||
| `export data` (首次) | `taskId` | 下一轮 export data 轮询 |
|
||||
| `attachment upload` | `fileToken` | record create/update 写入 attachment 字段 |
|
||||
| `template search` | `templateId` | base create --template-id,拼接模板预览 URI |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 所有操作使用 ID(baseId/tableId/fieldId/recordId/viewId/dashboardId/chartId),不使用名称
|
||||
- records 的 cells key 是 fieldId,不是字段名称
|
||||
- dashboard/chart 的 create/update 只有 `--config`(JSON 内含名称/布局/类型),没有独立 `--name`
|
||||
- view 创建的类型参数是 `--view-type`,不是 `--type`
|
||||
- import upload 的文件大小参数是 `--file-size`,不是 `--size`
|
||||
|
||||
## `--filters` 筛选语法排错与使用规范(极易出错)
|
||||
|
||||
调用 `record query` 时,如果条件筛选**完全失效(查询返回了所有记录)**,通常是因为 `--filters` JSON 语法错误,API 默默丢弃了不合规的 filter。
|
||||
|
||||
**强制规则:**
|
||||
1. **根节点必须是逻辑操作符**:`"operator"` 必须是 `"and"` 或 `"or"`,不能是 `"eq"` 等比较操作符。
|
||||
2. 比较操作必须放在根节点的 `"operands"` 数组内的对象中。
|
||||
3. `singleSelect` 和 `multipleSelect` 字段,推荐使用 **选项的 exact String 名称 (name)** 作为比较值,而不是 ID。
|
||||
4. **内层比较操作符语义**:支持 `eq`(等于)、`not_eq`(不等于)、`contain`(包含/模糊搜索)、`not_contain`(不包含)、`gt/gte`(大于/大于等于)、`lt/lte`(小于/小于等于)、`is_empty/is_not_empty`(为空/不为空,对应 operands 内只传单个 fieldId)。
|
||||
|
||||
**精简防呆模板与 4 种衍生情况**
|
||||
```json
|
||||
{
|
||||
"operator": "and", // 情况 4 (OR 查询): 这里改为 "or"
|
||||
"operands": [
|
||||
{
|
||||
"operator": "eq", // 情况 3 (文本包含): 这里改为 "contain"
|
||||
"operands": ["fld_state", "进行中"] // 情况 1 (基础等于)
|
||||
}
|
||||
// 情况 2 (多条件 AND): 在此行增加类似 {"operator":"eq","operands":["fld_priority","高"]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**错误示例 1:缺失根节点 and/or**(API 将忽略该 filter,返回全表)
|
||||
```json
|
||||
{"operator":"eq","operands":["fldXXX","本科"]}
|
||||
```
|
||||
|
||||
**错误示例 2:传入选项 ID 而非名称**(可能导致匹配不到 0 记录)
|
||||
```json
|
||||
{"operator":"and","operands":[{"operator":"eq","operands":["fldXXX","CXzrOHK9JI"]}]}
|
||||
```
|
||||
|
||||
## URL → baseId 提取
|
||||
|
||||
用户经常通过钉钉链接指定表格,链接格式为:
|
||||
|
||||
```
|
||||
https://alidocs.dingtalk.com/i/nodes/{baseId}
|
||||
https://alidocs.dingtalk.com/i/nodes/{baseId}?xxx=yyy
|
||||
```
|
||||
|
||||
**处理规则**(必须严格遵守):
|
||||
1. 当用户提供了包含 `alidocs.dingtalk.com/i/nodes/` 的 URL 时,提取 `/nodes/` 后的路径段作为 `baseId`
|
||||
2. 去掉尾部的查询参数(`?` 及其后内容)和尾部斜杠
|
||||
3. 将提取得到的 ID 传入 `--base-id` 参数
|
||||
|
||||
**示例**:
|
||||
```
|
||||
用户输入:帮我查看 https://alidocs.dingtalk.com/i/nodes/ABC123XYZ 这个表格
|
||||
→ 提取 baseId = ABC123XYZ
|
||||
→ 执行: dws aitable base get --base-id ABC123XYZ --format json
|
||||
```
|
||||
|
||||
> 💡 **注意**:URL 中的 nodeId 在 AI 表格场景下等同于 baseId,可以直接作为 `--base-id` 使用。
|
||||
|
||||
### cells 写入/读取格式速查
|
||||
|
||||
| 字段类型 | 写入格式 | 读取返回格式 |
|
||||
|---------|---------|------------|
|
||||
| text | `"字符串"` | `"字符串"` |
|
||||
| number | `123` | `"123"` |
|
||||
| singleSelect | `"选项名"` 或 `{"id":"xxx"}` | `{"id":"xxx","name":"选项名"}` |
|
||||
| multipleSelect | `["选项A","选项B"]` | `[{"id":"x","name":"选项A"},...]` |
|
||||
| date | `"2026-03-13"` 或时间戳 | ISO 日期字符串 |
|
||||
| checkbox | `true`/`false` | `true`/`false` |
|
||||
| user | `[{"userId":"xxx"}]` | `[{"corpId":"xxx","userId":"xxx"}]` |
|
||||
| attachment | `[{"fileToken":"ft_xxx"}]` ⚠️需先走 attachment upload 3步流程 | `[{"url":"...","filename":"...","size":N}]` |
|
||||
| url | `{"text":"显示文本","link":"https://..."}` | 同写入 |
|
||||
| richText | `{"markdown":"**加粗**"}` | `{"markdown":"..."}` |
|
||||
| group | `[{"cid":"xxx"}]` (注意: key 是 cid,不是 openConversationId) | 同写入 |
|
||||
|
||||
- 详见 [field-rules.md](../field-rules.md) 和 [error-codes.md](../error-codes.md)
|
||||
|
||||
## 相关产品
|
||||
|
||||
- [doc](./doc.md) — 富文本文档编辑,不是结构化数据表格
|
||||
@@ -0,0 +1,97 @@
|
||||
# 考勤 (attendance) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 查询个人考勤详情
|
||||
```
|
||||
Usage:
|
||||
dws attendance record get [flags]
|
||||
Example:
|
||||
dws attendance record get --user <USER_ID> --date 2026-03-08
|
||||
Flags:
|
||||
--date string 查询日期, 格式 YYYY-MM-DD (必填)
|
||||
--user string 钉钉用户 ID (必填)
|
||||
```
|
||||
|
||||
### 批量查询员工班次信息
|
||||
```
|
||||
Usage:
|
||||
dws attendance shift list [flags]
|
||||
Example:
|
||||
dws attendance shift list --users userId1,userId2 --start 2026-03-03 --end 2026-03-07
|
||||
Flags:
|
||||
--end string 结束日期, 格式 YYYY-MM-DD (必填)
|
||||
--start string 开始日期, 格式 YYYY-MM-DD (必填)
|
||||
--users string 用户 ID 列表, 逗号分隔, 最多 50 个 (必填)
|
||||
```
|
||||
|
||||
返回每条记录含:用户 ID、工作日期、打卡类型(OnDuty/OffDuty)、计划打卡时间、是否休息日。间隔不超过 7 天,最多 50 人。
|
||||
|
||||
### 查询某个人的考勤统计摘要
|
||||
```
|
||||
Usage:
|
||||
dws attendance summary [flags]
|
||||
Example:
|
||||
dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" --stats-type month
|
||||
dws attendance summary --user USER_ID --date "2026-03-12 15:00:00" --stats-type week
|
||||
Flags:
|
||||
--date string 工作日期, 格式 yyyy-MM-dd HH:mm:ss (必填)
|
||||
--stats-type string 统计类型:week(周统计)或 month(月统计)(必填,钉钉服务端业务层强制要求;CLI 层会直接拒绝缺失/非法值)
|
||||
--user string 钉钉用户 ID (必填)
|
||||
```
|
||||
|
||||
> ⚠️ **重要**:`--stats-type` 在钉钉 schema 中标记为 `required: []`(看似可选),但服务端业务层**强制要求**,不传服务端会回 `C0002 / 统计类型错误`。CLI 已在客户端层做了 fail-fast:缺失或非 `week`/`month` 的取值会直接被 CLI 拒绝,不会发出请求。
|
||||
|
||||
### 查询考勤组与考勤规则
|
||||
```
|
||||
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 (必填)
|
||||
```
|
||||
|
||||
查询考勤组/考勤规则。例如:我属于哪个考勤组、打卡范围是什么、弹性工时怎么算。
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"打卡记录/出勤/考勤" → `record get`
|
||||
用户说"排班/班次/当班" → `shift list`
|
||||
用户说"考勤汇总/统计" → `summary`
|
||||
用户说"考勤组/考勤规则/打卡规则" → `rules`
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 查看某人考勤
|
||||
dws attendance record get --user <USER_ID> --date 2026-03-08 --format json
|
||||
|
||||
# 批量查排班
|
||||
dws attendance shift list --users userId1,userId2 \
|
||||
--start 2026-03-03 --end 2026-03-07 --format json
|
||||
|
||||
# 查看考勤统计摘要
|
||||
dws attendance summary --user <USER_ID> --date "2026-03-12 15:00:00" --format json
|
||||
|
||||
# 查看考勤组和规则
|
||||
dws attendance rules --date 2026-03-14 --format json
|
||||
```
|
||||
## 上下文传递表
|
||||
| 操作 | 提取 | 用于 |
|
||||
|------|------|------|
|
||||
| `contact user get-self` | `userId` | record get 的 --user, shift list 的 --users, summary 的 --user |
|
||||
## 注意事项
|
||||
- `record get` 的 `--date` 格式: YYYY-MM-DD(如 `2026-03-08`),CLI 自动转换为毫秒时间戳
|
||||
- `shift list` 的 `--start/--end` 同样使用 YYYY-MM-DD 格式,间隔不超过 7 天
|
||||
- `summary` 的 `--date` 格式: yyyy-MM-dd HH:mm:ss(如 `2026-03-12 15:00:00`)
|
||||
- `rules` 的 `--date` 支持 YYYY-MM-DD 或 yyyy-MM-dd HH:mm:ss 两种格式
|
||||
- 用户 ID 需从 `contact user get-self` 或 `contact user search` 获取
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [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` |
|
||||
@@ -0,0 +1,245 @@
|
||||
# 日历 (calendar) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 查询日程列表
|
||||
```
|
||||
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"
|
||||
Flags:
|
||||
--end string 结束时间 ISO-8601 (例如 2026-03-10T18:00:00+08:00)
|
||||
--start string 开始时间 ISO-8601 (例如 2026-03-10T14:00:00+08:00)
|
||||
```
|
||||
|
||||
### 获取日程详情
|
||||
```
|
||||
Usage:
|
||||
dws calendar event get [flags]
|
||||
Example:
|
||||
dws calendar event get --id <EVENT_ID>
|
||||
Flags:
|
||||
--id string 日程 ID (必填)
|
||||
```
|
||||
|
||||
### 创建日程
|
||||
```
|
||||
Usage:
|
||||
dws calendar event create [flags]
|
||||
Example:
|
||||
dws calendar event create --title "Q1 复盘会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00"
|
||||
dws calendar event create --title "Q1 复盘会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" \
|
||||
--attendees userId1,userId2 --timezone "Asia/Shanghai" --desc "Q1 复盘"
|
||||
Flags:
|
||||
--attendees string 参会人 userId 列表 (逗号分隔)
|
||||
--desc string 日程描述
|
||||
--end string 结束时间 ISO-8601 (必填)
|
||||
--open-dingtalk-ids string 参会人 openDingTalkId 列表 (逗号分隔)
|
||||
--start string 开始时间 ISO-8601 (必填)
|
||||
--timezone string 时区 (如 Asia/Shanghai)
|
||||
--title string 日程标题 (必填)
|
||||
```
|
||||
|
||||
### 修改日程
|
||||
```
|
||||
Usage:
|
||||
dws calendar event update [flags]
|
||||
Example:
|
||||
dws calendar event update --id <EVENT_ID> --title "新标题"
|
||||
dws calendar event update --id <EVENT_ID> --desc "议程调整" --timezone "Asia/Shanghai"
|
||||
Flags:
|
||||
--desc string 新描述
|
||||
--end string 新结束时间
|
||||
--id string 日程 ID (必填)
|
||||
--start string 新开始时间
|
||||
--timezone string 时区 (如 Asia/Shanghai)
|
||||
--title string 新标题
|
||||
```
|
||||
|
||||
### 删除日程
|
||||
```
|
||||
Usage:
|
||||
dws calendar event delete [flags]
|
||||
Example:
|
||||
dws calendar event delete --id <EVENT_ID> --yes
|
||||
Flags:
|
||||
--id string 日程 ID (必填)
|
||||
```
|
||||
|
||||
### 推荐会议时间
|
||||
```
|
||||
Usage:
|
||||
dws calendar event suggest [flags]
|
||||
Example:
|
||||
dws calendar event suggest --users <USER_ID_1>,<USER_ID_2> \
|
||||
--start "2026-03-10T09:00:00+08:00" --end "2026-03-10T18:00:00+08:00" --duration 30
|
||||
Flags:
|
||||
--duration string 会议时长(分钟)
|
||||
--end string 候选时段结束时间 ISO-8601 (必填)
|
||||
--start string 候选时段开始时间 ISO-8601 (必填)
|
||||
--timezone string 时区 (如 Asia/Shanghai)
|
||||
--users string 参会人 userId 列表 (必填)
|
||||
```
|
||||
|
||||
基于参会人闲忙数据给出排名后的候选时段,比 `busy search` 直接给出原始忙闲更适合"帮我约"场景。
|
||||
|
||||
### 查看参与者
|
||||
```
|
||||
Usage:
|
||||
dws calendar participant list [flags]
|
||||
Example:
|
||||
dws calendar participant list --event <EVENT_ID>
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
```
|
||||
|
||||
### 添加参与者
|
||||
```
|
||||
Usage:
|
||||
dws calendar participant add [flags]
|
||||
Example:
|
||||
dws calendar participant add --event <EVENT_ID> --users <USER_ID_1>,<USER_ID_2>
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--users string 用户 ID 列表 (必填)
|
||||
```
|
||||
|
||||
### 移除参与者
|
||||
```
|
||||
Usage:
|
||||
dws calendar participant delete [flags]
|
||||
Example:
|
||||
dws calendar participant delete --event <EVENT_ID> --users <USER_ID> --yes
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--users string 用户 ID 列表 (必填)
|
||||
```
|
||||
|
||||
### 搜索会议室
|
||||
```
|
||||
Usage:
|
||||
dws calendar room search [flags]
|
||||
Example:
|
||||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --available
|
||||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --group-id <GROUP_ID>
|
||||
Flags:
|
||||
--available 仅查空闲会议室
|
||||
--end string 结束时间 ISO-8601 (必填)
|
||||
--group-id string 会议室分组ID(可选,留空查根目录;超100条时需按分组查询)
|
||||
--start string 开始时间 ISO-8601 (必填)
|
||||
```
|
||||
|
||||
### 预定会议室
|
||||
```
|
||||
Usage:
|
||||
dws calendar room add [flags]
|
||||
Example:
|
||||
dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID>
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--rooms string 会议室 ID 列表 (必填)
|
||||
```
|
||||
|
||||
### 移除会议室
|
||||
```
|
||||
Usage:
|
||||
dws calendar room delete [flags]
|
||||
Example:
|
||||
dws calendar room delete --event <EVENT_ID> --rooms <ROOM_ID> --yes
|
||||
Flags:
|
||||
--event string 日程 ID (必填)
|
||||
--rooms string 会议室 ID 列表 (必填)
|
||||
```
|
||||
|
||||
### 会议室分组列表
|
||||
```
|
||||
Usage:
|
||||
dws calendar room list-groups [flags]
|
||||
Example:
|
||||
dws calendar room list-groups
|
||||
```
|
||||
|
||||
### 查询用户闲忙状态
|
||||
```
|
||||
Usage:
|
||||
dws calendar busy search [flags]
|
||||
Example:
|
||||
dws calendar busy search --users <USER_ID_1>,<USER_ID_2> \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T18:00:00+08:00"
|
||||
Flags:
|
||||
--end string 结束时间 ISO-8601 (必填)
|
||||
--start string 开始时间 ISO-8601 (必填)
|
||||
--users string 用户 ID 列表 (必填)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"日程/会议/约会/日历":
|
||||
- 查看 → `event list`
|
||||
- 详情 → `event get`
|
||||
- 创建/约 → `event create`
|
||||
- 修改/改时间 → `event update`
|
||||
- 取消/删除 → `event delete`
|
||||
- 帮我约/找个大家都有空的时间 → `event suggest`
|
||||
|
||||
用户说"参会人/与会者":
|
||||
- 查看 → `participant list`
|
||||
- 邀请/添加 → `participant add`
|
||||
- 移除 → `participant delete`
|
||||
|
||||
用户说"会议室/订会议室":
|
||||
- 哪个空闲 → `room search`
|
||||
- 预定 → `room add`
|
||||
- 取消预定 → `room delete`
|
||||
- 分组 → `room list-groups`,取 groupId 后 `room search --group-id`
|
||||
|
||||
用户说"有空吗/忙不忙/闲忙":
|
||||
- 要原始闲忙块 → `busy search`
|
||||
- 要直接给时段候选 → `event suggest`
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 创建日程 — 提取 eventId
|
||||
dws calendar event create --title "Q1 复盘会" \
|
||||
--start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json
|
||||
|
||||
# 2. 添加参与者
|
||||
dws calendar participant add --event <EVENT_ID> --users userId1,userId2 --format json
|
||||
|
||||
# 3. 预定会议室 (先搜空闲)
|
||||
dws calendar room search --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json
|
||||
# 若返回错误(会议室超100条),先查分组再按分组搜索:
|
||||
# dws calendar room list-groups --format json
|
||||
# dws calendar room search --start ... --end ... --group-id <GROUP_ID> --format json
|
||||
dws calendar room add --event <EVENT_ID> --rooms <ROOM_ID> --format json
|
||||
|
||||
# 4. 查看日程列表
|
||||
dws calendar event list --start "2026-03-10T14:00:00+08:00" --end "2026-03-10T15:00:00+08:00" --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `event create` | `eventId` | participant/room 操作的 --event |
|
||||
| `event list` | `events[].eventId` | event get/update/delete 的 --id |
|
||||
| `room search` | `rooms[].roomId` | room add 的 --rooms |
|
||||
| `room list-groups` | `groups[].groupId` | room search 的 --group-id |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 时间格式: `event create/update`、`event list` 和 `busy search` 用 ISO-8601
|
||||
- 创建日程不会自动预定会议室,需额外 `room add`
|
||||
- `room search` 不带 `--group-id` 时查根目录;企业会议室超过 100 条会报错,此时需先 `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` |
|
||||
@@ -0,0 +1,771 @@
|
||||
# 会话与群聊 (chat) 命令参考
|
||||
|
||||
> 命令别名: `dws im` 等价于 `dws chat`
|
||||
|
||||
## 命令总览
|
||||
|
||||
### group (群组管理)
|
||||
|
||||
| 子命令 | 用途 |
|
||||
|-------|------|
|
||||
| `group create` | 创建内部群 |
|
||||
| `group members` | 查看群成员列表 |
|
||||
| `group members add` | 添加群成员 |
|
||||
| `group members remove` | 移除群成员(⚠️ 危险操作) |
|
||||
| `group members add-bot` | 添加机器人到群 |
|
||||
| `group rename` | 修改群名称 |
|
||||
| `search` | 搜索群会话 |
|
||||
| `search-common` | 搜索共同群 |
|
||||
| `conversation-info` | 获取会话基础信息(单聊/群聊) |
|
||||
|
||||
### message (会话消息管理)
|
||||
|
||||
| 子命令 | 用途 |
|
||||
|-------|------|
|
||||
| `message send` | 以当前用户身份发群消息或单聊消息 |
|
||||
| `message list` | 拉取群聊或单聊会话消息 |
|
||||
| `message list-all` | 按时间范围拉取当前用户所有会话消息 |
|
||||
| `message list-topic-replies` | 拉取群话题回复消息列表 |
|
||||
| `message list-by-sender` | 搜索指定发送者的消息 |
|
||||
| `message list-mentions` | 拉取 @我 的消息 |
|
||||
| `message list-focused` | 拉取特别关注人的消息 |
|
||||
| `message list-unread-conversations` | 获取未读会话列表 |
|
||||
| `message search` | 按关键词搜索消息 |
|
||||
| `message send-by-bot` | 机器人发消息(群聊或批量单聊) |
|
||||
| `message recall-by-bot` | 机器人撤回消息 |
|
||||
| `message send-by-webhook` | 自定义机器人 Webhook 发消息 |
|
||||
| `list-top-conversations` | 拉取置顶会话列表 |
|
||||
|
||||
### bot (机器人管理)
|
||||
|
||||
| 子命令 | 用途 |
|
||||
|-------|------|
|
||||
| `bot search` | 搜索我的机器人 |
|
||||
|
||||
---
|
||||
|
||||
## group create — 创建内部群
|
||||
|
||||
当前登录用户自动成为群主。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat group create [flags]
|
||||
Example:
|
||||
dws chat group create --name "Q1 项目冲刺群" --users userId1,userId2,userId3
|
||||
Flags:
|
||||
--users string 成员 userId 列表,用户本身会自动加入,无需包含,逗号分隔,不超过20个 (必填)
|
||||
--name string 群名称 (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## group members list — 查看群成员列表
|
||||
|
||||
分页查询指定群聊的成员。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat group members list [flags]
|
||||
Example:
|
||||
dws chat group members list --id <openconversation_id>
|
||||
Flags:
|
||||
--cursor string 分页游标,首次从 0 开始
|
||||
--id string 群 ID / openconversation_id (必填)
|
||||
```
|
||||
|
||||
> ⚠️ 注意:v1.0.17 起 list 是显式子命令;旧用法 `dws chat group members --id ...` 已不再支持,请改用 `dws chat group members list --id ...`。
|
||||
|
||||
---
|
||||
|
||||
## group members add — 添加群成员
|
||||
|
||||
向指定群聊添加成员,需传入群 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 列表,逗号分隔 (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## group members remove — 移除群成员
|
||||
|
||||
> ⚠️ 危险操作:执行前必须向用户确认,同意后才加 `--yes`。
|
||||
|
||||
```
|
||||
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 列表,逗号分隔 (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## group members add-bot — 添加机器人到群
|
||||
|
||||
将自定义机器人添加到当前用户有管理权限的群聊中,如果没有权限则会报错。
|
||||
|
||||
```
|
||||
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 (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## group rename — 修改群名称
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat group rename [flags]
|
||||
Example:
|
||||
dws chat group rename --id <openconversation_id> --name "新群名"
|
||||
Flags:
|
||||
--id string 群 ID / openconversation_id (必填)
|
||||
--name string 修改后的群名称 (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## search — 搜索群会话
|
||||
|
||||
根据名称搜索会话列表。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat search [flags]
|
||||
Example:
|
||||
dws chat search --query "项目冲刺"
|
||||
Flags:
|
||||
--cursor string 分页游标 (首页留空)
|
||||
--query string 搜索关键词 (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## search-common — 搜索共同群
|
||||
|
||||
根据昵称列表搜索共同群聊。--nicks 指定要搜索的人员昵称(逗号分隔,必填)。--match-mode 控制匹配模式:AND 表示所有人都在群里,OR 表示任一人在群里(默认 AND)。
|
||||
|
||||
```
|
||||
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)
|
||||
|
||||
注意:
|
||||
- --nicks 传人员昵称(花名),逗号分隔,如 "风雷,山乔"
|
||||
- --match-mode AND 表示群里必须包含所有指定的人;OR 表示包含任意一人即可
|
||||
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message send — 以当前用户身份发消息
|
||||
|
||||
--group 指定群聊 ID 发群消息;--user 指定用户 userId 发单聊;--open-dingtalk-id 指定用户 openDingTalkId 发单聊。三者只能选其一,不能同时指定。消息内容为位置参数(恰好 1 个),支持 Markdown。`--title` 是消息标题,**群聊与单聊都必填**(API 强制要求;缺失时服务端返回误导性的 "发群服务窗会话消息失败",CLI 现在前置校验直接报错)。
|
||||
--群聊时可选 --at-all @所有人,或 --at-users 指定成员(仅群聊时生效)。
|
||||
--发送图片消息:指定 --media-id(通过 dt_media_upload 工具上传获得),自动设置 msgType=image,此时不需要传文本内容。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat message send [flags] [<text>]
|
||||
Example:
|
||||
dws chat message send --group <openconversation_id> --title "周报" --text "请提交本周日报"
|
||||
dws chat message send --user <userId> --title "提醒" --text "请查收"
|
||||
dws chat message send --open-dingtalk-id <openDingTalkId> --title "提醒" --text "请查收"
|
||||
dws chat message send --group <openconversation_id> --title "通知" "hello"
|
||||
dws chat message send --group <openconversation_id> --title "周报提醒" --text "请大家本周五前提交周报"
|
||||
dws chat message send --group <openconversation_id> --title "通知" --at-all "<@all> 请大家注意"
|
||||
dws chat message send --group <openconversation_id> --title "通知" --at-users userId1,userId2 "<@userId1> <@userId2> 请查收"
|
||||
dws chat message send --group <openconversation_id> --title "图片" --media-id <mediaId>
|
||||
dws chat message send --open-dingtalk-id <openDingTalkId> --title "图片" --media-id <mediaId>
|
||||
Flags:
|
||||
--text string 消息内容(推荐使用,也可用位置参数)
|
||||
--group string 群聊 openconversation_id(群聊时必填)
|
||||
--user string 接收人 userId(单聊时与 --open-dingtalk-id 二选一)
|
||||
--open-dingtalk-id string 接收人 openDingTalkId(单聊时与 --user 二选一,适用于三方应用等无法获取 userId 的场景)
|
||||
--title string 消息标题(必填)
|
||||
--at-all @所有人(仅群聊时生效,可选,默认 false)
|
||||
--at-users string @指定成员的 userId 列表,逗号分隔(仅群聊时生效,可选)
|
||||
--at-mobiles string @指定成员的手机号列表,逗号分隔(仅群聊时生效,可选)
|
||||
--media-id string 图片 mediaId(通过 dt_media_upload 工具上传获得,需从返回链接中去除 _宽_高.格式 后缀并加上 @ 前缀),指定后发送图片消息,不需要传文本内容
|
||||
--msg-type string 消息类型(可选,如 text/markdown/image/file;通常由 --text/--media-id/--dentry-id 自动推断)
|
||||
--dentry-id string 钉盘文件 dentryId(发送钉盘文件消息时使用,需配合 --space-id)
|
||||
--space-id string 钉盘空间 spaceId(与 --dentry-id 配合使用)
|
||||
--file-name string 文件消息的文件名
|
||||
--file-size string 文件消息的文件大小(字节)
|
||||
--file-type string 文件消息的文件类型(如 pdf / docx / xlsx 等)
|
||||
|
||||
注意:
|
||||
- --text 和位置参数二选一,--text 优先
|
||||
- --title 必填(群聊与单聊都必填,API 强制要求)
|
||||
- --group、--user、--open-dingtalk-id 三者互斥,只需指定其一:群聊用 --group,单聊用 --user 或 --open-dingtalk-id
|
||||
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
|
||||
- --at-all / --at-users / --at-mobiles 仅在 --group 群聊时生效;当设置--at-all时,消息内容中一定要包含对应的占位符<@all>;当设置--at-users userId1,userId2时,消息内容中一定要包含对应格式的占位符<@userId1> <@userId2>
|
||||
- --media-id 指定图片 mediaId 时自动发送图片消息(msgType=image),不需要传 --text;图片单聊仅支持 --open-dingtalk-id,不支持 --user
|
||||
- 发送钉盘文件消息:传 --dentry-id + --space-id(必要时配合 --file-name / --file-size / --file-type),msg-type 自动推断为 file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message list — 拉取会话消息内容
|
||||
|
||||
拉取指定群聊或单聊的会话消息内容。
|
||||
|
||||
--group 指定群聊,--user 指定单聊用户(通过 userId),--open-dingtalk-id 指定单聊用户(通过 openDingTalkId),三者互斥。默认拉取给定时间之后的消息,--forward=false 拉之前的。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat message list [flags]
|
||||
Example:
|
||||
# 拉取群聊中某个时间点之后的消息
|
||||
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00"
|
||||
# 拉取单聊消息(通过 userId)
|
||||
dws chat message list --user <userId> --time "2025-03-01 00:00:00" --limit 50
|
||||
# 拉取单聊消息(通过 openDingTalkId)
|
||||
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" --forward=false
|
||||
Flags:
|
||||
--forward true=拉给定时间之后的消息,false=拉给定时间之前的消息 (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
|
||||
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
|
||||
- 如果返回的会话消息中包含 openConvThreadId 字段,说明是话题类消息,需要调用 dws chat message list-topic-replies 拉取话题的回复内容列表,openConvThreadId 作为 --topic-id 参数
|
||||
```
|
||||
|
||||
### 分页翻页说明(重要)
|
||||
|
||||
`message list` 的翻页方式与 `message list-all` **完全不同**,请勿混淆:
|
||||
|
||||
| 命令 | 翻页参数 | 翻页值来源 | 值格式 |
|
||||
|------|---------|-----------|--------|
|
||||
| `message list` | `--time` | 上一页结果中**最后一条消息的 `createTime` 字段** | `yyyy-MM-dd HH:mm:ss`(如 `"2025-03-01 14:30:00"`) |
|
||||
| `message list-all` | `--cursor` | 上一页响应中的 `nextCursor` 字段 | 字符串(如 `"abc123token"`) |
|
||||
|
||||
**翻页步骤(message list):**
|
||||
|
||||
1. **首次请求**:指定起始时间
|
||||
```bash
|
||||
dws chat message list --group <id> --time "2025-03-01 00:00:00" --limit 50 --format json
|
||||
```
|
||||
2. **检查响应**:查看 `hasMore` 字段
|
||||
- `hasMore: false` → 没有更多消息,翻页结束
|
||||
- `hasMore: true` → 还有更多消息,继续下一步
|
||||
3. **获取翻页时间**:取返回结果中**最后一条消息**的 `createTime` 字段值(如 `"2025-03-01 14:30:00"`)
|
||||
4. **下一页请求**:将该 `createTime` 作为 `--time` 传入
|
||||
```bash
|
||||
dws chat message list --group <id> --time "2025-03-01 14:30:00" --limit 50 --format json
|
||||
```
|
||||
5. 重复步骤 2-4 直到 `hasMore: false`
|
||||
|
||||
> ⚠️ **常见错误**:
|
||||
> - **不要把 `nextCursor` 传给 `--time`**:响应中的 `nextCursor` 字段(纯数字时间戳如 `1776684611219`)**不是给 `--time` 用的**。`--time` 只接受 `yyyy-MM-dd HH:mm:ss` 格式。将 `nextCursor` 传给 `--time` 会导致返回相同页面,陷入死循环。`nextCursor` 仅用于 `message list-all` 的 `--cursor` 参数。
|
||||
> - **不要把 `nextCursor` 传给 `--forward`**:`--forward` 只接受 `true`(拉给定时间之后的消息)或 `false`(拉给定时间之前的消息),不是时间戳或游标参数。
|
||||
|
||||
---
|
||||
|
||||
## message list-all — 拉取指定时间范围内当前用户的所有会话消息
|
||||
|
||||
分页拉取当前登录用户在指定时间范围内的所有会话消息。
|
||||
|
||||
--start 和 --end 限定时间范围,--limit 指定每页数量,--cursor 传分页游标(首页传 "0",后续从响应中的 nextCursor 获取)。
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message list-topic-replies — 拉取群话题回复消息列表
|
||||
|
||||
查询指定群聊中某条话题消息的全部回复。--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
|
||||
Flags:
|
||||
--group string 群会话 openconversationId (必填)
|
||||
--topic-id string 话题 ID,由 dws chat message list 返回 (必填)
|
||||
--time string 开始时间,格式: yyyy-MM-dd HH:mm:ss(可选)
|
||||
--limit int 返回数量(默认 50)
|
||||
--forward true=从老往新,false=从新往老(默认 false)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message list-by-sender — 拉取指定发送者的消息
|
||||
|
||||
搜索特定人发送给我的消息,返回结果包含单聊和群聊标识。--sender-user-id 指定发送者 userId,--sender-open-dingtalk-id 指定发送者 openDingTalkId,二者互斥。
|
||||
|
||||
```
|
||||
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
|
||||
Flags:
|
||||
--sender-user-id string 发送者 userId(与 --sender-open-dingtalk-id 二选一)
|
||||
--sender-open-dingtalk-id string 发送者 openDingTalkId(与 --sender-user-id 二选一)
|
||||
--start string 开始时间,ISO-8601 格式 (必填)
|
||||
--end string 结束时间,ISO-8601 格式 (必填)
|
||||
--limit int 每页返回数量(默认 50)
|
||||
--cursor string 分页游标(默认 "0",翻页传 nextCursor)
|
||||
|
||||
注意:
|
||||
- --sender-user-id 和 --sender-open-dingtalk-id 二者互斥,必须且只能指定其一
|
||||
- 不需要指定单聊/群聊,MCP 返回结果自带会话类型标识
|
||||
- 时间支持多种 ISO-8601 格式,如 "2026-03-10T00:00:00+08:00"、"2026-03-10 14:00:00" 等
|
||||
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message list-mentions — 拉取 @我 的消息
|
||||
|
||||
搜索时间范围内 @我 的消息,可选指定群聊。
|
||||
|
||||
```
|
||||
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 --group <openconversation_id> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
|
||||
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)
|
||||
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message list-focused — 拉取特别关注人的消息
|
||||
|
||||
拉取当前用户特别关注人的消息。
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message list-unread-conversations — 获取未读会话列表
|
||||
|
||||
获取当前用户有未读消息的会话信息。可选通过 `--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 返回未读会话条数(可选)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message search — 按关键词搜索消息
|
||||
|
||||
在当前用户的会话中按关键词搜索消息。--keyword 必填,可选 --group 限定搜索某个会话。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat message search [flags]
|
||||
Example:
|
||||
dws chat message search --keyword "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 --keyword "codereview" --group <openconversation_id> --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 100 --cursor 0
|
||||
Flags:
|
||||
--keyword 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)
|
||||
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## conversation-info — 获取会话基础信息
|
||||
|
||||
按会话 ID 获取单聊或群聊的基础元数据(名称、类型、成员数等)。在对会话执行操作前用来确认上下文。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat conversation-info [flags]
|
||||
Example:
|
||||
dws chat conversation-info --group <openConversationId>
|
||||
dws chat conversation-info --open-dingtalk-id <openDingTalkId>
|
||||
Flags:
|
||||
--group string 群聊会话 ID openConversationId(与 --open-dingtalk-id 二选一)
|
||||
--open-dingtalk-id string 用户 openDingTalkId(单聊时与 --group 二选一)
|
||||
|
||||
注意:
|
||||
- --group(群聊 openConversationId)和 --open-dingtalk-id(单聊用户 openDingTalkId)二选一
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## list-top-conversations — 拉取置顶会话列表
|
||||
|
||||
拉取当前用户的置顶会话列表。
|
||||
|
||||
```
|
||||
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 int 分页游标(首次不传或传 0,翻页传 nextCursor)
|
||||
|
||||
注意:
|
||||
- 用户询问"置顶会话"时,直接调用此命令返回置顶会话列表即可
|
||||
- 用户询问"置顶消息"时,需两步:先调用此命令拉取置顶会话列表获取各会话的 openConversationId,再用 `chat message list --group <openConversationId>` 分别拉取每个会话内的消息
|
||||
- 翻页:hasMore=true 时,用返回的 nextCursor 作为下次 --cursor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## bot search — 搜索我的机器人
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message send-by-bot — 机器人发消息
|
||||
|
||||
群聊:传 --group 指定群;单聊:传 --users 指定用户列表,二者只能选其一,不能同时指定。--text 支持 Markdown。
|
||||
|
||||
```
|
||||
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 "请提交周报"
|
||||
Flags:
|
||||
--group string 群聊 openConversationId(群聊时必填)
|
||||
--robot-code string 机器人 Code (必填)
|
||||
--text string 消息内容 Markdown (必填)
|
||||
--title string 消息标题 (必填)
|
||||
--users string 用户 userId 列表,逗号分隔,最多20个(单聊时必填)
|
||||
|
||||
注意:
|
||||
- --group 与 --users 互斥,必须且只能指定其一
|
||||
- --group 的别名: --id, --chat, --conversation-id (均可替代 --group)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message recall-by-bot — 机器人撤回消息
|
||||
|
||||
群聊:传 --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 (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## message send-by-webhook — 自定义机器人 Webhook 发消息
|
||||
|
||||
@ 人时需在 --text 中包含 @userId 或 @手机号,否则 @ 不生效。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws chat message send-by-webhook [flags]
|
||||
Example:
|
||||
dws chat message send-by-webhook --token <webhook-token> --title "告警" --text "CPU 超 90%" --at-all
|
||||
dws chat message send-by-webhook --token <webhook-token> --title "test" --text "hi @118785" --at-users 118785
|
||||
Flags:
|
||||
--at-all @ 所有人
|
||||
--at-mobiles string @ 指定手机号,逗号分隔
|
||||
--at-users string @ 指定用户,逗号分隔(需在 text 中包含 @userId)
|
||||
--text string 消息内容 (必填)
|
||||
--title string 消息标题 (必填)
|
||||
--token string Webhook Token (必填)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"建群/创建群聊" → `chat group create`
|
||||
用户说"搜索群/找群" → `chat search`
|
||||
用户说"群成员/看群里有谁" → `chat group members list`
|
||||
用户说"拉人进群/加群成员" → `chat group members add`
|
||||
用户说"踢人/移除群成员" → `chat group members remove`
|
||||
用户说"加机器人到群" → `chat group members add-bot`
|
||||
用户说"改群名" → `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-by-bot`
|
||||
用户说"Webhook 发消息/告警消息" → `chat message send-by-webhook`
|
||||
用户说"话题回复/群话题消息回复/拉取话题回复" → `chat message list-topic-replies`
|
||||
用户说"所有消息/全部会话消息/拉取全部消息/时间范围内消息/我的消息/我今天的消息/查我的钉钉消息/最近的消息" → `chat message list-all`
|
||||
用户说"特别关注人的消息/关注的人的消息/星标联系人的消息" → `chat message list-focused`
|
||||
用户说"查看我的机器人" → `chat bot search`
|
||||
用户说"搜索消息/查找关键词/搜一下消息里的XX" → `chat message search`
|
||||
用户说"我和XX的共同群/我们都在哪些群/查共同群" → `chat search-common`
|
||||
用户说"置顶会话/置顶消息/我的置顶/查看置顶" → `chat list-top-conversations`
|
||||
用户说"获取会话信息/会话详情/会话元数据" → `chat conversation-info`
|
||||
|
||||
关键区分:
|
||||
- `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 list-all` — 拉取当前用户所有会话的消息,按时间范围 + cursor 分页。只要用户没有指定某个具体的会话(如某个群名、某个人名),即使提到"单聊消息""群聊消息"等笼统范围,也应路由到此命令
|
||||
- `chat message list-topic-replies` — 拉取群话题的回复消息列表
|
||||
- `chat message list-focused` — 拉取特别关注人的消息,cursor 分页
|
||||
- `chat list-top-conversations` — 拉取置顶会话列表(用户询问"置顶会话"或"置顶消息"时路由到此),cursor 分页
|
||||
- `chat message send` — 以**当前用户**身份发消息(群聊或单聊),text 为位置参数;支持 --media-id 发送图片消息
|
||||
- `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` — 通过机器人撤回已发送的消息
|
||||
- `chat conversation-info` — 按会话 ID 获取单聊/群聊的基础元数据(名称、类型、成员数等)
|
||||
- `chat bot search` — 搜索当前用户名下的机器人,拿到 robotCode 用于 send-by-bot / recall-by-bot / group members add-bot
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```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> --title "问候" "你好" --format json
|
||||
|
||||
# 4b. 以个人身份单聊(通过 openDingTalkId,三方应用等无法获取 userId 时使用)
|
||||
dws chat message send --open-dingtalk-id <openDingTalkId> --title "问候" "你好" --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%" --at-all --format json
|
||||
```
|
||||
|
||||
## 复合工作流
|
||||
|
||||
### 机器人发消息后撤回(完整流程)
|
||||
|
||||
撤回只能用于 `send-by-bot` 发出的消息。个人身份 (`chat message send`) 发出的消息**无法通过 API 撤回**。
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### 将已有机器人加入群并发消息(完整流程)
|
||||
|
||||
机器人需先在钉钉开放平台创建好,这里只做"找机器人 → 加入群 → 发消息"。
|
||||
|
||||
```bash
|
||||
# Step 1: 搜索我的机器人 — 提取 robotCode
|
||||
dws chat bot search --name "项目提醒" --format json
|
||||
|
||||
# Step 2: 搜索群 — 提取 openConversationId
|
||||
dws chat search --query "项目群" --format json
|
||||
|
||||
# Step 3: 将机器人添加到群(需当前用户对该群有管理权限)
|
||||
dws chat group members add-bot --id <openConversationId> --robot-code <robotCode> --format json
|
||||
|
||||
# Step 4: 机器人发消息
|
||||
dws chat message send-by-bot --robot-code <robotCode> --group <openConversationId> \
|
||||
--title "提醒" --text "请及时更新项目状态" --format json
|
||||
```
|
||||
|
||||
### 机器人 @指定人发群消息
|
||||
|
||||
`--text` 中**必须**包含 `<@userId>` 占位符,否则 @ 不生效。
|
||||
|
||||
```bash
|
||||
# Step 1: 搜人获取 userId
|
||||
dws aisearch person --keyword "张三" --dimension name --format json
|
||||
|
||||
# Step 2: 取 userId 发送(注意 text 中的占位符)
|
||||
dws chat message send-by-bot --robot-code <robot-code> --group <openconversation_id> \
|
||||
--title "提醒" --text "<@userId1> <@userId2> 请查收本周报告" --format json
|
||||
```
|
||||
|
||||
### 发送图片/文件消息(跨产品: drive → chat)
|
||||
|
||||
```bash
|
||||
# Step 1: 上传文件到钉盘 — 获取 uploadId 和凭证
|
||||
dws drive upload-info --file-name "截图.png" --file-size <字节数> --format json
|
||||
|
||||
# Step 2: HTTP PUT 上传文件到 OSS
|
||||
curl -X PUT -T "截图.png" "<upload-info 返回的上传 URL>"
|
||||
|
||||
# Step 3: 提交上传 — 获取 dentryUuid
|
||||
dws drive commit --file-name "截图.png" --file-size <字节数> --upload-id <uploadId> --format json
|
||||
|
||||
# Step 4: 获取下载链接
|
||||
dws drive download --file-id <dentryUuid> --format json
|
||||
|
||||
# Step 5: 用 Markdown 图片语法发送
|
||||
dws chat message send --group <openconversation_id> \
|
||||
--title "截图" --text "" --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `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、--at-users、send-by-bot 的 --users、list-by-sender 的 --sender-user-id |
|
||||
| `aisearch person` → `contact user get` | `openDingTalkId` | list-by-sender 的 --sender-open-dingtalk-id、message send/list 的 --open-dingtalk-id |
|
||||
| `chat bot search` | `robotCode` | send-by-bot / recall-by-bot 的 --robot-code、group members add-bot 的 --robot-code |
|
||||
| `chat message send-by-bot` | `processQueryKey` | recall-by-bot 的 --keys |
|
||||
| `chat conversation-info` | `openConversationId` / 成员信息 | 作为 message send/list 等后续操作的会话上下文确认 |
|
||||
| `chat message search` | `nextCursor` | 下次 message search 的 --cursor |
|
||||
| `chat search-common` | `openConversationId` | message send/list 等的 --group |
|
||||
| `drive download` | 下载链接 | message send 的 Markdown 图片/链接语法 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `--group` 为群聊会话 ID (openconversation_id),可从群搜索或群聊信息中获取
|
||||
- `chat message send` 的 text 是位置参数(恰好 1 个),非 flag;群聊用 `--group`,单聊用 `--user`(userId)或 `--open-dingtalk-id`(openDingTalkId),三者互斥;`--at-all`、`--at-users` 仅在 `--group` 群聊时生效;发送图片消息用 `--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` 不需要指定单聊/群聊,返回结果自带会话类型标识
|
||||
- `chat message list-mentions` 可选 `--group` 指定群聊,不传则查全部
|
||||
- `chat message list-unread-conversations` 获取当前用户未读会话列表,可选 `--count` 指定返回条数
|
||||
- `chat message search` 按关键词搜索消息内容,`--keyword` 必填,可选 `--group` 限定搜索某个会话
|
||||
- `chat search-common` 搜索共同群,`--nicks` 传人员昵称(逗号分隔),`--match-mode` AND/OR 控制匹配逻辑
|
||||
- `chat list-top-conversations` 拉取置顶会话列表,分页用 `--limit`(默认 1000)/`--cursor`
|
||||
- `send-by-bot` 群聊传 `--group`,单聊传 `--users`,二者互斥且必选其一
|
||||
- `recall-by-bot` 群聊传 `--group` + `--keys`,单聊仅传 `--keys`(不传 `--group` 即为单聊撤回)
|
||||
- `send-by-webhook` 支持 `--at-all`、`--at-mobiles`、`--at-users` 进行 @ 操作,但需在 `--text` 中包含 `@userId` 或 `@手机号` 才能生效
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [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"` |
|
||||
|
||||
## 相关产品
|
||||
|
||||
- [contact](./contact.md) — 搜索同事/好友,获取 userId 用于 --user、--at-users、send-by-bot --users、list-by-sender --sender-user-id;获取 openDingTalkId 用于 list-by-sender --sender-open-dingtalk-id、--open-dingtalk-id
|
||||
- [drive](./drive.md) — 上传文件获取下载链接,用于 Markdown 图片/文件消息
|
||||
@@ -0,0 +1,107 @@
|
||||
# 通讯录 (contact) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### user (人员查询)
|
||||
|
||||
#### 获取当前用户信息
|
||||
```
|
||||
Usage:
|
||||
dws contact user get-self [flags]
|
||||
Example:
|
||||
dws contact user get-self
|
||||
```
|
||||
|
||||
#### 按关键词搜索用户
|
||||
```
|
||||
Usage:
|
||||
dws contact user search [flags]
|
||||
Example:
|
||||
dws contact user search --query "张三"
|
||||
Flags:
|
||||
--query string 搜索关键词 (必填)
|
||||
```
|
||||
|
||||
#### 按手机号搜索用户
|
||||
```
|
||||
Usage:
|
||||
dws contact user search-mobile [flags]
|
||||
Example:
|
||||
dws contact user search-mobile --mobile 13800138000
|
||||
Flags:
|
||||
--mobile string 手机号 (必填)
|
||||
```
|
||||
|
||||
#### 批量获取用户详情
|
||||
```
|
||||
Usage:
|
||||
dws contact user get [flags]
|
||||
Example:
|
||||
dws contact user get --ids userId1,userId2
|
||||
Flags:
|
||||
--ids string 用户 ID 列表,逗号分隔 (必填)
|
||||
```
|
||||
|
||||
### dept (部门查询)
|
||||
|
||||
#### 搜索部门
|
||||
```
|
||||
Usage:
|
||||
dws contact dept search [flags]
|
||||
Example:
|
||||
dws contact dept search --query "技术部"
|
||||
Flags:
|
||||
--query string 搜索关键词 (必填)
|
||||
```
|
||||
|
||||
#### 查看部门成员
|
||||
```
|
||||
Usage:
|
||||
dws contact dept list-members [flags]
|
||||
Example:
|
||||
dws contact dept list-members --ids 12345,67890
|
||||
Flags:
|
||||
--ids string 部门 ID 列表,逗号分隔 (必填)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"我是谁/我的信息" → `user get-self`
|
||||
用户说"找人/搜人" → `user search`(按名字)或 `user search-mobile`(按手机号)
|
||||
用户说"查用户详情" → `user get`(需 userId)
|
||||
用户说"找部门/哪个部门" → `dept search`
|
||||
用户说"部门有谁/部门成员" → `dept list-members`(需 deptId)
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 查看自己的信息 — 提取 userId
|
||||
dws contact user get-self --format json
|
||||
|
||||
# 2. 按名字搜索同事 — 提取 userId
|
||||
dws contact user search --query "张三" --format json
|
||||
|
||||
# 3. 查看部门结构 — 提取 deptId
|
||||
dws contact dept search --query "技术部" --format json
|
||||
|
||||
# 4. 查看部门成员
|
||||
dws contact dept list-members --ids <deptId> --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 提取 | 用于 |
|
||||
|------|------|------|
|
||||
| `user get-self/search` | `userId` | 其他产品中的 --users/--executor 参数 |
|
||||
| `dept search` | `deptId` | dept list-members 的 --ids |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `user get-self` 是获取 userId 的最快方式,其他产品的 --users/--executor 都需要 userId
|
||||
- `user get --ids` 和 `dept list-members --ids` 都支持批量查询,逗号分隔
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [contact_dept_members.py](../../scripts/contact_dept_members.py) | 按部门名称搜索并列出所有成员 | `python contact_dept_members.py --keyword "技术部"` |
|
||||
@@ -0,0 +1,48 @@
|
||||
# 开放平台文档 (devdoc) 命令参考
|
||||
|
||||
搜索钉钉**开放平台**开发文档,用于回答开发者关于 OpenAPI、字段、错误码、接入指南、配额等技术问题。
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 搜索开发文档
|
||||
```
|
||||
Usage:
|
||||
dws devdoc article search [flags]
|
||||
Example:
|
||||
dws devdoc article search --query "OAuth2 接入"
|
||||
dws devdoc article search --query "消息卡片" --page 2 --size 5
|
||||
dws devdoc article search --query "机器人" --size 10
|
||||
Flags:
|
||||
--query string 搜索关键词 (必填)
|
||||
--page int 分页页码 (从 1 开始,默认 1)
|
||||
--size int 分页大小 (默认 10)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户问开放平台 API / 字段 / 错误码 / SDK / 鉴权 / 回调 / 配额相关的技术细节:
|
||||
- 走 `devdoc article search`,把用户问的关键短语作为 `--query`
|
||||
|
||||
关键区分:
|
||||
- devdoc(钉钉**开放平台**开发者文档,面向研发) vs doc(钉钉在线文档,面向普通用户内容)
|
||||
- devdoc 只做搜索,不做读取;命中条目返回标题、摘要、文档链接,由 Agent 引用链接或进一步浏览
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 开发者问"OAuth2 怎么接"
|
||||
dws devdoc article search --query "OAuth2 接入" --format json
|
||||
|
||||
# 命中结果多时翻页
|
||||
dws devdoc article search --query "消息卡片" --page 2 --size 5 --format json
|
||||
|
||||
# 查错误码 / 字段含义
|
||||
dws devdoc article search --query "errcode 40078" --format json
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `--query` 必填;建议传用户原话里的关键名词(API 名、错误码、能力名),不要过度改写
|
||||
- 返回按相关性排序,默认 `--size 10`;要拿更多结果时先翻页,再考虑换关键词
|
||||
- 命中结果里的链接是钉钉开放平台公开文档,可直接给用户做参考
|
||||
- 不要把 devdoc 用来查业务数据(那是 aitable / doc / report 的事);devdoc 只查**官方开发者文档**
|
||||
@@ -0,0 +1,57 @@
|
||||
# DING 消息 (ding) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 发送 DING 消息
|
||||
```
|
||||
Usage:
|
||||
dws ding message send [flags]
|
||||
Example:
|
||||
dws ding message send --robot-code <ROBOT_CODE> --users <USER_ID_1>,<USER_ID_2> --content "请查看"
|
||||
Flags:
|
||||
--content string 消息内容 (必填)
|
||||
--robot-code string 机器人 ID (必填, 可从 应用管理→机器人 获取, 或设 DINGTALK_DING_ROBOT_CODE)
|
||||
--users string 接收人 userId 列表 (必填)
|
||||
--type string 提醒类型: app/sms/call (默认 app)
|
||||
```
|
||||
|
||||
### 撤回 DING 消息
|
||||
```
|
||||
Usage:
|
||||
dws ding message recall [flags]
|
||||
Example:
|
||||
dws ding message recall --robot-code <ROBOT_CODE> --id <OPEN_DING_ID>
|
||||
Flags:
|
||||
--id string DING 消息 ID (必填)
|
||||
--robot-code string 机器人 ID (必填, 或设 DINGTALK_DING_ROBOT_CODE)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"DING 一下/紧急通知/电话提醒" → `message send`
|
||||
用户说"撤回 DING" → `message recall`
|
||||
|
||||
关键区分:
|
||||
- ding(紧急提醒, 支持电话/短信) vs bot(常规群/单聊消息)
|
||||
- sms/call 类型有通信费用
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 应用内 DING (免费)
|
||||
dws ding message send --robot-code <ROBOT_CODE> --type app --users userId1,userId2 --content "请查看" --format json
|
||||
|
||||
# 电话 DING (紧急, 有成本!)
|
||||
dws ding message send --robot-code <ROBOT_CODE> --type call --users userId1 --content "紧急告警" --format json
|
||||
|
||||
# 撤回
|
||||
dws ding message recall --robot-code <ROBOT_CODE> --id <OPEN_DING_ID> --format json
|
||||
```
|
||||
## 上下文传递表
|
||||
| 操作 | 提取 | 用于 |
|
||||
|------|------|------|
|
||||
| `message send` | `openDingId` | message recall 的 --id |
|
||||
## 注意事项
|
||||
- `--robot-code` 从钉钉开放平台 **应用管理 → 机器人** 中获取,也可设环境变量 `DINGTALK_DING_ROBOT_CODE`
|
||||
- sms/call 类型有通信费用,使用前需和用户确认
|
||||
- 默认 `--type app`(应用内 DING,免费)
|
||||
@@ -0,0 +1,577 @@
|
||||
# 文档 (doc) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 搜索文档
|
||||
```
|
||||
Usage:
|
||||
dws doc search [flags]
|
||||
Example:
|
||||
dws doc search --query "会议纪要"
|
||||
dws doc search
|
||||
dws doc search --extensions pdf,docx
|
||||
dws doc search --query "方案" --created-from 1700000000000 --created-to 1710000000000
|
||||
dws doc search --creator-uids uid1,uid2
|
||||
dws doc search --workspace-ids wsId1,wsId2
|
||||
Flags:
|
||||
--query string 搜索关键词 (不传则返回最近访问)
|
||||
--extensions strings 按文件扩展名过滤,不含点号,逗号分隔 (如 pdf,docx,png)。支持的在线文档类型后缀名: adoc=文字, axls=表格, appt=演示文稿, awbd=白板, adraw=画板, amind=脑图, able=多维表格, aform=收集表
|
||||
--created-from int 创建时间起始 (毫秒时间戳,含)
|
||||
--created-to int 创建时间截止 (毫秒时间戳,含)
|
||||
--visited-from int 访问时间起始 (毫秒时间戳,含)
|
||||
--visited-to int 访问时间截止 (毫秒时间戳,含)
|
||||
--creator-uids strings 按创建者用户 ID 过滤,逗号分隔
|
||||
--editor-uids strings 按编辑者用户 ID 过滤,逗号分隔
|
||||
--mentioned-uids strings 按 @提及的用户 ID 过滤,逗号分隔
|
||||
--workspace-ids strings 按知识库 ID 过滤,支持知识库 URL,逗号分隔
|
||||
--page-size int 每页数量 (默认 10,最大 30)
|
||||
--page-token string 分页游标 (从上次结果的 nextPageToken 获取)
|
||||
```
|
||||
|
||||
### 遍历文件列表
|
||||
```
|
||||
Usage:
|
||||
dws doc list [flags]
|
||||
Example:
|
||||
dws doc list
|
||||
dws doc list --folder <FOLDER_ID>
|
||||
dws doc list --workspace <WS_ID> --page-size 20
|
||||
Flags:
|
||||
--folder string 文件夹 ID 或 URL
|
||||
--workspace string 知识库 ID
|
||||
--page-size int 每页数量 (默认 50,最大 50)
|
||||
--page-token string 分页游标 (从上次结果的 nextPageToken 获取)
|
||||
```
|
||||
|
||||
### 获取文档元信息
|
||||
```
|
||||
Usage:
|
||||
dws doc info [flags]
|
||||
Example:
|
||||
dws doc info --node <DOC_ID>
|
||||
dws doc info --node "https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>"
|
||||
Flags:
|
||||
--node string 文档 ID 或 URL (必填)
|
||||
```
|
||||
|
||||
### 读取文档内容
|
||||
```
|
||||
Usage:
|
||||
dws doc read [flags]
|
||||
Example:
|
||||
dws doc read --node <DOC_ID>
|
||||
dws doc read --node "https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>"
|
||||
Flags:
|
||||
--node string 文档 ID 或 URL (必填)
|
||||
```
|
||||
|
||||
### 创建文档
|
||||
```
|
||||
Usage:
|
||||
dws doc create [flags]
|
||||
Example:
|
||||
dws doc create --name "项目周报"
|
||||
dws doc create --name "Q1 总结" --markdown "# Q1 总结" --folder <FOLDER_ID>
|
||||
dws doc create --name "知识库文档" --workspace <WS_ID>
|
||||
Flags:
|
||||
--name string 文档名称 (必填)
|
||||
--folder string 目标文件夹 ID 或 URL
|
||||
--workspace string 目标知识库 ID
|
||||
--markdown string 文档初始 Markdown 内容
|
||||
```
|
||||
|
||||
### 创建其他类型文件 (表格/脑图/白板/多维表/画板)
|
||||
```
|
||||
Usage:
|
||||
dws doc file create [flags]
|
||||
Example:
|
||||
dws doc file create --name "项目周报" --type adoc
|
||||
dws doc file create --name "数据统计" --type axls --folder <FOLDER_ID>
|
||||
dws doc file create --name "思维导图" --type amind --workspace <WS_ID>
|
||||
dws doc file create --name "子文件夹" --type folder
|
||||
Flags:
|
||||
--name string 文件名称 (必填)
|
||||
--type string 文件类型 (必填): adoc=文档, axls=表格, appt=演示, adraw=白板, amind=脑图, able=多维表, folder=文件夹
|
||||
--folder string 目标文件夹 ID 或 URL
|
||||
--workspace string 目标知识库 ID 或 URL
|
||||
```
|
||||
|
||||
### 更新文档内容
|
||||
```
|
||||
Usage:
|
||||
dws doc update [flags]
|
||||
Example:
|
||||
dws doc update --node <DOC_ID> --markdown "# 追加内容" --mode append
|
||||
dws doc update --node <DOC_ID> --markdown "# 完整替换" --mode overwrite
|
||||
Flags:
|
||||
--node string 文档 ID 或 URL (必填)
|
||||
--markdown string Markdown 内容 (必填)
|
||||
--mode string 更新模式: overwrite=覆盖, append=追加 (默认 append)
|
||||
```
|
||||
|
||||
### 上传文件到钉钉文档或钉钉知识库
|
||||
```
|
||||
Usage:
|
||||
dws doc upload [flags]
|
||||
Example:
|
||||
dws doc upload --file ./report.pdf
|
||||
dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder <FOLDER_ID>
|
||||
dws doc upload --file ./data.xlsx --workspace <WS_ID> --convert
|
||||
Flags:
|
||||
--file string 本地文件路径 (必填)
|
||||
--name string 文件显示名称 (默认使用文件名)
|
||||
--folder string 目标文件夹 ID 或 URL
|
||||
--workspace string 目标知识库 ID
|
||||
--convert 是否转换为钉钉在线文档
|
||||
```
|
||||
|
||||
### 下载文件到本地
|
||||
```
|
||||
Usage:
|
||||
dws doc download [flags]
|
||||
Example:
|
||||
dws doc download --node <NODE_ID>
|
||||
dws doc download --node <NODE_ID> --output ./report.pdf
|
||||
dws doc download --node "https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>" --output ~/downloads/
|
||||
Flags:
|
||||
--node string 文件节点 ID 或 URL (必填)
|
||||
--output string 本地保存路径 (文件路径或目录,必填)
|
||||
```
|
||||
|
||||
### 创建文件夹
|
||||
```
|
||||
Usage:
|
||||
dws doc folder create [flags]
|
||||
Example:
|
||||
dws doc folder create --name "项目资料"
|
||||
dws doc folder create --name "子文件夹" --folder <PARENT_FOLDER_ID>
|
||||
Flags:
|
||||
--name string 文件夹名称 (必填)
|
||||
--folder string 父文件夹 ID 或 URL
|
||||
--workspace string 目标知识库 ID
|
||||
```
|
||||
|
||||
### 复制文档/文件
|
||||
```
|
||||
Usage:
|
||||
dws doc copy [flags]
|
||||
Example:
|
||||
dws doc copy --node <DOC_ID> --folder <TARGET_FOLDER_ID>
|
||||
dws doc copy --node <DOC_ID> --workspace <TARGET_WS_ID>
|
||||
dws doc copy --node "https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>" --folder <FOLDER_ID>
|
||||
Flags:
|
||||
--node string 源文档/文件 ID 或 URL (必填)
|
||||
--folder string 目标文件夹 ID 或 URL
|
||||
--workspace string 目标知识库 ID 或 URL (不传 --folder 时复制到该知识库根目录)
|
||||
```
|
||||
|
||||
### 移动文档/文件
|
||||
```
|
||||
Usage:
|
||||
dws doc move [flags]
|
||||
Example:
|
||||
dws doc move --node <DOC_ID> --folder <TARGET_FOLDER_ID>
|
||||
dws doc move --node <DOC_ID> --workspace <TARGET_WS_ID>
|
||||
Flags:
|
||||
--node string 源文档/文件 ID 或 URL (必填)
|
||||
--folder string 目标文件夹 ID 或 URL
|
||||
--workspace string 目标知识库 ID 或 URL (不传 --folder 时移动到该知识库根目录)
|
||||
```
|
||||
|
||||
### 重命名文档/文件
|
||||
```
|
||||
Usage:
|
||||
dws doc rename [flags]
|
||||
Example:
|
||||
dws doc rename --node <DOC_ID> --name "新名称"
|
||||
dws doc rename --node "https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>" --name "项目周报 v2"
|
||||
Flags:
|
||||
--node string 文档/文件 ID 或 URL (必填)
|
||||
--name string 新名称 (必填)
|
||||
```
|
||||
|
||||
### 查询块元素
|
||||
```
|
||||
Usage:
|
||||
dws doc block list [flags]
|
||||
Example:
|
||||
dws doc block list --node <DOC_ID>
|
||||
dws doc block list --node <DOC_ID> --start-index 0 --end-index 5
|
||||
dws doc block list --node <DOC_ID> --block-type heading
|
||||
Flags:
|
||||
--node string 文档 ID 或 URL (必填)
|
||||
--start-index int 起始位置 (从 0 开始)
|
||||
--end-index int 终止位置 (含)
|
||||
--block-type string 按块类型过滤
|
||||
```
|
||||
|
||||
### 插入块元素
|
||||
```
|
||||
Usage:
|
||||
dws doc block insert [flags]
|
||||
Example:
|
||||
dws doc block insert --node <DOC_ID> --text "这是一段文字"
|
||||
dws doc block insert --node <DOC_ID> --heading "二级标题" --level 2
|
||||
dws doc block insert --node <DOC_ID> --element '{"blockType":"paragraph","paragraph":{"text":"内容"}}'
|
||||
dws doc block insert --node <DOC_ID> --text "在此处之前插入" --ref-block <BLOCK_ID> --where before
|
||||
Flags:
|
||||
--node string 文档 ID 或 URL (必填)
|
||||
--text string 快捷: 段落文本内容
|
||||
--heading string 快捷: 标题文本
|
||||
--level int 标题级别 1-6 (配合 --heading,默认 1)
|
||||
--element string 块元素 JSON (高级)
|
||||
--index int 参照位置索引 (从 0 开始)
|
||||
--where string 插入方向: before / after (默认 after)
|
||||
--ref-block string 参照块 ID (优先级高于 --index)
|
||||
```
|
||||
|
||||
### 更新块元素
|
||||
```
|
||||
Usage:
|
||||
dws doc block update [flags]
|
||||
Example:
|
||||
dws doc block update --node <DOC_ID> --block-id <BLOCK_ID> --text "新内容"
|
||||
dws doc block update --node <DOC_ID> --block-id <BLOCK_ID> --element '{"blockType":"heading","heading":{"text":"新标题","level":1}}'
|
||||
Flags:
|
||||
--node string 文档 ID 或 URL (必填)
|
||||
--block-id string 目标块 ID (必填)
|
||||
--text string 快捷: 段落文本内容
|
||||
--heading string 快捷: 标题文本
|
||||
--level int 标题级别 1-6 (配合 --heading,默认 1)
|
||||
--element string 块元素 JSON (高级)
|
||||
```
|
||||
|
||||
### 删除块元素
|
||||
|
||||
> **CAUTION:** 不可逆操作 — 执行前必须向用户确认。
|
||||
|
||||
```
|
||||
Usage:
|
||||
dws doc block delete [flags]
|
||||
Example:
|
||||
dws doc block delete --node <DOC_ID> --block-id <BLOCK_ID> --yes
|
||||
Flags:
|
||||
--node string 文档 ID 或 URL (必填)
|
||||
--block-id string 目标块 ID (必填)
|
||||
```
|
||||
|
||||
### 查询文档评论列表
|
||||
```
|
||||
Usage:
|
||||
dws doc comment list [flags]
|
||||
Example:
|
||||
dws doc comment list --node <DOC_ID>
|
||||
dws doc comment list --node <DOC_ID> --type inline --resolve-status unresolved
|
||||
dws doc comment list --node <DOC_ID> --page-size 20 --next-token <TOKEN>
|
||||
Flags:
|
||||
--node string 目标文档的标识,支持传入 URL 或 ID (必填)
|
||||
--page-size int 每页返回的评论数量,默认 50,最大 50
|
||||
--next-token string 分页游标,从上一次请求的返回结果中获取 (首次请求不传)
|
||||
--type string 按评论类型过滤: global (全文评论) / inline (划词评论)
|
||||
--resolve-status string 按解决状态过滤: resolved (已解决) / unresolved (未解决)
|
||||
```
|
||||
|
||||
### 创建文档评论
|
||||
```
|
||||
Usage:
|
||||
dws doc comment create [flags]
|
||||
Example:
|
||||
dws doc comment create --node <DOC_ID> --content "这里需要修改"
|
||||
dws doc comment create --node <DOC_ID> --content "请review" --mention uid1,uid2
|
||||
Flags:
|
||||
--node string 目标文档的标识,支持传入 URL 或 ID (必填)
|
||||
--content string 评论的文字内容,纯文本 (必填)
|
||||
--mention string 被 @ 的用户 uid 列表,逗号分隔
|
||||
```
|
||||
|
||||
### 创建划词评论 (内联评论)
|
||||
```
|
||||
Usage:
|
||||
dws doc comment create-inline [flags]
|
||||
Example:
|
||||
dws doc comment create-inline --node <DOC_ID> --block-id <BLOCK_ID> --start 0 --end 10 --content "这里需要修改"
|
||||
dws doc comment create-inline --node <DOC_ID> --block-id <BLOCK_ID> --start 5 --end 20 --content "建议调整" --selected-text "被选中的原文"
|
||||
dws doc comment create-inline --node <DOC_ID> --block-id <BLOCK_ID> --start 0 --end 10 --content "请review" --mention uid1,uid2
|
||||
Flags:
|
||||
--node string 目标文档的标识,支持传入 URL 或 ID (必填)
|
||||
--block-id string 评论锚定所在的块 ID (必填,可通过 dws doc block list 获取)
|
||||
--start int 选中文本在块内的起始字符偏移量,从 0 开始 (必填)
|
||||
--end int 选中文本在块内的结束字符偏移量,必须大于 start (必填)
|
||||
--content string 评论的文字内容,纯文本 (必填)
|
||||
--selected-text string 选中文本内容,填写后评论列表会展示「引用原文:xxx」
|
||||
--mention string 被 @ 的用户 uid 列表,逗号分隔
|
||||
```
|
||||
|
||||
### 回复文档评论
|
||||
```
|
||||
Usage:
|
||||
dws doc comment reply [flags]
|
||||
Example:
|
||||
dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "同意"
|
||||
dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "比心" --emoji
|
||||
dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "请确认" --mention uid1,uid2
|
||||
Flags:
|
||||
--node string 目标文档的标识,支持传入 URL 或 ID (必填)
|
||||
--content string 回复的文字内容,表情回复时填写表情名称 (必填)
|
||||
--comment-key string 被回复评论的 commentKey,格式: {13位毫秒时间戳}{32位UUID},可从 list/create 结果获取 (必填)
|
||||
--emoji 设为 true 时作为表情贴图回复 (默认 false)
|
||||
--mention string 被 @ 的用户 uid 列表,逗号分隔
|
||||
```
|
||||
|
||||
## URL 识别与 DOC_ID 提取
|
||||
|
||||
当用户输入包含钉钉文档 URL 时,**必须先识别并提取 DOC_ID**,再判断意图。
|
||||
|
||||
### 支持的 URL 格式
|
||||
|
||||
| 格式 | 示例 | DOC_ID 提取方式 |
|
||||
|------|------|----------------|
|
||||
| `alidocs.dingtalk.com/i/nodes/{id}` | `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` | 取 URL 路径最后一段:`9E05BDRVQePjzLkZt2p2vE7kV63zgkYA` |
|
||||
| `alidocs.dingtalk.com/i/nodes/{id}?queryParams` | `https://alidocs.dingtalk.com/i/nodes/abc123?doc_type=wiki_doc` | 忽略 query 参数,取路径最后一段:`abc123` |
|
||||
|
||||
### 提取规则
|
||||
|
||||
1. 匹配 URL 中 `alidocs.dingtalk.com` 域名
|
||||
2. 取 URL path 的最后一段作为 DOC_ID(去掉 query string 和 fragment)
|
||||
3. 提取出的 DOC_ID 可直接用于所有 `--node` 参数,也可将完整 URL 传给 `--node`(CLI 会自动解析)
|
||||
|
||||
### 处理流程
|
||||
|
||||
```
|
||||
用户输入含 alidocs.dingtalk.com URL
|
||||
→ 提取 DOC_ID(URL 路径最后一段)
|
||||
→ 结合用户意图选择命令(默认 read)
|
||||
→ 将 DOC_ID 传给 --node 参数
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"找文档/搜文档/最近文档":
|
||||
- 搜索 → `search`
|
||||
- 浏览 → `list`
|
||||
|
||||
用户说"看文档/读内容/文档内容":
|
||||
- 读取 → `read` (需文档 ID 或 URL)
|
||||
- 元信息 → `info`
|
||||
|
||||
用户说"写文档/创建文档":
|
||||
- 新建纯文档 (adoc) → `create`
|
||||
- 追加内容 → `update --mode append`
|
||||
- 覆盖替换 → `update --mode overwrite`
|
||||
|
||||
用户说"新建表格/脑图/白板/多维表/演示文稿":
|
||||
- 用 `file create --type` 指定类型 (axls/amind/adraw/able/appt/adraw)
|
||||
|
||||
用户说"建文件夹/新建目录":
|
||||
- 创建 → `folder create` 或 `file create --type folder`
|
||||
|
||||
用户说"复制文档/拷贝一份":
|
||||
- 复制 → `copy` (需源 --node 和目标 --folder/--workspace)
|
||||
|
||||
用户说"移动文档/换个目录":
|
||||
- 移动 → `move` (需源 --node 和目标 --folder/--workspace)
|
||||
|
||||
用户说"改文档名字/重命名":
|
||||
- 改名 → `rename` (需 --node 和新 --name)
|
||||
|
||||
用户说"上传文件/传文件/上传到文档/上传到知识库":
|
||||
- 上传 → `upload`(需本地文件路径)
|
||||
- 上传并转换 → `upload --convert`
|
||||
|
||||
用户说"下载文件/导出文件/下载到本地":
|
||||
- 下载 → `download`(需文件节点 ID 或 URL)
|
||||
|
||||
用户说"编辑块/改段落/插入标题/删除块":
|
||||
- 查看结构 → `block list`
|
||||
- 插入 → `block insert`
|
||||
- 修改 → `block update`
|
||||
- 删除 → `block delete`
|
||||
|
||||
**用户直接粘贴文档 URL(无其他指令)**:
|
||||
- 默认 → `read`(读取文档内容)
|
||||
- 如 URL 明显是文件夹 → `list`(列出文件夹内容)
|
||||
|
||||
**用户粘贴 URL + 附加指令**:
|
||||
- "帮我看看这个文档" → `read`
|
||||
- "这个文档的信息" → `info`
|
||||
- "往这个文档追加内容" → `update --mode append`
|
||||
- "编辑这个文档的标题" → `block update`
|
||||
|
||||
关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理)
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# ── 工作流 1: 浏览并阅读文档 ──
|
||||
|
||||
# 1. 浏览我的文档根目录
|
||||
dws doc list --format json
|
||||
|
||||
# 2. 浏览子文件夹
|
||||
dws doc list --folder <FOLDER_ID> --format json
|
||||
|
||||
# 3. 获取文档元信息 (标题、类型、权限)
|
||||
dws doc info --node <DOC_ID> --format json
|
||||
|
||||
# 4. 读取文档内容 (Markdown 格式)
|
||||
dws doc read --node <DOC_ID> --format json
|
||||
|
||||
# ── 工作流 2: 创建文档并写入内容 ──
|
||||
|
||||
# 1. (可选) 创建文件夹 — 提取 nodeId
|
||||
dws doc folder create --name "项目资料" --format json
|
||||
|
||||
# 2. 创建文档 — 提取 nodeId
|
||||
dws doc create --name "项目周报" --folder <FOLDER_ID> --format json
|
||||
|
||||
# 3. 写入内容 (追加模式)
|
||||
dws doc update --node <DOC_ID> --markdown "# 本周总结\n\n- 完成了 A\n- 推进了 B" --mode append --format json
|
||||
|
||||
# ── 工作流 3: 一步创建带内容的文档 ──
|
||||
|
||||
dws doc create --name "会议纪要" --markdown "# 会议纪要\n\n## 议题\n\n1. ..." --format json
|
||||
|
||||
# ── 工作流 4: 上传本地文件到钉钉文档/知识库 ──
|
||||
|
||||
# 1. 上传到"我的文档"根目录
|
||||
dws doc upload --file ./report.pdf
|
||||
|
||||
# 2. 上传到指定文件夹
|
||||
dws doc upload --file ./slides.pptx --name "Q1汇报.pptx" --folder <FOLDER_ID>
|
||||
|
||||
# 3. 上传到知识库并转换为在线文档
|
||||
dws doc upload --file ./data.xlsx --workspace <WS_ID> --convert
|
||||
|
||||
# ── 工作流 5: 下载文件到本地 ──
|
||||
|
||||
# 1. 下载到当前目录 (自动推断文件名)
|
||||
dws doc download --node <NODE_ID>
|
||||
|
||||
# 2. 下载到指定路径
|
||||
dws doc download --node <NODE_ID> --output ./report.pdf
|
||||
|
||||
# 3. 下载到指定目录 (自动推断文件名)
|
||||
dws doc download --node <NODE_ID> --output ~/downloads/
|
||||
|
||||
# ── 工作流 6: 块级精细编辑 ──
|
||||
|
||||
# 1. 查看文档块结构 — 获取 blockId
|
||||
dws doc block list --node <DOC_ID> --format json
|
||||
|
||||
# 2. 在文档末尾插入段落
|
||||
dws doc block insert --node <DOC_ID> --text "新增内容"
|
||||
|
||||
# 3. 在指定块之前插入标题
|
||||
dws doc block insert --node <DOC_ID> --heading "新章节" --level 2 --ref-block <BLOCK_ID> --where before
|
||||
|
||||
# 4. 更新某个块的内容
|
||||
dws doc block update --node <DOC_ID> --block-id <BLOCK_ID> --text "修改后的内容"
|
||||
|
||||
# 5. 删除块
|
||||
dws doc block delete --node <DOC_ID> --block-id <BLOCK_ID> --yes
|
||||
|
||||
# ── 工作流 7: 文档评论管理 ──
|
||||
|
||||
# 1. 查看文档的所有评论
|
||||
dws doc comment list --node <DOC_ID> --format json
|
||||
|
||||
# 2. 在文档上创建全文评论
|
||||
dws doc comment create --node <DOC_ID> --content "这里需要补充数据来源" --format json
|
||||
|
||||
# 3. 创建评论并 @ 相关人
|
||||
# 先搜索用户: dws contact user search --query "张三" --format json → 提取 userId
|
||||
# 再将 userId 传入 --mention
|
||||
dws doc comment create --node <DOC_ID> --content "请确认这部分内容" --mention <userId1>,<userId2> --format json
|
||||
|
||||
# 4. 对某段文字创建划词评论(需先 block list 拿 blockId 和字符偏移)
|
||||
dws doc block list --node <DOC_ID> --format json
|
||||
dws doc comment create-inline --node <DOC_ID> --block-id <BLOCK_ID> --start 0 --end 12 \
|
||||
--content "这里的数据要复核" --selected-text "被选中的原文片段" --format json
|
||||
|
||||
# 5. 回复某条评论(commentKey 从 list 或 create 返回中获取)
|
||||
dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "已修改" --format json
|
||||
|
||||
# 6. 用表情回复评论
|
||||
dws doc comment reply --node <DOC_ID> --comment-key <COMMENT_KEY> --content "比心" --emoji --format json
|
||||
|
||||
# ── 工作流 8: 创建非文档类型文件 ──
|
||||
|
||||
# 创建表格 / 脑图 / 白板 / 多维表 / 演示文稿
|
||||
dws doc file create --name "销售数据" --type axls --folder <FOLDER_ID> --format json
|
||||
dws doc file create --name "需求脑图" --type amind --workspace <WS_ID> --format json
|
||||
dws doc file create --name "Q1 立项会" --type appt --format json
|
||||
|
||||
# ── 工作流 9: 整理文档结构 (复制 / 移动 / 重命名) ──
|
||||
|
||||
# 1. 把模板复制到目标目录作为新工作件
|
||||
dws doc copy --node <TEMPLATE_DOC_ID> --folder <TARGET_FOLDER_ID> --format json
|
||||
|
||||
# 2. 把文档从个人空间挪到团队知识库
|
||||
dws doc move --node <DOC_ID> --workspace <WS_ID> --format json
|
||||
|
||||
# 3. 重命名
|
||||
dws doc rename --node <DOC_ID> --name "项目周报 v2" --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `list` | `nodes[].nodeId` | read / info / update / block 操作的 --node |
|
||||
| `list` | folder 类型的 `nodeId` | list 的 --folder, create 的 --folder |
|
||||
| `search` | 文档 `nodeId` / URL / `createTime` / `creatorUid` | read / info / update 的 --node;创建时间与创建者信息 |
|
||||
| `create` | `nodeId` | update / block 操作的 --node |
|
||||
| `folder create` | `nodeId` | create / list / upload 的 --folder |
|
||||
| `block list` | `blockId` | block insert 的 --ref-block, block update/delete 的 --block-id |
|
||||
| `upload` | `nodeId` / URL | 上传后文件的访问链接 |
|
||||
| `download` | 本地文件路径 | 下载后的文件保存位置 |
|
||||
| `comment list` | `commentList[].commentKey` | comment reply 的 --comment-key |
|
||||
| `comment create` / `comment create-inline` | `commentKey` | comment reply 的 --comment-key |
|
||||
| `block list` | `blockId` + 文本内容 | comment create-inline 的 --block-id 及 --start/--end 计算 |
|
||||
| `contact user search` | `userId` | comment create / create-inline / reply 的 --mention |
|
||||
| `file create` | `nodeId` | 后续 read / update / block 操作的 --node(仅 adoc 支持 read/update,axls/amind 等类型用各自产品的命令) |
|
||||
| `copy` / `move` | 新 `nodeId`(copy)或原 nodeId(move) | 后续 read / info 等的 --node |
|
||||
|
||||
## nodeId 双格式说明
|
||||
|
||||
所有 `--node` 参数同时支持两种格式,系统自动识别:
|
||||
- **文档 ID**: 字母数字字符串,如 `9E05BDRVQePjzLkZt2p2vE7kV63zgkYA`
|
||||
- **文档 URL**: `https://alidocs.dingtalk.com/i/nodes/{dentryUuid}`,如 `https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA`
|
||||
|
||||
两种方式等价,以下命令效果相同:
|
||||
```bash
|
||||
dws doc read --node 9E05BDRVQePjzLkZt2p2vE7kV63zgkYA
|
||||
dws doc read --node "https://alidocs.dingtalk.com/i/nodes/9E05BDRVQePjzLkZt2p2vE7kV63zgkYA"
|
||||
```
|
||||
|
||||
`--folder` 参数同样支持文件夹 URL 或 ID。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `update --mode overwrite` 会**清空原内容后重写**,⚠️ 谨慎使用;默认 `--mode append` (追加) 更安全
|
||||
- `read` 返回 Markdown 格式的文档内容,仅限有"下载"权限的文档
|
||||
- `create` 不传 `--folder` 和 `--workspace` 时,默认创建在"我的文档"根目录
|
||||
- `block list/insert/update/delete` 是块级精细编辑,适合结构化修改;简单内容追加建议用 `update --mode append`
|
||||
- `block insert` 优先使用 `--text` 或 `--heading` 快捷方式;复杂块类型 (table, callout 等) 使用 `--element` JSON
|
||||
- `markdown` 参数中的换行必须使用**真实换行符**(即实际的换行字符,Unicode `U+000A`),而不是字面量字符串 `\n`(反斜杠加字母 n)。在通过程序或大模型构造此参数时,请确保字符串在发送前已正确反转义。如果传入的是两个字符的字面量 `\n`,所有内容将渲染在同一行,导致标题、段落和表格格式全部错乱。
|
||||
- 块类型包括: paragraph, heading, blockquote, callout, columns, orderedList, unorderedList, table, sheet, attachment, slot
|
||||
- 关键区分: doc(文档编辑/阅读) vs aitable(数据表格操作) vs drive(钉盘文件管理)
|
||||
- `upload` 支持上传任意类型文件 (PDF、Office、图片等) 到钉钉文档空间或知识库;`--convert` 可将 Office 文件转换为钉钉在线文档
|
||||
- `upload` 是三步自动完成的流程 (获取凭证 → OSS 上传 → 提交入库),无需手动分步操作
|
||||
- `download` 是两步自动完成的流程 (获取下载链接 → HTTP GET 下载),支持自动推断文件名;`--output` 可指定文件路径或目录
|
||||
- `create` 只能建"文档"(adoc);要建表格/脑图/白板/多维表/演示/文件夹,用 `file create --type`
|
||||
- `copy` 需要对源节点有"阅读"权限、对目标目录有"编辑"权限;`move` 需要对源节点有"管理"权限
|
||||
- `copy` / `move` 不传 `--folder` 时,`--workspace` 表示放到知识库根目录;两者都不传则回落到"我的文档"
|
||||
- `comment create` 是全文评论;`comment create-inline` 是划词评论,必须先 `block list` 拿到 `blockId` 并确定 `--start` / `--end` 偏移(按块内纯文本字符算,从 0 开始)
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [doc_create_and_write.py](../../scripts/doc_create_and_write.py) | 创建文档并写入 Markdown 内容 | `python doc_create_and_write.py --name "周报" --content "# 本周总结"` |
|
||||
|
||||
## 相关产品
|
||||
|
||||
- [aitable](./aitable.md) — 结构化数据表格(行列/字段/记录),不是富文本文档
|
||||
- [drive](./drive.md) — 钉盘文件存储/上传/下载,不是文档内容编辑
|
||||
- [report](./report.md) — 钉钉日志系统(日报/周报模版),不是在线文档
|
||||
@@ -0,0 +1,177 @@
|
||||
# 钉盘 (drive) 命令参考
|
||||
|
||||
钉盘 = DingTalk Drive,用于云端文件存储 / 上传 / 下载 / 目录管理。不是在线文档编辑;要编辑文档请用 [doc](./doc.md)。
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 列出钉盘目录
|
||||
```
|
||||
Usage:
|
||||
dws drive list [flags]
|
||||
Example:
|
||||
dws drive list
|
||||
dws drive list --parent-id <FOLDER_ID>
|
||||
dws drive list --parent-id <FOLDER_ID> --max 20 --order-by modifiedTime --order desc
|
||||
Flags:
|
||||
--parent-id string 父目录 ID (不传则列根目录)
|
||||
--space-id string 钉盘空间 ID (一般无需指定)
|
||||
--max int 每页数量 (默认 20)
|
||||
--next-token string 分页游标 (从上次结果的 nextToken 获取)
|
||||
--order-by string 排序字段: name / createdTime / modifiedTime
|
||||
--order string 排序方向: asc / desc
|
||||
--thumbnail 是否返回缩略图链接
|
||||
```
|
||||
|
||||
### 获取文件元信息
|
||||
```
|
||||
Usage:
|
||||
dws drive info [flags]
|
||||
Example:
|
||||
dws drive info --file-id <FILE_ID>
|
||||
Flags:
|
||||
--file-id string 文件或文件夹 ID (必填)
|
||||
--space-id string 钉盘空间 ID (一般无需指定)
|
||||
```
|
||||
|
||||
### 创建文件夹
|
||||
```
|
||||
Usage:
|
||||
dws drive mkdir [flags]
|
||||
Example:
|
||||
dws drive mkdir --name "项目资料"
|
||||
dws drive mkdir --name "子目录" --parent-id <PARENT_FOLDER_ID>
|
||||
Flags:
|
||||
--name string 文件夹名称 (必填)
|
||||
--parent-id string 父目录 ID (不传则建在根目录)
|
||||
--space-id string 钉盘空间 ID (一般无需指定)
|
||||
```
|
||||
|
||||
### 获取下载临时链接
|
||||
```
|
||||
Usage:
|
||||
dws drive download [flags]
|
||||
Example:
|
||||
dws drive download --file-id <FILE_ID>
|
||||
Flags:
|
||||
--file-id string 文件 ID (必填)
|
||||
--space-id string 钉盘空间 ID (一般无需指定)
|
||||
```
|
||||
|
||||
### 获取上传凭证 (上传第一步)
|
||||
```
|
||||
Usage:
|
||||
dws drive upload-info [flags]
|
||||
Example:
|
||||
dws drive upload-info --file-name "report.pdf" --file-size 102400
|
||||
dws drive upload-info --file-name "slides.pptx" --file-size 512000 --parent-id <FOLDER_ID>
|
||||
Flags:
|
||||
--file-name string 文件名含后缀 (必填)
|
||||
--file-size int 文件大小,单位字节 (必填)
|
||||
--mime-type string MIME 类型 (可选,服务端会自动推断)
|
||||
--parent-id string 父目录 ID (不传则上传到根目录)
|
||||
--space-id string 钉盘空间 ID (一般无需指定)
|
||||
```
|
||||
|
||||
### 提交上传 (上传第三步)
|
||||
```
|
||||
Usage:
|
||||
dws drive commit [flags]
|
||||
Example:
|
||||
dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id <UPLOAD_ID>
|
||||
Flags:
|
||||
--file-name string 文件名含后缀 (必填,须与 upload-info 一致)
|
||||
--file-size int 文件大小,单位字节 (必填,须与 upload-info 一致)
|
||||
--upload-id string upload-info 返回的 uploadId (必填)
|
||||
--parent-id string 父目录 ID (必填时须与 upload-info 一致)
|
||||
--space-id string 钉盘空间 ID (一般无需指定)
|
||||
--conflict-handler string 同名冲突策略: AUTO_RENAME / OVERWRITE / RETURN_DENTRY_IF_EXIST (默认 AUTO_RENAME)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"钉盘有什么文件/列钉盘/看钉盘目录" → `list`
|
||||
用户说"钉盘文件详情/文件信息" → `info` (需 fileId)
|
||||
用户说"新建钉盘目录/钉盘里建文件夹" → `mkdir`
|
||||
用户说"下载钉盘文件/把这个文件拿下来" → `download` 拿临时 URL,再由 Agent 自行发起 HTTP GET
|
||||
用户说"上传文件到钉盘/把本地文件传钉盘":
|
||||
- 三步走: `upload-info` → HTTP PUT 到预签名 URL → `commit`
|
||||
- **没有**一条聚合命令可以完成;必须完整走完三步
|
||||
|
||||
关键区分:
|
||||
- drive(钉盘云存储,面向文件二进制) vs doc(在线文档/知识库,面向富文本内容)
|
||||
- 把图片/文件发到群里一般走 drive 上传拿链接 → chat 发送 Markdown 链接 (见 [chat.md](./chat.md) 的 `drive → chat` 工作流)
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# ── 工作流 1: 浏览钉盘 ──
|
||||
|
||||
# 1. 看根目录
|
||||
dws drive list --format json
|
||||
|
||||
# 2. 进入子目录 (parentId 取自上一步的 dentryUuid)
|
||||
dws drive list --parent-id <FOLDER_ID> --format json
|
||||
|
||||
# 3. 看单个文件的元信息
|
||||
dws drive info --file-id <FILE_ID> --format json
|
||||
|
||||
# ── 工作流 2: 上传本地文件到钉盘 (三步不能省) ──
|
||||
|
||||
# Step 1: 拿上传凭证
|
||||
dws drive upload-info --file-name "report.pdf" --file-size 102400 --parent-id <FOLDER_ID> --format json
|
||||
# → 返回: resourceUrl (预签名 URL), headers, uploadId
|
||||
|
||||
# Step 2: Agent 自己发 HTTP PUT 把文件二进制推到 resourceUrl
|
||||
# 请求头必须携带返回的 headers 全部键值对;期望 HTTP 200
|
||||
curl -X PUT -T ./report.pdf -H "<header-from-step-1>: <value>" "<resourceUrl>"
|
||||
|
||||
# Step 3: 提交入库
|
||||
dws drive commit --file-name "report.pdf" --file-size 102400 --upload-id <UPLOAD_ID> \
|
||||
--parent-id <FOLDER_ID> --format json
|
||||
# → 返回: dentryUuid (= 后续 download/info 用的 fileId)
|
||||
|
||||
# ── 工作流 3: 下载钉盘文件到本地 (两步) ──
|
||||
|
||||
# Step 1: 拿临时下载 URL
|
||||
dws drive download --file-id <FILE_ID> --format json
|
||||
# → 返回: resourceUrl (带签名的临时下载 URL), expirationSeconds
|
||||
|
||||
# Step 2: Agent 自己发 HTTP GET 下载二进制
|
||||
curl -o ./report.pdf "<resourceUrl>"
|
||||
|
||||
# ── 工作流 4: 建目录后批量上传 ──
|
||||
|
||||
# 1. 建目录 → 拿 folderId
|
||||
dws drive mkdir --name "2026 Q1 归档" --format json
|
||||
|
||||
# 2. 再走 "工作流 2" 把每个文件上传到该目录
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `list` | `dentryUuid` (folder 类) | 下次 list 的 --parent-id、upload-info / commit 的 --parent-id、mkdir 的 --parent-id |
|
||||
| `list` | `dentryUuid` (file 类) | info / download 的 --file-id |
|
||||
| `list` | `nextToken` | 下次 list 的 --next-token |
|
||||
| `mkdir` | `dentryUuid` | 作为父目录传给 upload-info / commit 的 --parent-id |
|
||||
| `upload-info` | `resourceUrl` + `headers` | Agent 自行执行 HTTP PUT 上传二进制 |
|
||||
| `upload-info` | `uploadId` | commit 的 --upload-id |
|
||||
| `commit` | `dentryUuid` | download / info / chat message 发送图片链接的 --file-id |
|
||||
| `download` | `resourceUrl` | Agent 自行执行 HTTP GET 下载;也可作为 Markdown 图片/附件链接发送到 chat |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 上传是**三步**流程:`upload-info` → 客户端 HTTP PUT 到预签名 URL → `commit`。**没有**自动聚合命令,跳过任何一步都会失败
|
||||
- Step 2 的 HTTP PUT 必须把 upload-info 返回的 `headers` 全部回传,`Content-Type` 通常要留空;只有 PUT 返回 200 才能调 `commit`
|
||||
- 上传凭证 (`uploadId`) 有过期时间,拿到后尽快 commit;过期需重新调 `upload-info`
|
||||
- `download` 只返回临时 URL(几分钟级别有效期),不会把文件落地;要真正下载到本地必须再发 HTTP GET
|
||||
- `--parent-id` 在 upload-info / commit 中要保持一致,否则 commit 会报位置不匹配
|
||||
- `--conflict-handler` 默认 `AUTO_RENAME` (自动重命名);`OVERWRITE` 覆盖同名文件前必须和用户确认
|
||||
- `--space-id` 绝大多数场景不需要传;默认会用用户主钉盘空间
|
||||
- 文件名规则:头尾不能有空格;不能含 `*`、`"`、`<`、`>`、`|`、制表符;不能以 `.` 结尾
|
||||
|
||||
## 相关产品
|
||||
|
||||
- [doc](./doc.md) — 钉钉在线文档 / 知识库(文字、表格、脑图等富文本节点),和 drive 的裸文件存储不同
|
||||
- [chat](./chat.md) — 结合 drive 发送图片 / 附件消息到群聊(Markdown 链接语法)
|
||||
@@ -0,0 +1,160 @@
|
||||
# 邮箱 (mail) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 查询可用邮箱地址
|
||||
```
|
||||
Usage:
|
||||
dws mail mailbox list [flags]
|
||||
Example:
|
||||
dws mail mailbox list
|
||||
```
|
||||
|
||||
**返回字段:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `mailboxes` | `List[]` | 邮箱列表,每条包含邮箱地址、账号类型、所属企业 |
|
||||
|
||||
### 搜索邮件 (KQL 语法)
|
||||
```
|
||||
Usage:
|
||||
dws mail message search [flags]
|
||||
Example:
|
||||
dws mail message search --email user@company.com --query "subject:\"周报\"" --size 20
|
||||
dws mail message search --email user@company.com --query "from:alice AND date>2025-06-01T00:00:00Z" --size 10
|
||||
Flags:
|
||||
--cursor string 邮件的起始偏移标识, 其值取自响应中的nextCursor字段。""表示从头开始
|
||||
--email string 搜索目标邮箱地址 (必填)
|
||||
--query string KQL 查询表达式 (必填), 其中 date 格式需遵循 ISO8601 规范
|
||||
--size string 每页返回数量(最大限制 100, 默认 20) (必填),别名: --limit, --page-size
|
||||
```
|
||||
|
||||
KQL 查询字段: date, size, tag, folderId, isRead, hasAttachments, subject, attachname, body, from, to
|
||||
常用文件夹 ID: 1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除
|
||||
|
||||
### KQL 查询字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 | 正确示例 | 错误示例 |
|
||||
|------|------|------|----------|----------|
|
||||
| `date` | ISO8601 日期时间 | 邮件日期,支持 `>` `<` `>=` `<=` 比较运算符 | `date>2025-06-01T00:00:00Z` | `date>2025-06-01`(缺少时间部分) |
|
||||
| `size` | 整数(字节数) | 邮件大小,支持 `>` `<` `>=` `<=` 比较运算符 | `size>1024` | `size>"1024"`(值不需要引号) |
|
||||
| `tag` | 字符串 | 邮件标签 | `tag:important` | `tag:""` |
|
||||
| `folderId` | 整数 | 文件夹 ID(1=已发送, 2=收件箱, 3=垃圾邮件, 5=草稿, 6=已删除) | `folderId:2` | `folderId:"收件箱"`(必须用数字 ID) |
|
||||
| `isRead` | 布尔 `true`/`false` | 是否已读 | `isRead:false` | `isRead:0`、`isRead:"false"`(不支持数字或字符串形式) |
|
||||
| `hasAttachments` | 布尔 `true`/`false` | 是否有附件 | `hasAttachments:true` | `hasAttachments:yes` |
|
||||
| `subject` | 字符串 | 邮件主题,含空格须加双引号 | `subject:周报`、`subject:"项目 进展"` | `subject:项目 进展`(含空格未加引号) |
|
||||
| `attachname` | 字符串 | 附件文件名,含空格须加双引号 | `attachname:report.pdf`、`attachname:"月度 报告.xlsx"` | `attachname:月度 报告.xlsx`(含空格未加引号) |
|
||||
| `body` | 字符串 | 邮件正文内容,含空格须加双引号 | `body:会议纪要`、`body:"Q1 总结"` | `body:Q1 总结`(含空格未加引号) |
|
||||
| `from` | 字符串(邮件地址或名称) | 发件人,支持:纯邮件地址、纯名称(含空格须加双引号)、`"名称<邮件地址>"` 格式 | `from:alice@company.com`、`from:"张 三"`、`from:"alice<a@b.com>"` | `from:张 三`(含空格未加引号) |
|
||||
| `to` | 字符串(邮件地址或名称) | 收件人,支持:纯邮件地址、纯名称(含空格须加双引号)、`"名称<邮件地址>"` 格式 | `to:bob@company.com`、`to:"李 四"`、`to:"alice<a@b.com>"` | `to:李 四`(含空格未加引号) |
|
||||
|
||||
**组合查询说明:**
|
||||
- 支持 `AND` / `OR` / `NOT` 逻辑运算符(大写)
|
||||
- 括号用于分组:`(from:alice OR from:bob) AND folderId:2`
|
||||
- 排除特定文件夹:`(NOT folderId:3) AND (NOT folderId:6)`
|
||||
|
||||
### message search 返回值说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `messages` | `List[]` | 邮件列表,每条包含邮件 ID 及元信息(不含正文) |
|
||||
| `total` | `int32` | 符合条件的总邮件数 |
|
||||
| `nextCursor` | `string` | 下一页游标,传入 `--cursor` 翻页;值为 `$` 表示已到达列表尾部 |
|
||||
|
||||
**翻页示例:**
|
||||
```bash
|
||||
# 第一页
|
||||
dws mail message search --email user@company.com --query "folderId:2" --size 20 --format json
|
||||
# 取返回中的 nextCursor,传入下一次请求(nextCursor="$" 时停止)
|
||||
dws mail message search --email user@company.com --query "folderId:2" --size 20 --cursor <nextCursor> --format json
|
||||
```
|
||||
|
||||
### 查看邮件完整内容
|
||||
```
|
||||
Usage:
|
||||
dws mail message get [flags]
|
||||
Example:
|
||||
dws mail message get --email user@company.com --id <messageId>
|
||||
Flags:
|
||||
--email string 邮件所属邮箱地址 (必填)
|
||||
--id string 邮件 ID (必填)
|
||||
```
|
||||
|
||||
**返回字段:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `message` | `object` | 邮件完整信息,包含主题、发件人、收件人、正文、附件等 |
|
||||
|
||||
### 发送邮件
|
||||
```
|
||||
Usage:
|
||||
dws mail message send [flags]
|
||||
Example:
|
||||
dws mail message send --from user@company.com --to colleague@company.com \
|
||||
--subject "周报" --body "本周完成任务A和任务B"
|
||||
Flags:
|
||||
--body string 邮件正文,支持 Markdown 格式 (必填)
|
||||
--cc string 抄送人列表
|
||||
--from string 发件人邮箱 (必填),别名: --sender
|
||||
--subject string 邮件标题 (必填)
|
||||
--to string 收件人列表 (必填)
|
||||
```
|
||||
|
||||
## 通用错误说明
|
||||
|
||||
以下错误适用于所有 mail 命令。
|
||||
|
||||
| 错误标识 | 含义 | 处理建议 |
|
||||
|----------|------|----------|
|
||||
| `domain.notFound` | 该用户的邮箱不是由钉钉邮箱托管,无法完成操作 | 确认邮箱是否已开通钉钉企业邮箱服务 |
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"我的邮箱/邮箱地址" → `mailbox list`
|
||||
用户说"找邮件/搜邮件/查邮件" → `message search`
|
||||
用户说"看邮件/打开邮件/邮件内容" → 先 `message search` 获取 messageId,再 `message get`
|
||||
用户说"发邮件/写邮件" → 先 `mailbox list` 获取发件地址,再 `message send`
|
||||
用户说"给(某人名字)发邮件" → 先 `aisearch person` 获取 userId,再 `contact user get` 获取收件人邮箱,再 `message send`
|
||||
|
||||
|
||||
## 严格禁止 (NEVER DO)
|
||||
- 明确禁止猜测、假设、推断发件人和收件人邮箱
|
||||
- 无法获取邮箱时,强引导ask_human,由用户确认,不要通过假设或其他方式继续执行
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 查看可用邮箱 — 提取邮箱地址
|
||||
dws mail mailbox list --format json
|
||||
|
||||
# 2. 搜索邮件 — 提取 messageId
|
||||
dws mail message search --email user@company.com \
|
||||
--query "subject:\"周报\" AND date>2025-06-01T00:00:00Z" --size 10 --format json
|
||||
|
||||
# 3. 查看邮件详情
|
||||
dws mail message get --email user@company.com --id <messageId> --format json
|
||||
|
||||
# 4. 发送邮件
|
||||
dws mail message send --from user@company.com --to colleague@company.com \
|
||||
--subject "周报" --body "本周完成…" --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `mailbox list` | 邮箱地址 | message search/get/send 的 --email/--from |
|
||||
| `message search` | `messageId` | message get 的 --id |
|
||||
| `aisearch person` → `contact user get` | 用户邮箱 (orgAuthEmail) | message send 的 --to/--cc (跨产品) |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `mailbox list` 返回用户所有邮箱(含个人和企业),每条记录包含邮箱地址、账号类型、所属企业。选择邮箱时优先匹配用户当前所在企业的企业邮箱;若有多个可选,向用户确认后再操作
|
||||
- `message search` 返回邮件 ID 和元信息(不含正文),需 `message get` 获取完整内容
|
||||
- KQL 查询支持 AND/OR/NOT 组合,字段值含空格时需用双引号
|
||||
- `--cc` 抄送人支持多人,逗号分隔
|
||||
- 收件人邮箱获取:用户只知道同事名字时,先通过 `dws aisearch person --keyword "名字" --dimension name` 获取 userId,再 `dws contact user get --ids <userId>` 从返回中提取 orgAuthEmail 字段
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
# AI听记 (minutes) 命令参考
|
||||
|
||||
AI 听记:列表 / 详情 / 摘要 / 待办 / 文字稿 / 思维导图 / 发言人 / 热词 / 文件上传。
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 查询我创建的听记列表
|
||||
```
|
||||
Usage:
|
||||
dws minutes list mine [flags]
|
||||
Example:
|
||||
dws minutes list mine
|
||||
dws minutes list mine --max 10
|
||||
dws minutes list mine --max 10 --next-token <nextToken>
|
||||
dws minutes list mine --query "周会"
|
||||
Flags:
|
||||
--max float 查询的听记篇数 (默认 10)
|
||||
--next-token string 分页 token (首页留空,后续填写前次返回的 nextToken)
|
||||
--query string 关键字筛选 (可选)
|
||||
--start string 开始时间 ISO-8601 (可选)
|
||||
--end string 结束时间 ISO-8601 (可选)
|
||||
```
|
||||
|
||||
查询我创建的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。
|
||||
|
||||
### 查询他人共享给我的听记列表
|
||||
```
|
||||
Usage:
|
||||
dws minutes list shared [flags]
|
||||
Example:
|
||||
dws minutes list shared
|
||||
dws minutes list shared --max 20
|
||||
dws minutes list shared --max 5 --next-token <nextToken>
|
||||
Flags:
|
||||
--max float 查询的听记篇数 (默认 10)
|
||||
--next-token string 分页 token (首页留空,后续填写前次返回的 nextToken)
|
||||
--query string 关键字筛选 (可选)
|
||||
--start string 开始时间 ISO-8601 (可选)
|
||||
--end string 结束时间 ISO-8601 (可选)
|
||||
```
|
||||
|
||||
查询他人共享给我的听记列表,支持 `--max` 和 `--next-token` 分页,支持按关键字和时间范围筛选。
|
||||
|
||||
### 查询我有权限访问的所有听记列表
|
||||
```
|
||||
Usage:
|
||||
dws minutes list all [flags]
|
||||
Example:
|
||||
dws minutes list all
|
||||
dws minutes list all --max 20
|
||||
dws minutes list all --query "周会" --max 20
|
||||
dws minutes list all --start "2026-03-01T00:00:00+08:00" --end "2026-03-20T23:59:59+08:00"
|
||||
dws minutes list all --max 10 --next-token <nextToken>
|
||||
Flags:
|
||||
--end string 结束时间 ISO-8601 (可选)
|
||||
--query string 关键字筛选 (可选)
|
||||
--max float 查询的听记篇数 (默认 10)
|
||||
--next-token string 分页 token (首页留空,后续填写前次返回的 nextToken)
|
||||
--start string 开始时间 ISO-8601 (可选)
|
||||
```
|
||||
|
||||
查询我有权限访问的所有听记列表(包括我创建的、他人共享给我的等所有有权限的听记)。支持按关键字和时间范围筛选。时间范围和关键字为可选参数,不传则返回所有有权限的听记。支持使用 `--max` 和 `--next-token` 进行分页查询。
|
||||
|
||||
### 获取听记基础信息
|
||||
```
|
||||
Usage:
|
||||
dws minutes get info [flags]
|
||||
Example:
|
||||
dws minutes get info --id <taskUuid>
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项
|
||||
```
|
||||
|
||||
返回字段: 创建人、开始时间、截止时间、听记标题、听记访问链接URL
|
||||
|
||||
### 获取听记 AI 摘要
|
||||
```
|
||||
Usage:
|
||||
dws minutes get summary [flags]
|
||||
Example:
|
||||
dws minutes get summary --id <taskUuid>
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项
|
||||
```
|
||||
|
||||
返回 Markdown 格式摘要,涵盖会议主题、核心结论、关键讨论点等
|
||||
|
||||
### 获取听记关键字列表
|
||||
```
|
||||
Usage:
|
||||
dws minutes get keywords [flags]
|
||||
Example:
|
||||
dws minutes get keywords --id <taskUuid>
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项
|
||||
```
|
||||
|
||||
### 获取听记语音转写原文
|
||||
```
|
||||
Usage:
|
||||
dws minutes get transcription [flags]
|
||||
Example:
|
||||
dws minutes get transcription --id <taskUuid>
|
||||
dws minutes get transcription --id <taskUuid> --direction 1
|
||||
Flags:
|
||||
--direction string 排序方向: 0=正序, 1=倒序 (默认 0)
|
||||
--id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项
|
||||
--next-token string 下一页的token 首次查询可空 后续查询需填写前次请求返回的nextToken
|
||||
```
|
||||
|
||||
每条记录包含: 发言人信息、转写文本、对应时间戳
|
||||
|
||||
### 获取听记中提取的待办事项
|
||||
```
|
||||
Usage:
|
||||
dws minutes get todos [flags]
|
||||
Example:
|
||||
dws minutes get todos --id <taskUuid>
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项
|
||||
```
|
||||
|
||||
每条记录包含: 待办内容、待办唯一ID、参与人信息、待办时间
|
||||
|
||||
### 批量查询听记详情
|
||||
```
|
||||
Usage:
|
||||
dws minutes get batch [flags]
|
||||
Example:
|
||||
dws minutes get batch --ids uuid1,uuid2,uuid3
|
||||
Flags:
|
||||
--ids string 听记 taskUuid 列表,逗号分隔 (必填)
|
||||
```
|
||||
|
||||
返回字段: 听记标题、时长、参与人列表、创建时间、taskUuid、听记状态
|
||||
|
||||
### 修改听记标题
|
||||
```
|
||||
Usage:
|
||||
dws minutes update title [flags]
|
||||
Example:
|
||||
dws minutes update title --id <taskUuid> --title "Q2 复盘会议"
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填),取值逻辑参考 ## 注意事项
|
||||
--title string 新标题 (必填)
|
||||
```
|
||||
|
||||
### 覆盖听记 AI 摘要
|
||||
```
|
||||
Usage:
|
||||
dws minutes update summary [flags]
|
||||
Example:
|
||||
dws minutes update summary --id <taskUuid> --content "修订后的摘要 Markdown"
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填)
|
||||
--content string 新的摘要正文 (必填),会整体覆盖原摘要
|
||||
```
|
||||
|
||||
将 AI 生成的摘要替换成定制版本,适合人工修订或按业务口径重写后回写。
|
||||
|
||||
### 全文替换听记文本
|
||||
```
|
||||
Usage:
|
||||
dws minutes replace-text [flags]
|
||||
Example:
|
||||
dws minutes replace-text --id <taskUuid> --search "小钉" --replace "DingTalk"
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填)
|
||||
--search string 需要替换的原文 (必填)
|
||||
--replace string 替换后的文本 (必填)
|
||||
```
|
||||
|
||||
在转写段落与摘要中一次性替换所有命中文本,适合纠正系统性的识别错误(如专有名词)。
|
||||
|
||||
### 替换发言人标签
|
||||
```
|
||||
Usage:
|
||||
dws minutes speaker replace [flags]
|
||||
Example:
|
||||
dws minutes speaker replace --id <taskUuid> --from "说话人1" --to "张三"
|
||||
dws minutes speaker replace --id <taskUuid> --from "说话人1" --to "张三" --target-uid <userId>
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填)
|
||||
--from string 原发言人昵称,如 "说话人1" (必填)
|
||||
--to string 新发言人昵称 (必填)
|
||||
--target-uid string 绑定到指定钉钉 userId (可选)
|
||||
```
|
||||
|
||||
修正自动分离的发言人标签,可顺带把该说话人对应到真实用户。
|
||||
|
||||
### 添加个人热词
|
||||
```
|
||||
Usage:
|
||||
dws minutes hot-word add [flags]
|
||||
Example:
|
||||
dws minutes hot-word add --words "钉钉,悟空,DWS"
|
||||
Flags:
|
||||
--words string 热词列表,逗号分隔 (必填)
|
||||
```
|
||||
|
||||
把专有名词/业务术语写入个人热词库,后续听记的语音识别会优先匹配,用来兜底高频误识别。
|
||||
|
||||
### 生成思维导图
|
||||
```
|
||||
Usage:
|
||||
dws minutes mind-graph create [flags]
|
||||
Example:
|
||||
dws minutes mind-graph create --id <taskUuid>
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填)
|
||||
```
|
||||
|
||||
异步任务:提交后返回 jobId,需要用 `mind-graph status` 轮询结果。
|
||||
|
||||
### 查询思维导图状态
|
||||
```
|
||||
Usage:
|
||||
dws minutes mind-graph status [flags]
|
||||
Example:
|
||||
dws minutes mind-graph status --id <taskUuid>
|
||||
Flags:
|
||||
--id string 听记 taskUuid (必填)
|
||||
```
|
||||
|
||||
轮询 `mind-graph create` 的生成任务,就绪后返回思维导图内容。
|
||||
|
||||
### 创建文件上传会话
|
||||
```
|
||||
Usage:
|
||||
dws minutes upload create [flags]
|
||||
Example:
|
||||
dws minutes upload create --file-name "会议录音.m4a" --file-size 10485760 --title "Q2 复盘"
|
||||
Flags:
|
||||
--file-name string 本地文件名 (必填)
|
||||
--file-size string 文件大小,单位字节 (必填)
|
||||
--title string 听记标题 (可选)
|
||||
--template-id string 摘要模板 ID (可选)
|
||||
--input-language string 语言,如 zh_CN / en_US (可选)
|
||||
--enable-message-card string 是否推送消息卡片: true/false (可选)
|
||||
```
|
||||
|
||||
三步上传的第 1 步:返回 sessionId 和预签名 URL,由调用方把本地音视频文件 HTTP PUT 到该 URL。
|
||||
|
||||
### 完成上传并生成听记
|
||||
```
|
||||
Usage:
|
||||
dws minutes upload complete [flags]
|
||||
Example:
|
||||
dws minutes upload complete --session-id <sessionId>
|
||||
Flags:
|
||||
--session-id string upload create 返回的 sessionId (必填)
|
||||
```
|
||||
|
||||
三步上传的第 3 步:通知服务端文件已上传完毕,触发转写与 AI 处理,返回新的 taskUuid。
|
||||
|
||||
### 取消上传
|
||||
```
|
||||
Usage:
|
||||
dws minutes upload cancel [flags]
|
||||
Example:
|
||||
dws minutes upload cancel --session-id <sessionId>
|
||||
Flags:
|
||||
--session-id string upload create 返回的 sessionId (必填)
|
||||
```
|
||||
|
||||
上传中途放弃或上游出错时释放会话,避免残留。
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"我的听记/我创建的听记" → `list mine`(可附加 `--query`、`--start`、`--end` 筛选)
|
||||
用户说"别人给我的听记/共享听记" → `list shared`(可附加 `--query`、`--start`、`--end` 筛选)
|
||||
用户说"有权限的听记/我能访问的听记/所有听记" → `list all`(可附加 `--query`、`--start`、`--end` 筛选)
|
||||
用户说"某时间段内的听记/按时间查听记/按关键词查听记" → 根据所属范围选择 `list mine`/`list shared`/`list all`,附加 `--start`、`--end`、`--query` 参数
|
||||
用户说"听记详情/听记信息" → `get info`
|
||||
用户说"批量看听记/一次查多篇" → `get batch`(`--ids` 传逗号分隔 taskUuid)
|
||||
用户说"摘要/总结/会议纪要" → `get summary`
|
||||
用户说"关键字/关键词" → `get keywords`
|
||||
用户说"原文/转写/录音文字" → `get transcription`
|
||||
用户说"会议待办/听记待办" → `get todos`
|
||||
用户说"改听记标题/重命名听记" → `update title`
|
||||
用户说"改摘要/覆盖摘要/重写总结" → `update summary`
|
||||
用户说"全文替换/批量改转写里的错字/专有名词搞错了" → `replace-text`
|
||||
用户说"改发言人/把说话人1改成张三/认领发言人" → `speaker replace`
|
||||
用户说"加热词/让后续识别更准/把某个专业术语教给系统" → `hot-word add`
|
||||
用户说"生成思维导图/导图/脑图" → `mind-graph create`(异步),再 `mind-graph status` 轮询
|
||||
用户说"上传录音生成听记/把本地音视频变听记" → `upload create` → 本地 PUT → `upload complete`;放弃则 `upload cancel`
|
||||
用户传入听记 URL(如 `https://shanji.dingtalk.com/app/transcribes/xxx`),从 URL 提取 taskUuid,再执行对应的 get/update 操作
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 查看我的听记列表 — 提取 taskUuid
|
||||
dws minutes list mine --format json
|
||||
dws minutes list mine --max 10 --next-token <nextToken> --format json
|
||||
dws minutes list mine --query "周会" --format json
|
||||
|
||||
# 1b. 查看共享给我的听记
|
||||
dws minutes list shared --max 20 --format json
|
||||
dws minutes list shared --query "日报" --format json
|
||||
|
||||
# 1c. 查看我有权限访问的所有听记(支持关键字和时间范围筛选)
|
||||
dws minutes list all --format json
|
||||
dws minutes list all --query "周会" --start "2026-03-01T00:00:00+08:00" --end "2026-03-20T23:59:59+08:00" --format json
|
||||
|
||||
# 2. 获取 AI 摘要
|
||||
dws minutes get summary --id <taskUuid> --format json
|
||||
|
||||
# 3. 查看完整转写原文
|
||||
dws minutes get transcription --id <taskUuid> --format json
|
||||
|
||||
# 4. 提取待办事项
|
||||
dws minutes get todos --id <taskUuid> --format json
|
||||
|
||||
# 5. 批量补齐多个 taskUuid 的详情
|
||||
dws minutes get batch --ids <taskUuid1>,<taskUuid2>,<taskUuid3> --format json
|
||||
|
||||
# 6. 修改标题 / 覆盖摘要
|
||||
dws minutes update title --id <taskUuid> --title "新标题" --format json
|
||||
dws minutes update summary --id <taskUuid> --content "修订后的摘要 Markdown" --format json
|
||||
|
||||
# 7. 修正转写错字 / 发言人标签 / 加热词
|
||||
dws minutes replace-text --id <taskUuid> --search "小钉" --replace "DingTalk" --format json
|
||||
dws minutes speaker replace --id <taskUuid> --from "说话人1" --to "张三" --format json
|
||||
dws minutes hot-word add --words "钉钉,悟空,DWS" --format json
|
||||
|
||||
# 8. 异步生成思维导图(create 拿 jobId,status 轮询)
|
||||
dws minutes mind-graph create --id <taskUuid> --format json
|
||||
dws minutes mind-graph status --id <taskUuid> --format json
|
||||
|
||||
# 9. 从本地音视频上传生成听记(三步走)
|
||||
dws minutes upload create --file-name "会议录音.m4a" --file-size 10485760 --title "Q2 复盘" --format json
|
||||
# → 拿到 sessionId 和预签名 URL,调用方自行 HTTP PUT 把文件推上去
|
||||
dws minutes upload complete --session-id <sessionId> --format json
|
||||
# 放弃上传
|
||||
dws minutes upload cancel --session-id <sessionId> --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `list mine` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token |
|
||||
| `list shared` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token |
|
||||
| `list all` | `taskUuid`、`nextToken` | get/update 的 --id;翻页时 --next-token |
|
||||
| `get batch` | 各听记 `taskUuid` | 进一步查询详情 |
|
||||
| `upload create` | `sessionId`、预签名 URL | 本地 PUT 上传 + `upload complete` / `upload cancel` 的 --session-id |
|
||||
| `upload complete` | 新听记 `taskUuid` | 后续 get/update/mind-graph 的 --id |
|
||||
| `mind-graph create` | 异步 jobId(随 taskUuid 关联) | `mind-graph status` 的 --id 查进度与结果 |
|
||||
|
||||
## 注意事项
|
||||
- `taskUuid` 是听记的唯一标识,所有 get/update/mind-graph/speaker/replace-text 操作均以此为入参
|
||||
- 如果用户传入听记 URL(格式: `https://shanji.dingtalk.com/app/transcribes/<taskUuid>`),直接从路径末段提取 taskUuid 作为 `--id` 参数,无需再调用 list 查询
|
||||
- `list mine`、`list shared`、`list all` 统一走 `list_by_keyword_and_time_range` 链路,通过 `belongingConditionId` 区分(`created` / `shared` / `noLimit`);三者均支持 `--max`、`--next-token` 分页及 `--query`、`--start`、`--end` 筛选
|
||||
- `list mine`、`list shared` 默认每页 20 条,`list all` 默认每页 10 条
|
||||
- `get info` 是单条查询;`get batch` 是批量补齐 ID 列表(`--ids` 逗号分隔),用在"有一串 taskUuid,想一次拿标题/时长/参与人"的场景
|
||||
- `get summary` 返回 AI 生成的结构化 Markdown 摘要;`update summary` 会整体覆盖原摘要,不做增量合并
|
||||
- `get transcription` 的 `--direction` 控制时间排序: 0=正序(默认), 1=倒序
|
||||
- `replace-text` 是系统性纠错:命中即替换,整篇转写与摘要一起改,慎用模糊文本
|
||||
- `speaker replace` 只改"标签",不重跑说话人分离;`--target-uid` 可顺带把该说话人绑定到真实 userId
|
||||
- `hot-word add` 作用于**个人**热词库,只对本人后续的听记识别生效
|
||||
- `mind-graph create` 是异步:调用后立即返回,需要用 `mind-graph status --id <taskUuid>` 轮询到 ready 才能拿到图
|
||||
- 上传流程严格三步:`upload create`(拿 sessionId + 预签名 URL)→ 客户端自己 HTTP PUT 文件 → `upload complete`(触发转写);中途放弃必须用 `upload cancel` 释放会话,不要只丢 sessionId
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [minutes_recent_summary.py](../../scripts/minutes_recent_summary.py) | 获取最近听记的 AI 摘要并合并 | `python minutes_recent_summary.py --max 5` |
|
||||
| [minutes_extract_todos.py](../../scripts/minutes_extract_todos.py) | 从听记中提取待办事项汇总 | `python minutes_extract_todos.py --max 5` |
|
||||
@@ -0,0 +1,199 @@
|
||||
# OA 审批 (oa) 命令参考
|
||||
|
||||
钉钉 OA 审批:待办审批列表 / 查看详情 / 同意 / 拒绝 / 撤销 / 流程记录 / 表单模板。
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 查询待我处理的审批实例
|
||||
```
|
||||
Usage:
|
||||
dws oa approval list-pending [flags]
|
||||
Example:
|
||||
dws oa approval list-pending
|
||||
dws oa approval list-pending --page 1 --size 20
|
||||
dws oa approval list-pending --start "2026-03-01T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00"
|
||||
Flags:
|
||||
--page string 页码 (默认 1)
|
||||
--size string 每页数量 (默认 10)
|
||||
--start string 提交开始时间 ISO-8601 (可选)
|
||||
--end string 提交结束时间 ISO-8601 (可选)
|
||||
```
|
||||
|
||||
"待办收件箱"视图:列出当前用户仍需动作的审批 `processInstanceId`。注意返回的是**实例 ID**,真正驱动 approve/reject 还需要用 `tasks` 再换成 `taskId`。
|
||||
|
||||
### 查询我发起的审批实例
|
||||
```
|
||||
Usage:
|
||||
dws oa approval list-initiated [flags]
|
||||
Example:
|
||||
dws oa approval list-initiated
|
||||
dws oa approval list-initiated --process-code <processCode> --max-results 20
|
||||
dws oa approval list-initiated --start "2026-03-01T00:00:00+08:00" --end "2026-03-31T23:59:59+08:00" --next-token <nextToken>
|
||||
Flags:
|
||||
--process-code string 按指定表单/流程模板过滤 (可选)
|
||||
--start string 发起开始时间 ISO-8601 (可选)
|
||||
--end string 发起结束时间 ISO-8601 (可选)
|
||||
--max-results string 每页条数 (可选)
|
||||
--next-token string 分页 token (首页留空)
|
||||
```
|
||||
|
||||
查询当前用户作为申请人发起的审批,用来查"我报销到哪一步了"、"我之前提的请假单"。
|
||||
|
||||
### 查询可发起的审批表单模板
|
||||
```
|
||||
Usage:
|
||||
dws oa approval list-forms [flags]
|
||||
Example:
|
||||
dws oa approval list-forms
|
||||
dws oa approval list-forms --cursor 0 --size 20
|
||||
Flags:
|
||||
--cursor string 分页游标,首次传 0 (默认 0)
|
||||
--size string 每页大小 (可选)
|
||||
```
|
||||
|
||||
列出当前用户被授权发起的审批表单(如"请假"、"报销"),返回 `processCode`,后续用于提交新的申请或按模板筛选历史实例。
|
||||
|
||||
### 获取审批实例详情
|
||||
```
|
||||
Usage:
|
||||
dws oa approval detail [flags]
|
||||
Example:
|
||||
dws oa approval detail --instance-id <processInstanceId>
|
||||
Flags:
|
||||
--instance-id string 审批实例 processInstanceId (必填)
|
||||
```
|
||||
|
||||
拉取单个实例的完整内容:表单字段、附件、当前状态、参与人。适合让 AI 读懂审批单再决定动作或生成摘要。
|
||||
|
||||
### 查询审批实例的操作记录
|
||||
```
|
||||
Usage:
|
||||
dws oa approval records [flags]
|
||||
Example:
|
||||
dws oa approval records --instance-id <processInstanceId>
|
||||
Flags:
|
||||
--instance-id string 审批实例 processInstanceId (必填)
|
||||
```
|
||||
|
||||
列出实例的流转历史(谁在什么时候同意/加签/转交/评论),用于复盘或解释"这单卡在哪"。
|
||||
|
||||
### 查询实例下待处理的任务
|
||||
```
|
||||
Usage:
|
||||
dws oa approval tasks [flags]
|
||||
Example:
|
||||
dws oa approval tasks --instance-id <processInstanceId>
|
||||
Flags:
|
||||
--instance-id string 审批实例 processInstanceId (必填)
|
||||
```
|
||||
|
||||
针对具体实例拿到当前用户名下的 `taskId`。approve / reject 要的是 `taskId`,不是 `processInstanceId`,这一步不能省。
|
||||
|
||||
### 同意审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval approve [flags]
|
||||
Example:
|
||||
dws oa approval approve --instance-id <processInstanceId> --task-id <taskId>
|
||||
dws oa approval approve --instance-id <processInstanceId> --task-id <taskId> --remark "同意,按流程推进"
|
||||
Flags:
|
||||
--instance-id string 审批实例 processInstanceId (必填)
|
||||
--task-id string 待办任务 taskId (必填),来自 approval tasks
|
||||
--remark string 审批意见 (可选)
|
||||
```
|
||||
|
||||
以当前用户身份同意某个审批任务。`--task-id` 必须是当前用户的待办 taskId,否则 MCP 会拒绝。
|
||||
|
||||
### 拒绝审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval reject [flags]
|
||||
Example:
|
||||
dws oa approval reject --instance-id <processInstanceId> --task-id <taskId> --remark "预算不足,下月再议"
|
||||
Flags:
|
||||
--instance-id string 审批实例 processInstanceId (必填)
|
||||
--task-id string 待办任务 taskId (必填),来自 approval tasks
|
||||
--remark string 拒绝理由 (可选,但强烈建议填)
|
||||
```
|
||||
|
||||
以当前用户身份驳回审批任务;拒绝后一般由 MCP 端决定流程是打回申请人还是整体终止。
|
||||
|
||||
### 撤回已发起的审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval revoke [flags]
|
||||
Example:
|
||||
dws oa approval revoke --instance-id <processInstanceId>
|
||||
dws oa approval revoke --instance-id <processInstanceId> --remark "金额填错,重新提交" --yes
|
||||
Flags:
|
||||
--instance-id string 审批实例 processInstanceId (必填)
|
||||
--remark string 撤回原因 (可选)
|
||||
```
|
||||
|
||||
只能撤销**自己发起且尚未终态**的实例。命令标了 `isSensitive`,AI 调用前建议与用户再确认一次,可配合全局 `--yes` 跳过确认。
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"我要审的单/待我处理的审批/审批收件箱" → `list-pending` 拿 `processInstanceId`
|
||||
用户说"这单我同意/批了它/通过" → `approval tasks` 换 `taskId` → `approval approve`
|
||||
用户说"这单不行/驳回/拒绝" → `approval tasks` 换 `taskId` → `approval reject --remark "<理由>"`
|
||||
用户说"这单详情/看看内容/这单在说什么" → `approval detail`
|
||||
用户说"这单卡在谁那/谁还没审/流程走到哪" → `approval records`
|
||||
用户说"我发起的审批/我的申请" → `list-initiated`(可用 `--process-code` 按模板筛)
|
||||
用户说"能发起哪些审批/有哪些表单" → `list-forms`
|
||||
用户说"撤回我的申请/我不提了" → `approval revoke`(仅限自己发起且未终态)
|
||||
|
||||
关键区分: oa(钉钉 OA 审批流程) vs todo(个人待办) vs report(日志汇报)
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 看我有哪些待审的单 — 提取 processInstanceId
|
||||
dws oa approval list-pending --page 1 --size 20 --format json
|
||||
|
||||
# 2. 看单子内容(表单字段 / 附件 / 当前状态)
|
||||
dws oa approval detail --instance-id <processInstanceId> --format json
|
||||
|
||||
# 3. 想动作(同意/拒绝)前,先拿 taskId
|
||||
dws oa approval tasks --instance-id <processInstanceId> --format json
|
||||
|
||||
# 4. 同意 / 拒绝(taskId 来自步骤 3)
|
||||
dws oa approval approve --instance-id <processInstanceId> --task-id <taskId> --remark "同意" --format json
|
||||
dws oa approval reject --instance-id <processInstanceId> --task-id <taskId> --remark "预算不足" --format json
|
||||
|
||||
# 5. 查看流程记录 — 谁何时做了什么
|
||||
dws oa approval records --instance-id <processInstanceId> --format json
|
||||
|
||||
# 6. 查我发起的审批 / 按模板过滤
|
||||
dws oa approval list-initiated --format json
|
||||
dws oa approval list-initiated --process-code <processCode> --format json
|
||||
|
||||
# 7. 能发起哪些表单
|
||||
dws oa approval list-forms --cursor 0 --size 20 --format json
|
||||
|
||||
# 8. 撤回我发起的单
|
||||
dws oa approval revoke --instance-id <processInstanceId> --remark "填错了" --yes --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `list-pending` | `processInstanceId`、`nextToken` | detail / tasks / records 的 --instance-id |
|
||||
| `list-initiated` | `processInstanceId`、`nextToken` | detail / records / revoke 的 --instance-id |
|
||||
| `list-forms` | `processCode` | `list-initiated --process-code` 按模板过滤,或后续发起新审批 |
|
||||
| `approval tasks` | `taskId` | approve / reject 的 --task-id |
|
||||
| `approval detail` | 表单字段、参与人、当前状态 | 供 AI 决策是否批准、生成摘要 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `processInstanceId`(实例 ID)与 `taskId`(待办任务 ID)**不是一回事**,不能混用:
|
||||
- `list-pending` / `list-initiated` / `detail` / `records` / `revoke` 用 `processInstanceId`
|
||||
- `approve` / `reject` 用 `taskId`,必须先 `approval tasks --instance-id <processInstanceId>` 换一次
|
||||
- `list-pending` 是"需要我动作"视图;同一个实例如果流转到下一节点就会从这里消失
|
||||
- `list-initiated` 是"我发起的"视图;和 `list-pending` 是两套语义,不要互相替代
|
||||
- `--start` / `--end` 使用 ISO-8601 格式,内部会自动转毫秒时间戳
|
||||
- `revoke` 只能撤销**当前用户发起且未完结**的实例;已流转结束、已作废、或不是自己发起的都会失败
|
||||
- `approve` / `reject` 的 `--remark` 可选但强烈建议填,尤其是 `reject`——审批流下游会把理由展示给申请人
|
||||
- 所有敏感动作(`approve` / `reject` / `revoke`)在 AI 调用时应与用户二次确认,可用全局 `--yes` 跳过确认
|
||||
- `list-forms` 返回的 `processCode` 是未来发起新审批、或筛选历史实例的钩子;目前本命令集只覆盖审批的"处理侧",发起新审批需走业务方自己的流程
|
||||
@@ -0,0 +1,164 @@
|
||||
# 日志 (report) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 获取日志模版列表
|
||||
```
|
||||
Usage:
|
||||
dws report template list [flags]
|
||||
Example:
|
||||
dws report template list
|
||||
```
|
||||
|
||||
### 获取日志模版详情
|
||||
```
|
||||
Usage:
|
||||
dws report template detail [flags]
|
||||
Example:
|
||||
dws report template detail --name <templateName>
|
||||
Flags:
|
||||
--name string 模版名称 (必填)
|
||||
```
|
||||
|
||||
### 创建日志
|
||||
```
|
||||
Usage:
|
||||
dws report create [flags]
|
||||
Example:
|
||||
dws report create --template-id <templateId> \
|
||||
--contents '[{"key":"今日完成","sort":"0","content":"完成了需求评审","contentType":"markdown","type":"1"}]' \
|
||||
--format json
|
||||
dws report create --template-id <templateId> \
|
||||
--contents '[{"key":"今日完成","sort":"0","content":"内容","contentType":"markdown","type":"1"}]' \
|
||||
--dd-from "script" --to-chat --to-user-ids "userId1,userId2" --format json
|
||||
Flags:
|
||||
--template-id string 日志模版 ID (必填),从 template list 返回中取
|
||||
--contents string 日志内容 JSON 数组 (必填),每项须含 key/sort/content/contentType/type
|
||||
--dd-from string 创建来源标识 (默认 dws)
|
||||
--to-chat string 是否发送到日志接收人单聊 (传 "true" 发送)
|
||||
--to-user-ids string 接收人 userId,逗号分隔 (可选)
|
||||
```
|
||||
|
||||
**`contents` 数组元素**(与 MCP `create_report` 一致):
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `key` | 控件标题,与 template detail 一致 |
|
||||
| `sort` | 控件排序,与 template detail 一致 |
|
||||
| `content` | 填写值;文本类可写 Markdown |
|
||||
| `contentType` | `type` 为文本 (`1`) 时用 `markdown`,其余类型用 `origin` |
|
||||
| `type` | `1` 文本、`2` 数字、`3` 单选、`5` 日期、`7` 多选 |
|
||||
|
||||
shell 中传 `--contents` 时外层建议用**单引号**包住整段 JSON。
|
||||
|
||||
### 获取日志详情
|
||||
```
|
||||
Usage:
|
||||
dws report detail [flags]
|
||||
Example:
|
||||
dws report detail --report-id <reportId>
|
||||
Flags:
|
||||
--report-id string 日志 ID (必填)
|
||||
```
|
||||
|
||||
### 日志收件箱列表
|
||||
```
|
||||
Usage:
|
||||
dws report list [flags]
|
||||
Example:
|
||||
dws report list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --cursor 0 --size 20
|
||||
Flags:
|
||||
--cursor string 分页游标 (必填)
|
||||
--end string 结束时间 ISO-8601 (如 2026-03-10T23:59:59+08:00) (必填)
|
||||
--size string 每页大小 (必填)
|
||||
--start string 开始时间 ISO-8601 (如 2026-03-10T00:00:00+08:00) (必填)
|
||||
```
|
||||
|
||||
### 获取日志统计数据
|
||||
```
|
||||
Usage:
|
||||
dws report stats [flags]
|
||||
Example:
|
||||
dws report stats --report-id <reportId>
|
||||
Flags:
|
||||
--report-id string 日志 ID (必填)
|
||||
```
|
||||
|
||||
返回该日志的互动聚合数据:浏览数 (views)、点赞数 (likes)、评论数 (comments) 等,用于衡量一篇日志的阅读与反馈情况。
|
||||
|
||||
### 查询当前人创建的日志列表
|
||||
```
|
||||
Usage:
|
||||
dws report sent [flags]
|
||||
Example:
|
||||
dws report sent --cursor 0 --size 20
|
||||
dws report sent --cursor 0 --size 20 --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00"
|
||||
dws report sent --cursor 0 --size 20 --template-name "日报"
|
||||
Flags:
|
||||
--cursor string 分页游标,首次传 0 (默认 0)
|
||||
--size string 每页条数,最大 20 (默认 20)
|
||||
--start string 创建开始时间 ISO-8601 (默认最近 30 天)
|
||||
--end string 创建结束时间 ISO-8601 (默认最近 30 天)
|
||||
--modified-start string 修改开始时间 ISO-8601 (可选)
|
||||
--modified-end string 修改结束时间 ISO-8601 (可选)
|
||||
--template-name string 日志模版名称 (可选,不传查全部)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"查日志/看日报" → `list` 获取列表,再 `detail`
|
||||
用户说"写日报/提交周报/发日志/填日志" → 先 `template list` / `template detail` 取 `templateId` 与各控件 `key`/`sort`/类型,拼 `--contents` JSON,再 `create`
|
||||
用户说"日志数据/互动情况/多少人看了/点赞评论" → `stats`
|
||||
用户说"有什么日志模版" → `template list` 或 `template detail`
|
||||
用户说"我发过的日志/我创建的日志" → `sent`
|
||||
|
||||
关键区分: report(钉钉日志模版汇报,含创建) vs doc(文档编辑) vs todo(待办任务)
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 获取当前用户可用的日志模版
|
||||
dws report template list --format json
|
||||
|
||||
# 2. 按名称查看模版详情(含字段定义)
|
||||
dws report template detail --name "日报" --format json
|
||||
|
||||
# 2b. 创建日志(从步骤 1/2 取 templateId 与 contents 字段)
|
||||
dws report create --template-id <templateId> \
|
||||
--contents '[{"key":"今日完成","sort":"0","content":"完成了需求评审","contentType":"markdown","type":"1"}]' \
|
||||
--format json
|
||||
|
||||
# 3. 查看收件箱日志列表 — 提取 reportId
|
||||
dws report list --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" \
|
||||
--cursor 0 --size 20 --format json
|
||||
|
||||
# 4. 查看日志详情
|
||||
dws report detail --report-id <reportId> --format json
|
||||
|
||||
# 5. 查看日志统计(浏览/点赞/评论聚合)
|
||||
dws report stats --report-id <reportId> --format json
|
||||
|
||||
# 6. 查看当前人创建的日志列表
|
||||
dws report sent --cursor 0 --size 20 --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `template list` | template 名称 | template detail 的 --name |
|
||||
| `template list` | `templateId`(或等价字段) | `create` 的 --template-id |
|
||||
| `template detail` | 各控件 `key`、`sort`、类型 | 拼 `create` 的 --contents JSON(含 contentType/type) |
|
||||
| `list` | `reportId` | detail / stats 的 --report-id |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `--start` / `--end` 使用 ISO-8601 格式(如 `2026-03-10T00:00:00+08:00`)
|
||||
- `template list` 不需要参数,直接返回当前用户可用的所有日志模版
|
||||
- `create` 前必须先查模版,勿猜测 `templateId` 或 `contents` 中的 `key`/`sort`;多控件时数组须覆盖模版必填项
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [report_inbox_today.py](../../scripts/report_inbox_today.py) | 查看今天收到的日志列表及详情 | `python report_inbox_today.py` |
|
||||
@@ -0,0 +1,304 @@
|
||||
# 在线电子表格 (sheet) 命令参考
|
||||
|
||||
## 适用范围(重要)
|
||||
|
||||
`sheet` 产品**仅支持钉钉在线电子表格**(`contentType=ALIDOC`、`extension=axls`),**不支持**上传的 `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件。
|
||||
|
||||
| 文件类型 | 处理方式 |
|
||||
|---------|---------|
|
||||
| 在线电子表格(`axls`) | 走 `sheet` 全部命令(读/写/筛选/合并/导出等服务端原子操作) |
|
||||
| `xlsx` / `xls` / `xlsm` / `csv` 等本地表格文件 | 必须用 `dws doc download --node <ID> --output <路径>` 先下载到本地再用本地工具解析,**禁止**调用任何 `sheet` 子命令 |
|
||||
| 想把在线表格导出为 xlsx | 用 `dws sheet submit_export_job` 提交导出任务 → 拿到 `jobId` → `dws sheet query_export_job` 轮询直到 `finished` → 用返回的 `downloadUrl` 下载 |
|
||||
|
||||
> 用户直接粘贴 `alidocs` URL 时,先用 `dws doc info --node <URL> --format json` 确认 `contentType=ALIDOC` 且 `extension=axls` 后再走 `sheet`;否则转 `dws doc download`。
|
||||
|
||||
## 命名风格说明(v1.0.25 envelope 现状)
|
||||
|
||||
`sheet` 产品的命令 cli_name **当前同时存在两种风格**——这是 envelope schema 还在演进中、`CLIAliases` (#246) 重命名尚未完成的过渡态:
|
||||
|
||||
- **kebab-case** (~17 个):`add-dimension`、`merge-cells`、`filter-view update-criteria` 等,与 dws 其它产品风格一致
|
||||
- **snake_case** (~12 个):`copy_sheet`、`submit_export_job`、`set_filter_criteria` 等,envelope 原始名直透出来
|
||||
|
||||
**调用时以 `dws sheet --help` 输出为准**——本文档与 envelope schema 同步,未来命名收敛后会同步更新。所有命令的最终参数名以 `dws sheet <cmd> --help` 和 `dws schema sheet.<canonical_path>` 为准。
|
||||
|
||||
## 命令总览(按功能分组)
|
||||
|
||||
### 工作表 (Worksheet) 级
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet create` | `sheet.create_workspace_sheet` | 在知识库中创建一个新的钉钉表格文档 |
|
||||
| `dws sheet new` | `sheet.create_sheet` | 在已有钉钉表格文档中新建一张工作表 |
|
||||
| `dws sheet list` | `sheet.get_all_sheets` | 列出指定文档的所有工作表 |
|
||||
| `dws sheet info` | `sheet.get_sheet` | 获取指定工作表详情 |
|
||||
| `dws sheet copy_sheet` | `sheet.copy_sheet` | 复制工作表(同文档内) |
|
||||
| `dws sheet update_sheet` | `sheet.update_sheet` | 更新工作表元信息(如改名) |
|
||||
|
||||
### 区域 (Range) 读写
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet range read` | `sheet.get_range` | 读取指定区域的单元格内容 |
|
||||
| `dws sheet range update` | `sheet.update_range` | 写入/更新指定区域的单元格 |
|
||||
| `dws sheet append` | `sheet.append_rows` | 在工作表末尾追加若干行 |
|
||||
|
||||
### 行列 (Dimension)
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet add-dimension` | `sheet.add_dimension` | 在末尾追加空行或空列 |
|
||||
| `dws sheet insert-dimension` | `sheet.insert_dimension` | 在指定位置插入空行/空列 |
|
||||
| `dws sheet delete-dimension` | `sheet.delete_dimension` | 删除指定位置起的若干行/列 |
|
||||
| `dws sheet move-dimension` | `sheet.move_dimension` | 移动行/列到指定位置 |
|
||||
| `dws sheet update-dimension` | `sheet.update_dimension` | 更新行/列属性(显隐、行高/列宽) |
|
||||
|
||||
### 单元格合并
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet merge-cells` | `sheet.merge_cells` | 合并指定范围的单元格(`mergeAll`/`mergeRows`/`mergeColumns`) |
|
||||
| `dws sheet unmerge-cells` | `sheet.unmerge_range` | 取消指定范围的合并 |
|
||||
|
||||
### 查找/替换
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet find` | `sheet.find_cells` | 在工作表中搜索单元格内容(支持正则/整格匹配/隐藏) |
|
||||
| `dws sheet replace` | `sheet.replace_all` | 全局查找替换 |
|
||||
|
||||
### 筛选视图 (Filter View) — 命名视图、按列条件、不影响表本身
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet filter-view create` | `sheet.create_filter_view` | 创建筛选视图 |
|
||||
| `dws sheet filter-view list` | `sheet.get_filter_views` | 列出工作表的所有筛选视图 |
|
||||
| `dws sheet filter-view update` | `sheet.update_filter_view` | 更新筛选视图(名称/范围/条件) |
|
||||
| `dws sheet filter-view delete` | `sheet.delete_filter_view` | 删除整个筛选视图 |
|
||||
| `dws sheet filter-view update-criteria` | `sheet.set_filter_view_criteria` | 设置/更新视图内某列的筛选条件 |
|
||||
| `dws sheet filter-view delete-criteria` | `sheet.clear_filter_view_criteria` | 清除视图内某列的筛选条件 |
|
||||
|
||||
### 表级筛选 (Filter) — 直接作用于工作表本身的临时筛选
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet create_filter` | `sheet.create_filter` | 在工作表上创建筛选器 |
|
||||
| `dws sheet get_filter` | `sheet.get_filter` | 获取当前筛选器配置 |
|
||||
| `dws sheet update_filter` | `sheet.update_filter` | 更新筛选器条件 |
|
||||
| `dws sheet delete_filter` | `sheet.delete_filter` | 删除筛选器 |
|
||||
| `dws sheet set_filter_criteria` | `sheet.set_filter_criteria` | 设置某列的筛选条件 |
|
||||
| `dws sheet clear_filter_criteria` | `sheet.clear_filter_criteria` | 清除某列的筛选条件 |
|
||||
| `dws sheet sort_filter` | `sheet.sort_filter` | 对筛选范围按指定列排序 |
|
||||
|
||||
### 图片
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet write-image` | `sheet.write_image` | 将已上传的图片资源写入指定单元格 |
|
||||
|
||||
### 导出(两步原子,**v1.0.25 没有合并的 `export` 命令**)
|
||||
|
||||
| 命令 | canonical | 用途 |
|
||||
|------|-----------|------|
|
||||
| `dws sheet submit_export_job` | `sheet.submit_export_job` | 提交导出任务,返回 `jobId` |
|
||||
| `dws sheet query_export_job` | `sheet.query_export_job` | 轮询导出任务状态,完成后返回 `downloadUrl` |
|
||||
|
||||
> v1.0.25 envelope 暴露的是这两个**原子动作**。要完整完成"导出 → 下载"流程需要 client 侧轮询 + 调 `downloadUrl`。`CLIToolOverride.Pipeline` (#247) 提供了底层编排能力,但**当前 envelope 还没把这两个动作 Pipeline 化成一条 `dws sheet export` 总命令**。
|
||||
|
||||
## 通用必填参数
|
||||
|
||||
绝大多数 `sheet` 命令都需要:
|
||||
|
||||
- `--node <NODE_ID>` —— 钉钉表格文档的 nodeId 或 `https://alidocs.dingtalk.com/i/nodes/<DOC_UUID>` URL(alias 来自 `nodeId`)
|
||||
- `--sheet-id <SHEET_ID>` —— 工作表 ID(alias 来自 `sheetId`),从 `dws sheet list` 拿
|
||||
|
||||
例外:
|
||||
- `create` 只需要 `--name`(在知识库创建文档时不需要 nodeId)
|
||||
- `list` / `info` / `range read` 只需要 `--node`
|
||||
- `submit_export_job` 只需要 `--node` + `--export-format`(无 sheet-id)
|
||||
- `query_export_job` 只需要 `--job-id`
|
||||
|
||||
## 常用命令示例
|
||||
|
||||
### 创建文档 + 新建工作表
|
||||
|
||||
```bash
|
||||
# 在知识库下创建一个钉钉表格文档
|
||||
dws sheet create --name "销售数据" --workspace <WS_ID> --format json
|
||||
# 返回的 nodeId 用于后续操作
|
||||
|
||||
# 在已有文档中新建一张工作表
|
||||
dws sheet new --node <NODE_ID> --name "Q1 数据" --format json
|
||||
```
|
||||
|
||||
### 读写区域
|
||||
|
||||
```bash
|
||||
# 读 A1:D10
|
||||
dws sheet range read --node <NODE_ID> --sheet-id <SHEET_ID> --range "A1:D10" --format json
|
||||
|
||||
# 写入 5x4 区域(values 是二维 JSON 数组)
|
||||
dws sheet range update --node <NODE_ID> --sheet-id <SHEET_ID> --range "A1:D5" \
|
||||
--values '[["姓名","岗位","入职","薪资"],["张三","研发","2024-01","30000"]]' \
|
||||
--format json
|
||||
|
||||
# 追加行
|
||||
dws sheet append --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--values '[["李四","产品","2025-03","28000"]]' \
|
||||
--format json
|
||||
```
|
||||
|
||||
### 行列操作
|
||||
|
||||
```bash
|
||||
# 在第 5 行处插入 2 个空行
|
||||
dws sheet insert-dimension --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--dimension rows --position 4 --length 2 --format json
|
||||
|
||||
# 末尾追加 3 列
|
||||
dws sheet add-dimension --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--dimension columns --length 3 --format json
|
||||
|
||||
# 删除第 10-12 行
|
||||
dws sheet delete-dimension --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--dimension rows --position 9 --length 3 --format json
|
||||
|
||||
# 隐藏 B 列(startIndex=1, length=1)
|
||||
dws sheet update-dimension --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--dimension columns --start-index 1 --length 1 --hidden true --format json
|
||||
```
|
||||
|
||||
### 合并/取消合并
|
||||
|
||||
```bash
|
||||
# 合并 A1:C1
|
||||
dws sheet merge-cells --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--range "A1:C1" --merge-type mergeAll --format json
|
||||
|
||||
# 取消合并
|
||||
dws sheet unmerge-cells --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--range "A1:C1" --format json
|
||||
```
|
||||
|
||||
### 查找/替换
|
||||
|
||||
```bash
|
||||
# 查找
|
||||
dws sheet find --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--find "TODO" --use-regexp false --match-case false --format json
|
||||
|
||||
# 全局替换
|
||||
dws sheet replace --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--find "TODO" --replacement "DONE" --format json
|
||||
```
|
||||
|
||||
### 筛选视图(推荐:可命名、不破坏原表)
|
||||
|
||||
```bash
|
||||
# 创建筛选视图(范围必须包含表头行)
|
||||
dws sheet filter-view create --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--name "未完成项" --range "A1:E100" --format json
|
||||
# 返回的 filterViewId 用于后续 update/delete/criteria 操作
|
||||
|
||||
# 列出所有筛选视图
|
||||
dws sheet filter-view list --node <NODE_ID> --sheet-id <SHEET_ID> --format json
|
||||
|
||||
# 给视图的某一列设置筛选条件(column 是相对视图范围首列的 0-based 偏移)
|
||||
dws sheet filter-view update-criteria --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--filter-view-id <FV_ID> --column 2 \
|
||||
--filter-criteria '{"conditions":[{"type":"TEXT_CONTAINS","values":["pending"]}]}' \
|
||||
--format json
|
||||
|
||||
# 清除某列的筛选条件(不删除视图本身)
|
||||
dws sheet filter-view delete-criteria --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--filter-view-id <FV_ID> --column 2 --format json
|
||||
|
||||
# 删除整个筛选视图
|
||||
dws sheet filter-view delete --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--filter-view-id <FV_ID> --format json
|
||||
```
|
||||
|
||||
### 表级筛选(snake_case 系列,直接作用于工作表本身)
|
||||
|
||||
```bash
|
||||
# 创建筛选器(一张表只有一个,再次 create 会替换)
|
||||
dws sheet create_filter --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--range "A1:E100" --format json
|
||||
|
||||
# 给某列加筛选条件
|
||||
dws sheet set_filter_criteria --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--column 0 --filter-criteria '{...}' --format json
|
||||
|
||||
# 按指定列排序
|
||||
dws sheet sort_filter --node <NODE_ID> --sheet-id <SHEET_ID> --field 0 --format json
|
||||
|
||||
# 删除筛选器
|
||||
dws sheet delete_filter --node <NODE_ID> --sheet-id <SHEET_ID> --format json
|
||||
```
|
||||
|
||||
### 复制工作表
|
||||
|
||||
```bash
|
||||
dws sheet copy_sheet --node <NODE_ID> --sheet-id <SHEET_ID> --format json
|
||||
# 返回新工作表 ID
|
||||
```
|
||||
|
||||
### 写入图片
|
||||
|
||||
```bash
|
||||
# 已有图片资源 ID 和 URL(通过 drive 或 doc 上传得到)后写入单元格
|
||||
dws sheet write-image --node <NODE_ID> --sheet-id <SHEET_ID> \
|
||||
--range "B2:B2" --resource-id <RES_ID> --resource-url <RES_URL> \
|
||||
--width 200 --height 100 --format json
|
||||
```
|
||||
|
||||
### 导出 xlsx(两步流程)
|
||||
|
||||
```bash
|
||||
# Step 1: 提交导出任务
|
||||
JOB=$(dws sheet submit_export_job --node <NODE_ID> --export-format xlsx --format json --jq '.result.jobId')
|
||||
|
||||
# Step 2: 轮询任务状态(建议 sleep + 重试)
|
||||
dws sheet query_export_job --job-id "$JOB" --format json
|
||||
# 直到返回 status=finished + downloadUrl
|
||||
|
||||
# Step 3: 下载(用 curl / dws doc download / 其它工具拉 downloadUrl)
|
||||
```
|
||||
|
||||
## 易混淆点
|
||||
|
||||
| 区分 | 说明 |
|
||||
|---|---|
|
||||
| `dws sheet create` vs `dws sheet new` | `create` 在知识库**新建一个文档**(返回新 nodeId);`new` 在**已有文档中新建一张工作表**(需 nodeId) |
|
||||
| `dws sheet filter-view *` vs `dws sheet create_filter`/`set_filter_criteria` 等 | filter-view 是**命名视图**,多个并存、不影响表本身;filter 是**表级唯一**筛选器,直接作用于工作表显示 |
|
||||
| `filter-view update-criteria` vs `filter-view delete-criteria` | update 是设置/覆盖列条件;delete 是清除列条件(视图本身保留);要删整个视图用 `filter-view delete` |
|
||||
| `dws sheet submit_export_job` + `query_export_job` vs `dws sheet export` | 后者**不存在**于 v1.0.25 envelope。需 client 端自己轮询,或基于 Pipeline (#247) 在 envelope 侧 PR 一条总命令 |
|
||||
| `dws sheet write-image` vs `range update` | write-image 写入图片(需 resourceId + resourceUrl);range update 写入文本/数字/公式 |
|
||||
| `range update` vs `append` | range update 指定区域覆盖;append 在末尾追加行 |
|
||||
| online axls vs 本地 xlsx | sheet 全部命令只认 axls;本地 xlsx 必须先 `doc download` 再用本地工具解析 |
|
||||
|
||||
## 危险操作(必须先向用户确认)
|
||||
|
||||
| 命令 | 风险 |
|
||||
|---|---|
|
||||
| `delete-dimension` | 删除行/列(含数据),不可恢复 |
|
||||
| `filter-view delete` | 删除整个筛选视图 |
|
||||
| `delete_filter` | 删除表级筛选器 |
|
||||
| `replace` | 全局替换可能影响大量单元格 |
|
||||
| `unmerge-cells` | 取消合并可能丢失部分单元格内容(钉钉行为依赖合并模式) |
|
||||
| `update_sheet` | 更新工作表元信息(如改名) |
|
||||
|
||||
执行前先 `--dry-run` 预览,并向用户展示操作摘要 + 拿到明确同意,再加 `--yes` 提交。
|
||||
|
||||
## 何时**不要**用 sheet
|
||||
|
||||
- 用户给的是 `xlsx` / `xls` / `xlsm` / `csv` 本地文件 → 用 `dws doc download` 下载后本地解析
|
||||
- 用户给的是 AI 表格(不是在线电子表格)→ 用 `dws aitable record query` 等
|
||||
- 用户给的是富文本/普通文档 → 用 `dws doc read`
|
||||
|
||||
## 权威参考
|
||||
|
||||
- 列出所有 sheet 工具:`dws schema | jq '.products[] | select(.id=="sheet") | .tools[] | "\(.group) \(.cli_name)"' -r`
|
||||
- 看某个命令的完整 JSON Schema:`dws schema sheet.<canonical_path>`(如 `dws schema sheet.update_range`)
|
||||
- 看某个命令的 flag 别名映射:`dws schema sheet.<canonical_path> --jq '.tool.flag_overlay'`
|
||||
- 看必填字段:`dws schema sheet.<canonical_path> --jq '.tool.required'`
|
||||
- 命令的人读视图:`dws sheet <cmd> --help`
|
||||
@@ -0,0 +1,117 @@
|
||||
# 单命令产品合集
|
||||
|
||||
以下产品命令较少,合并参考。
|
||||
|
||||
---
|
||||
|
||||
## devdoc — 开放平台文档
|
||||
|
||||
### 搜索开放平台文档
|
||||
```
|
||||
Usage:
|
||||
dws devdoc article search [flags]
|
||||
Example:
|
||||
dws devdoc article search --keyword "OAuth2 接入" --page 1 --size 10
|
||||
Flags:
|
||||
--keyword string 搜索关键词 (必填)
|
||||
--page string 页码 (默认 1)
|
||||
--size string 每页数量 (默认 10)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## oa — 审批
|
||||
|
||||
### 查询可见审批流程
|
||||
```
|
||||
Usage:
|
||||
dws oa approval list-forms [flags]
|
||||
Example:
|
||||
dws oa approval list-forms --format json
|
||||
```
|
||||
|
||||
### 查询审批实例详情
|
||||
```
|
||||
Usage:
|
||||
dws oa approval detail --instance-id <ID> [flags]
|
||||
Example:
|
||||
dws oa approval detail --instance-id <ID> --format json
|
||||
```
|
||||
|
||||
### 查询审批记录
|
||||
```
|
||||
Usage:
|
||||
dws oa approval records --instance-id <ID> [flags]
|
||||
Example:
|
||||
dws oa approval records --instance-id <ID> --format json
|
||||
```
|
||||
|
||||
### 查询待我审批的任务
|
||||
```
|
||||
Usage:
|
||||
dws oa approval tasks [flags]
|
||||
Example:
|
||||
dws oa approval tasks --format json
|
||||
```
|
||||
|
||||
### 查询待我处理的审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval list-pending [flags]
|
||||
Example:
|
||||
dws oa approval list-pending --format json
|
||||
```
|
||||
|
||||
### 查询我发起的审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval list-initiated [flags]
|
||||
Example:
|
||||
dws oa approval list-initiated --format json
|
||||
```
|
||||
|
||||
### 同意审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval approve --instance-id <ID> --task-id <TASK_ID> [flags]
|
||||
Example:
|
||||
dws oa approval approve --instance-id <ID> --task-id <TASK_ID> --format json
|
||||
```
|
||||
|
||||
### 拒绝审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval reject --instance-id <ID> --task-id <TASK_ID> [flags]
|
||||
Example:
|
||||
dws oa approval reject --instance-id <ID> --task-id <TASK_ID> --remark "不符合要求" --format json
|
||||
```
|
||||
|
||||
### 撤销审批
|
||||
```
|
||||
Usage:
|
||||
dws oa approval revoke --instance-id <ID> [flags]
|
||||
Example:
|
||||
dws oa approval revoke --instance-id <ID> --format json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 意图判断
|
||||
|
||||
- 用户说"开发文档/API 文档/接口文档" → `devdoc article search`
|
||||
- 用户说"审批/请假/报销/出差" → `oa approval`
|
||||
- 用户说"同意审批/批准" → `oa approval approve`
|
||||
- 用户说"拒绝审批/驳回" → `oa approval reject`
|
||||
- 用户说"撤销审批/撤回" → `oa approval revoke`
|
||||
- 用户说"待我审批/我要审批的" → `oa approval list-pending` 或 `oa approval tasks`
|
||||
- 用户说"我发起的审批" → `oa approval list-initiated`
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `devdoc article search` | 文档链接 | 直接展示给用户 |
|
||||
| `oa approval list-forms` | processCode | detail / records 等 |
|
||||
| `oa approval tasks` | taskId, instanceId | approve / reject |
|
||||
| `oa approval list-pending` | instanceId | detail / approve / reject |
|
||||
| `oa approval list-initiated` | instanceId | detail / revoke |
|
||||
@@ -0,0 +1,143 @@
|
||||
# 待办 (todo) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 创建待办
|
||||
```
|
||||
Usage:
|
||||
dws todo task create [flags]
|
||||
Example:
|
||||
dws todo task create --title "修复线上Bug" --executors <USER_ID_1>,<USER_ID_2> --priority 40
|
||||
dws todo task create --title "提交报告" --executors <USER_ID> --due "2026-03-20T10:00:00+08:00"
|
||||
dws todo task create --title "每日站会" --executors <USER_ID> \
|
||||
--due "2026-03-20T10:00:00+08:00" \
|
||||
--recurrence $'DTSTART:20260320T020000Z\nRRULE:FREQ=DAILY;INTERVAL=1'
|
||||
Flags:
|
||||
--due string 截止时间 ISO-8601 (如 2026-03-10T18:00:00+08:00)
|
||||
--executors string 执行者 userId 列表 (必填)
|
||||
--priority string 优先级: 10低/20普通/30较高/40紧急
|
||||
--recurrence string 循环规则 RFC 5545 (DTSTART+RRULE,仅在同时设置 --due 时生效)
|
||||
--title string 待办标题 (必填)
|
||||
```
|
||||
|
||||
### 查询待办列表
|
||||
```
|
||||
Usage:
|
||||
dws todo task list [flags]
|
||||
Example:
|
||||
dws todo task list --page 1 --size 20 --status false
|
||||
Flags:
|
||||
--page string 页码 (默认 1)
|
||||
--size string 每页数量 (默认 20)
|
||||
--status string true=已完成, false=未完成
|
||||
```
|
||||
|
||||
### 修改待办任务
|
||||
```
|
||||
Usage:
|
||||
dws todo task update [flags]
|
||||
Example:
|
||||
dws todo task update --task-id <taskId> --title "新标题"
|
||||
dws todo task update --task-id <taskId> --priority 40 --due "2026-03-10T18:00:00+08:00"
|
||||
dws todo task update --task-id <taskId> --done true
|
||||
Flags:
|
||||
--done string 完成状态: true/false
|
||||
--due string 截止时间 ISO-8601 (如 2026-03-10T18:00:00+08:00)
|
||||
--priority string 优先级: 10低/20普通/30较高/40紧急
|
||||
--task-id string 待办任务 ID (必填)
|
||||
--title string 新标题
|
||||
```
|
||||
|
||||
### 修改执行者的待办完成状态
|
||||
```
|
||||
Usage:
|
||||
dws todo task done [flags]
|
||||
Example:
|
||||
dws todo task done --task-id <taskId> --status true
|
||||
dws todo task done --task-id <taskId> --status false
|
||||
Flags:
|
||||
--status string 完成状态: true=已完成, false=未完成 (必填)
|
||||
--task-id string 待办任务 ID (必填)
|
||||
```
|
||||
|
||||
### 待办详情
|
||||
```
|
||||
Usage:
|
||||
dws todo task get [flags]
|
||||
Example:
|
||||
dws todo task get --task-id <taskId>
|
||||
Flags:
|
||||
--task-id string 待办任务 ID (必填)
|
||||
```
|
||||
|
||||
### 删除待办
|
||||
```
|
||||
Usage:
|
||||
dws todo task delete [flags]
|
||||
Example:
|
||||
dws todo task delete --task-id <taskId>
|
||||
dws todo task delete --task-id <taskId> --yes
|
||||
Flags:
|
||||
--task-id string 待办任务 ID (必填)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"加个待办/记一下/TODO" → `task create`
|
||||
用户说"看看待办/我有啥要做" → `task list`
|
||||
用户说"改个待办/修改待办标题/改优先级" → `task update`
|
||||
用户说"做完了/完成待办/标记完成" → `task done`
|
||||
用户说"看看待办详情" → `task get`
|
||||
用户说"删除待办/取消待办" → `task delete`
|
||||
|
||||
关键区分: todo(个人待办)
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 1. 创建待办 — 提取 todoTaskId
|
||||
dws todo task create --title "修复线上Bug" --executors userId1,userId2 \
|
||||
--priority 40 --due "2026-03-10T18:00:00+08:00" --format json
|
||||
|
||||
# 2. 查看未完成待办
|
||||
dws todo task list --page 1 --size 20 --status false --format json
|
||||
|
||||
# 3. 查看待办详情
|
||||
dws todo task get --task-id <taskId> --format json
|
||||
|
||||
# 4. 修改待办信息
|
||||
dws todo task update --task-id <taskId> --title "新标题" --priority 40 --format json
|
||||
|
||||
# 5. 标记待办完成
|
||||
dws todo task done --task-id <taskId> --status true --format json
|
||||
|
||||
# 6. 删除待办
|
||||
dws todo task delete --task-id <taskId> --yes --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `task create` | `todoTaskId` | update/done/get/delete 的 --task-id |
|
||||
| `task list` | `result[].id` | update/done/get/delete 的 --task-id |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 优先级值: 10=低, 20=普通, 30=较高, 40=紧急
|
||||
- `--due` 截止时间使用 ISO-8601 格式(如 2026-03-10T18:00:00+08:00)
|
||||
- `--recurrence` 为 RFC 5545 循环规则(`DTSTART:...\nRRULE:FREQ=DAILY;INTERVAL=1` 这种格式),必须与 `--due` 同时设置才生效
|
||||
- `task list` 的 `--status` 对应 MCP `get_user_todos_in_current_org` 的 `todoStatus` 参数
|
||||
- todo 是个人待办管理产品
|
||||
- `task update` 可同时修改标题/优先级/截止时间/完成状态
|
||||
- `task done` 专用于修改执行者的完成状态,与 `task update --done` 作用不同
|
||||
- `task delete` 为不可逆操作,建议加 `--yes` 并与用户确认
|
||||
|
||||
## 自动化脚本
|
||||
|
||||
| 脚本 | 场景 | 用法 |
|
||||
|------|------|------|
|
||||
| [todo_daily_summary.py](../../scripts/todo_daily_summary.py) | 查看今天/明天/本周未完成待办汇总 | `python todo_daily_summary.py today` |
|
||||
| [todo_batch_create.py](../../scripts/todo_batch_create.py) | 从 JSON 文件批量创建待办 | `python todo_batch_create.py todos.json` |
|
||||
| [todo_overdue_check.py](../../scripts/todo_overdue_check.py) | 扫描逾期待办输出逾期清单 | `python todo_overdue_check.py` |
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
# 知识库 (wiki) 命令参考
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 创建知识库
|
||||
```
|
||||
Usage:
|
||||
dws wiki space create [flags]
|
||||
Example:
|
||||
dws wiki space create --name "产品文档库" --format json
|
||||
dws wiki space create --name "技术方案" --description "团队技术方案归档" --format json
|
||||
Flags:
|
||||
--name string 知识库名称 (必填,不超过 100 字符)
|
||||
--description string 知识库描述 (选填,不超过 500 字符)
|
||||
--icon string 知识库图标标识 (选填)
|
||||
```
|
||||
|
||||
### 查看知识库详情
|
||||
```
|
||||
Usage:
|
||||
dws wiki space get [flags]
|
||||
Example:
|
||||
dws wiki space get --id <workspaceId> --format json
|
||||
dws wiki space get --id "https://alidocs.dingtalk.com/i/spaces/xxx/overview" --format json
|
||||
Flags:
|
||||
--id string 知识库 ID 或 URL (必填)
|
||||
```
|
||||
|
||||
支持传入知识库 ID 或知识库 URL,系统自动识别。
|
||||
知识库 URL 格式:`https://alidocs.dingtalk.com/i/spaces/{workspaceId}/overview`
|
||||
|
||||
### 列出知识库
|
||||
```
|
||||
Usage:
|
||||
dws wiki space list [flags]
|
||||
Example:
|
||||
dws wiki space list --format json
|
||||
dws wiki space list --type myWikiSpace --format json
|
||||
dws wiki space list --type orgWikiSpace --limit 50 --format json
|
||||
Flags:
|
||||
--type string 知识库类型: myWikiSpace / orgWikiSpace (默认 orgWikiSpace)
|
||||
--limit string 每页数量 1-50 (默认 20)
|
||||
--page-token string 分页游标 (首页留空)
|
||||
```
|
||||
|
||||
- `myWikiSpace`:返回当前用户的「我的文档」个人空间(固定 1 条,不支持分页)
|
||||
- `orgWikiSpace`(默认):返回组织内有权访问的知识库列表,支持分页
|
||||
|
||||
### 搜索知识库
|
||||
```
|
||||
Usage:
|
||||
dws wiki space search [flags]
|
||||
Example:
|
||||
dws wiki space search --keyword "产品文档" --format json
|
||||
dws wiki space search --keyword "技术方案" --limit 20 --format json
|
||||
dws wiki space search --type myWikiSpace --format json
|
||||
Flags:
|
||||
--keyword string 搜索关键词 (--type myWikiSpace 时可省略)
|
||||
--type string 知识库类型: myWikiSpace 时直接返回「我的文档」,省略则搜索组织知识库
|
||||
--limit string 返回数量 1-20 (默认 10)
|
||||
```
|
||||
|
||||
当 `--type myWikiSpace` 时,忽略 `--keyword`,直接返回「我的文档」个人空间。
|
||||
|
||||
### 添加知识库成员(容器级授权)
|
||||
```
|
||||
Usage:
|
||||
dws wiki member add [flags]
|
||||
Example:
|
||||
dws wiki member add --space <WS_ID> --user uid1 --role READER
|
||||
dws wiki member add --space <WS_ID> --user uid1,uid2 --role EDITOR
|
||||
dws wiki member add --space "https://alidocs.dingtalk.com/i/spaces/<WS_ID>/overview" --user uid1 --role MANAGER
|
||||
Flags:
|
||||
--space string 目标知识库 ID 或 URL (必填)
|
||||
--user strings 被加入的用户 userId 列表,逗号分隔 (必填,单次最多 30 个)
|
||||
--role string 授予的角色 (必填,大小写敏感,必须全大写): MANAGER (管理者) / EDITOR (可编辑) / DOWNLOADER (可下载) / READER (可阅读)
|
||||
```
|
||||
|
||||
> **❗ 重要约束**:
|
||||
> - 仅支持 USER 类型。
|
||||
> - 角色枚举严格大写:MANAGER / EDITOR / DOWNLOADER / READER(OWNER 不可通过此接口添加,知识库创建者默认为所有者)。
|
||||
> - 操作者需具备知识库的 OWNER 或 MANAGER 权限。
|
||||
> - 「我的文档」(myWikiSpace) 是个人空间,**不支持容器级成员管理**;后端会直接拒绝。如果你的目标只是把某篇文档分享给别人,请改用 `dws doc permission add` 在节点级别授权。
|
||||
|
||||
### 修改知识库成员角色
|
||||
```
|
||||
Usage:
|
||||
dws wiki member update [flags]
|
||||
Example:
|
||||
dws wiki member update --space <WS_ID> --user uid1 --role EDITOR
|
||||
dws wiki member update --space <WS_ID> --user uid1,uid2 --role READER
|
||||
Flags:
|
||||
--space string 目标知识库 ID 或 URL (必填)
|
||||
--user strings 目标用户 userId 列表,逗号分隔 (必填,单次最多 30 个)
|
||||
--role string 新角色 (必填,大小写敏感,必须全大写): MANAGER / EDITOR / DOWNLOADER / READER
|
||||
```
|
||||
|
||||
### 列出知识库成员
|
||||
```
|
||||
Usage:
|
||||
dws wiki member list [flags]
|
||||
Example:
|
||||
dws wiki member list --space <WS_ID>
|
||||
dws wiki member list --space <WS_ID> --max-results 100
|
||||
dws wiki member list --space <WS_ID> --filter-role EDITOR
|
||||
Flags:
|
||||
--space string 目标知识库 ID 或 URL (必填)
|
||||
--max-results int 返回数量上限,最大 200 (默认 50)
|
||||
--filter-role string 按角色过滤: MANAGER / EDITOR / DOWNLOADER / READER (选填)
|
||||
```
|
||||
|
||||
> 接口不支持游标分页,使用 `--max-results` 一次性拉取。
|
||||
|
||||
## 意图判断
|
||||
|
||||
- 用户说"创建知识库/新建知识库" → `space create`
|
||||
- 用户说"查看知识库/知识库详情" → `space get`
|
||||
- 用户说"我的知识库/知识库列表/有哪些知识库" → `space list`
|
||||
- 用户说"搜索知识库/找知识库" → `space search`
|
||||
- 用户说"我的文档/个人空间" → `space search --type myWikiSpace` 或 `space list --type myWikiSpace`
|
||||
- 用户说"把知识库分享给某人/给某人加入知识库/邀请进知识库" → `member add`(需 `--space` + `--user` + `--role`)
|
||||
- 用户说"修改某人在知识库的权限/调整成员角色" → `member update`
|
||||
- 用户说"知识库有哪些成员/查看知识库成员" → `member list`
|
||||
|
||||
关键区分:
|
||||
- wiki(知识库空间级管理:创建/查询/列出/搜索/成员管理) vs doc(文档内容级操作:搜索/读写/编辑/节点级权限)
|
||||
- wiki space(知识库容器) vs drive(钉盘文件存储/上传/下载)
|
||||
- **wiki member**(容器级,授权整个知识库)vs **doc permission**(节点级,授权单篇文档)
|
||||
- 「我的文档」**只能用** `doc permission`,不能用 `wiki member`
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 列出我有权访问的组织知识库
|
||||
dws wiki space list --format json
|
||||
|
||||
# 获取「我的文档」个人空间
|
||||
dws wiki space list --type myWikiSpace --format json
|
||||
|
||||
# 搜索知识库
|
||||
dws wiki space search --keyword "产品" --format json
|
||||
|
||||
# 搜索「我的文档」
|
||||
dws wiki space search --type myWikiSpace --format json
|
||||
|
||||
# 创建知识库
|
||||
dws wiki space create --name "新项目文档" --description "项目相关文档归档" --format json
|
||||
|
||||
# 查看知识库详情
|
||||
dws wiki space get --id <workspaceId> --format json
|
||||
|
||||
# ── 工作流: 给知识库加成员 ──
|
||||
|
||||
# 1. 先确认知识库 ID(避免授权到「我的文档」)
|
||||
dws wiki space list --format json # 注意:不要 --type myWikiSpace
|
||||
|
||||
# 2. 添加成员
|
||||
dws wiki member add --space <WS_ID> --user <UID> --role EDITOR --format json
|
||||
|
||||
# 3. 查看当前成员
|
||||
dws wiki member list --space <WS_ID> --format json
|
||||
```
|
||||
|
||||
## 上下文传递表
|
||||
|
||||
| 操作 | 从返回中提取 | 用于 |
|
||||
|------|-------------|------|
|
||||
| `space create` | `workspaceId` | space get 的 --id / member add 的 --space |
|
||||
| `space list` | `workspaceId` | space get 的 --id / member add 的 --space |
|
||||
| `space search` | `workspaceId` | space get 的 --id / member add 的 --space |
|
||||
| `space get` | `spaceUrl` | 分享给用户 |
|
||||
| `member list` | `userId` | member update 的 --user |
|
||||
|
||||
## 相关产品
|
||||
|
||||
- [doc](./doc.md) — 文档内容级操作(搜索/读写/编辑文档、知识库内文档管理)
|
||||
- [drive](./drive.md) — 钉盘文件存储/上传/下载
|
||||
@@ -0,0 +1,41 @@
|
||||
# 工作台 (workbench) 命令参考
|
||||
|
||||
> ⚠️ **Draft**:以下命令在当前 CLI 运行时中尚未上线(`dws workbench --help` 回退到根帮助页面),文档仅供预览,待服务端注册后生效。
|
||||
|
||||
## 命令总览
|
||||
|
||||
### 查看所有工作台应用
|
||||
```
|
||||
Usage:
|
||||
dws workbench app list [flags]
|
||||
Example:
|
||||
dws workbench app list
|
||||
```
|
||||
### 批量获取应用详情
|
||||
```
|
||||
Usage:
|
||||
dws workbench app get [flags]
|
||||
Example:
|
||||
dws workbench app get --ids <APP_ID_1>,<APP_ID_2>
|
||||
Flags:
|
||||
--ids string 应用 ID 列表 (必填)
|
||||
```
|
||||
|
||||
## 意图判断
|
||||
|
||||
用户说"工作台有什么应用/看看应用" → `app list`
|
||||
用户说"应用详情" → `app get` (需 appId)
|
||||
|
||||
## 核心工作流
|
||||
|
||||
```bash
|
||||
# 查看所有应用 — 提取 appId
|
||||
dws workbench app list --format json
|
||||
|
||||
# 获取应用详情
|
||||
dws workbench app get --ids app1,app2 --format json
|
||||
```
|
||||
## 上下文传递表
|
||||
| 操作 | 提取 | 用于 |
|
||||
|------|------|------|
|
||||
| `app list` | `appId` | app get 的 --ids |
|
||||
@@ -0,0 +1,94 @@
|
||||
# Recovery Guide
|
||||
|
||||
`dws` 的 recovery 闭环以 `dws recovery execute` 为正式入口。主入口仍是 [SKILL.md](../SKILL.md),错误分类与排查细节可结合 [error-codes.md](./error-codes.md) 和 [global-reference.md](./global-reference.md) 一起使用。
|
||||
|
||||
## 标准流程
|
||||
|
||||
1. 原始 `dws` 命令失败后,从 stderr 提取 `RECOVERY_EVENT_ID=<event_id>`
|
||||
2. 执行 `dws recovery execute --event-id <event_id> --format json`
|
||||
3. 读取返回的 `RecoveryBundle`
|
||||
4. 先查看 `plan.decision_owner`
|
||||
5. 当 `decision_owner == "agent"` 时,把完整 `RecoveryBundle` 视为事实包,由 Agent 基于事实包、对应产品文档和 `dws` skill 做整体判断,再决定是否组织下一次 grounded 的 `dws` 恢复尝试
|
||||
6. 恢复尝试结束后,执行 `dws recovery finalize --event-id <event_id> --outcome recovered|failed|handoff --execution-file <file.json> --format json`
|
||||
|
||||
CLI 会把 recovery 过程文件保存在 `DWS_CONFIG_DIR/recovery/` 下,并自动清理 30 天前的旧文件与旧事件记录。
|
||||
|
||||
## 如何阅读 `RecoveryBundle`
|
||||
|
||||
`RecoveryBundle` 重点字段:
|
||||
|
||||
- `status`:`needs_agent_action` 或 `analysis_failed`
|
||||
- `context`:原始失败上下文
|
||||
- `replay`:可重放的脱敏命令信息和工具参数
|
||||
- `plan`:规则分类、`decision_owner`、`agent_route`、`doc_search`、`kb_hits`、`doc_actions`、`human_actions`
|
||||
- `doc_search.request`:CLI 实际发起的开放平台文档检索请求参数
|
||||
- `doc_search.response`:CLI 收到的原始文档检索返回内容块
|
||||
- `probe_results`:CLI 已完成的只读探测和上下文审计结果
|
||||
- `agent_task`:给 Agent 的明确工作单
|
||||
- `finalize_hint`:最终必须回写的 `finalize` 命令模板和 `execution-file` 要求
|
||||
|
||||
当 `status == "analysis_failed"` 时,不要假设 CLI 已经修复问题;应先读取 `analysis_error`、`doc_search`、`probe_results`,再判断是否还能继续 grounded 尝试。
|
||||
|
||||
当 `plan.decision_owner == "agent"` 时:
|
||||
|
||||
- 必须把 CLI 返回内容视为事实包,而不是已定案的恢复结论
|
||||
- 优先基于完整 `RecoveryBundle` 做判断;若只能读取 `agent_route.payload`,也必须使用其中的 `context`、`replay`、`doc_search`、`kb_hits`、`doc_actions`、`probe_results`
|
||||
- unknown 场景里,不要把 `should_retry`、`should_stop`、`human_actions` 当作 CLI 已经替 Agent 作出的最终决定
|
||||
|
||||
## Agent 允许做什么
|
||||
|
||||
- 读取 [global-reference.md](./global-reference.md)、[error-codes.md](./error-codes.md) 和对应产品文档
|
||||
- 基于完整 `RecoveryBundle`,尤其是 `doc_actions`、`kb_hits`、`probe_results`、`context`、`replay` 做整体判断
|
||||
- 仅在依据明确时重新发起新的 `dws` 命令
|
||||
- 将真实尝试过程记录进 `execution-file`,再调用 `finalize`
|
||||
|
||||
## Agent 禁止做什么
|
||||
|
||||
- 编造 taskId、recordId、threadId、UUID、token、URL 或其他业务参数
|
||||
- 绕过 `dws` 改用 curl、HTTP API、浏览器或其他未授权手段
|
||||
- 未确认前把失败命令替换成另一套业务流程
|
||||
- 因为 `human_actions` 里提到用户步骤,就跳过 bundle 里的其余分析信息
|
||||
|
||||
## `execution-file` 最小要求
|
||||
|
||||
`execution-file` 必须至少包含:
|
||||
|
||||
- `actions`
|
||||
- `attempts`
|
||||
- `result`
|
||||
- `error_summary`
|
||||
|
||||
`attempts` 是数组,每次尝试至少记录:
|
||||
|
||||
- `command_summary`
|
||||
- `result`
|
||||
- `error_summary`
|
||||
- `source`
|
||||
|
||||
兼容旧格式:
|
||||
|
||||
- `action` 会被归一化成单元素 `actions`
|
||||
- 数值型 `attempts` 会被展开为 `legacy_execution_file` 来源的 attempts 记录
|
||||
- `error` 会被归一化到 `error_summary`
|
||||
|
||||
## unknown 参数错误处理
|
||||
|
||||
如果 `execute` 进入 unknown 场景,且 `probe_results` 已给出 CLI 检查过的上下文来源,Agent 应:
|
||||
|
||||
- 先检查上一步输出、已有上下文、产品文档或 `probe_results` 里是否存在真实参数来源
|
||||
- 若没有可靠来源,就回写 `handoff`
|
||||
- 不要盲猜新的 ID、UUID、URL、token 或其他业务参数
|
||||
|
||||
对应标准闭环:
|
||||
|
||||
```bash
|
||||
dws recovery execute --event-id <event_id> --format json
|
||||
dws recovery finalize --event-id <event_id> --outcome handoff --execution-file execution.json --format json
|
||||
```
|
||||
|
||||
## 必读参考
|
||||
|
||||
- [error-codes.md](./error-codes.md)
|
||||
- [global-reference.md](./global-reference.md)
|
||||
- [intent-guide.md](./intent-guide.md)
|
||||
- [products/](./products/)
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查看我今天/本周/指定日期的考勤记录(自动获取 userId)
|
||||
|
||||
用法:
|
||||
python attendance_my_record.py # 今天
|
||||
python attendance_my_record.py today # 今天
|
||||
python attendance_my_record.py 2026-03-10 # 指定日期
|
||||
python attendance_my_record.py --dry-run # 仅显示命令
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import List, Any, Optional
|
||||
|
||||
DATE_PATTERN = re.compile(r'^\d{4}-\d{2}-\d{2}$')
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_my_user_id(dry_run: bool = False) -> Optional[str]:
|
||||
data = run_dws([
|
||||
'contact', 'user', 'get-self', '--format', 'json',
|
||||
], dry_run=dry_run)
|
||||
if dry_run:
|
||||
return '<MY_USER_ID>'
|
||||
if not data or not isinstance(data, dict):
|
||||
return None
|
||||
return data.get('userId') or data.get('userid')
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
args = [a for a in sys.argv[1:] if a != '--dry-run']
|
||||
|
||||
date_str = args[0] if args else 'today'
|
||||
if date_str == 'today':
|
||||
date_str = datetime.now().strftime('%Y-%m-%d')
|
||||
elif not DATE_PATTERN.match(date_str):
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
print('🔍 获取当前用户信息...')
|
||||
user_id = get_my_user_id(dry_run=dry_run)
|
||||
if not user_id and not dry_run:
|
||||
print('错误:无法获取当前用户 ID')
|
||||
sys.exit(1)
|
||||
|
||||
print(f'📊 查询 {date_str} 考勤记录...\n')
|
||||
data = run_dws([
|
||||
'attendance', 'record', 'get',
|
||||
'--user', user_id or '<MY_USER_ID>',
|
||||
'--date', date_str,
|
||||
'--format', 'json',
|
||||
], dry_run=dry_run)
|
||||
|
||||
if dry_run:
|
||||
return
|
||||
if not data:
|
||||
print('未查到考勤记录')
|
||||
return
|
||||
|
||||
print(f"📋 考勤记录 ({date_str})")
|
||||
print('=' * 40)
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查询团队成员本周排班和出勤统计
|
||||
|
||||
用法:
|
||||
python attendance_team_shift.py --users userId1,userId2,userId3
|
||||
python attendance_team_shift.py --users userId1,userId2 \
|
||||
--from 2026-03-10 --to 2026-03-14
|
||||
python attendance_team_shift.py --users userId1 --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Any, Optional
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_week_range():
|
||||
today = datetime.now()
|
||||
monday = today - timedelta(days=today.weekday())
|
||||
friday = monday + timedelta(days=4)
|
||||
return monday.strftime('%Y-%m-%d'), friday.strftime('%Y-%m-%d')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='查询团队成员排班和出勤统计'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--users', required=True, help='用户 ID 列表,逗号分隔'
|
||||
)
|
||||
mon, fri = get_week_range()
|
||||
parser.add_argument('--from', dest='from_date', default=mon)
|
||||
parser.add_argument('--to', dest='to_date', default=fri)
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
user_count = len(args.users.split(','))
|
||||
if user_count > 50:
|
||||
print('错误:最多查询 50 人')
|
||||
sys.exit(1)
|
||||
|
||||
print(f"📊 团队排班查询 ({args.from_date} ~ {args.to_date})")
|
||||
print(f" 人数: {user_count}")
|
||||
print('=' * 50)
|
||||
|
||||
print('\n🔍 查询排班信息...')
|
||||
data = run_dws([
|
||||
'attendance', 'shift', 'list',
|
||||
'--users', args.users,
|
||||
'--start', args.from_date,
|
||||
'--end', args.to_date,
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
|
||||
if args.dry_run:
|
||||
return
|
||||
if not data:
|
||||
print('未查到排班信息')
|
||||
return
|
||||
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
批量添加字段到钉钉 AI 表格数据表(新版 schema)
|
||||
|
||||
用法:
|
||||
python bulk_add_fields.py <baseId> <tableId> fields.json
|
||||
|
||||
fields.json 格式:
|
||||
[
|
||||
{"fieldName": "字段 1", "type": "text"},
|
||||
{"fieldName": "字段 2", "type": "number", "config": {"formatter": "INT"}},
|
||||
{"fieldName": "字段 3", "type": "singleSelect", "config": {"options": [{"name": "高"}]}}
|
||||
]
|
||||
|
||||
兼容写法:
|
||||
- name 会自动映射为 fieldName
|
||||
- phone 会自动映射为 telephone
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict, Any, Optional, Tuple
|
||||
|
||||
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_-]{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',
|
||||
}
|
||||
FIELD_TYPE_ALIASES = {
|
||||
'phone': 'telephone',
|
||||
}
|
||||
|
||||
|
||||
def resolve_safe_path(path: str, allowed_root: Optional[str] = None) -> Path:
|
||||
if allowed_root is None:
|
||||
allowed_root = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd())
|
||||
|
||||
allowed_root = Path(allowed_root).resolve()
|
||||
target_path = (
|
||||
Path(path).resolve()
|
||||
if Path(path).is_absolute()
|
||||
else (Path.cwd() / path).resolve()
|
||||
)
|
||||
|
||||
try:
|
||||
target_path.relative_to(allowed_root)
|
||||
return target_path
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"路径超出允许范围:{path}\n"
|
||||
f"目标路径:{target_path}\n"
|
||||
f"允许根目录:{allowed_root}\n"
|
||||
f"提示:设置 OPENCLAW_WORKSPACE 环境变量或确保文件在工作目录内"
|
||||
)
|
||||
|
||||
|
||||
def validate_resource_id(resource_id: str) -> bool:
|
||||
return bool(resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip()))
|
||||
|
||||
|
||||
def validate_file_extension(filename: str, allowed_extensions: list) -> bool:
|
||||
return any(filename.lower().endswith(ext) for ext in allowed_extensions)
|
||||
|
||||
|
||||
def safe_json_load(file_path: Path, max_size: int = MAX_FILE_SIZE) -> JsonData:
|
||||
file_size = file_path.stat().st_size
|
||||
if file_size > max_size:
|
||||
raise ValueError(
|
||||
f"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)"
|
||||
)
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def normalize_field_config(field: Dict[str, Any]) -> Dict[str, Any]:
|
||||
normalized = dict(field)
|
||||
if 'fieldName' not in normalized and 'name' in normalized:
|
||||
normalized['fieldName'] = normalized.pop('name')
|
||||
normalized['type'] = FIELD_TYPE_ALIASES.get(
|
||||
normalized.get('type', 'text'), normalized.get('type', 'text')
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def validate_field_config(field: Dict[str, Any]) -> Tuple[bool, str]:
|
||||
if not isinstance(field, dict):
|
||||
return False, '字段配置必须是对象'
|
||||
|
||||
field = normalize_field_config(field)
|
||||
|
||||
if 'fieldName' not in field:
|
||||
return False, '缺少必需字段:fieldName'
|
||||
if not isinstance(field['fieldName'], str) or not field['fieldName'].strip():
|
||||
return False, 'fieldName 必须是非空字符串'
|
||||
|
||||
field_type = field.get('type', 'text')
|
||||
if field_type not in ALLOWED_FIELD_TYPES:
|
||||
return False, f"不支持的字段类型:{field_type}"
|
||||
|
||||
config = field.get('config')
|
||||
if config is not None and not isinstance(config, dict):
|
||||
return False, 'config 必须是对象'
|
||||
|
||||
if field_type in {'singleSelect', 'multipleSelect'}:
|
||||
options = (config or {}).get('options')
|
||||
if not options or not isinstance(options, list):
|
||||
return False, (
|
||||
'singleSelect / multipleSelect 必须提供 config.options 数组'
|
||||
)
|
||||
|
||||
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'
|
||||
|
||||
return True, ''
|
||||
|
||||
|
||||
def build_fields_json(fields: List[Dict[str, Any]]) -> str:
|
||||
"""构建 --fields 参数的 JSON 字符串。"""
|
||||
payload_fields = []
|
||||
for field in fields:
|
||||
normalized = normalize_field_config(field)
|
||||
item: Dict[str, Any] = {
|
||||
'fieldName': normalized['fieldName'].strip(),
|
||||
'type': normalized.get('type', 'text'),
|
||||
}
|
||||
if 'config' in normalized and normalized['config'] is not None:
|
||||
item['config'] = normalized['config']
|
||||
payload_fields.append(item)
|
||||
return json.dumps(payload_fields, ensure_ascii=False)
|
||||
|
||||
|
||||
def run_dws(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||
if not args:
|
||||
print('错误:空命令')
|
||||
return None
|
||||
|
||||
cmd = ['dws'] + args
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}")
|
||||
return None
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"无法解析响应:{result.stdout[:200]}...")
|
||||
print(f"JSON 解析错误:{e}")
|
||||
return None
|
||||
except subprocess.TimeoutExpired:
|
||||
print('错误:命令执行超时(60 秒)')
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
print('错误:未找到 dws 命令,请确认已安装')
|
||||
return None
|
||||
|
||||
|
||||
def bulk_add_fields(
|
||||
base_id: str, table_id: str, fields_file: str
|
||||
) -> bool:
|
||||
try:
|
||||
safe_path = resolve_safe_path(fields_file)
|
||||
except ValueError as e:
|
||||
print(f"路径验证失败:{e}")
|
||||
return False
|
||||
|
||||
if not validate_file_extension(fields_file, ALLOWED_FILE_EXTENSIONS):
|
||||
print(f"错误:只允许 {', '.join(ALLOWED_FILE_EXTENSIONS)} 文件")
|
||||
return False
|
||||
if not safe_path.exists():
|
||||
print(f"错误:文件不存在:{safe_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
fields = safe_json_load(safe_path)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"错误:JSON 格式无效:{e}")
|
||||
return False
|
||||
|
||||
if not isinstance(fields, list) or not fields:
|
||||
print('错误:fields.json 必须是非空 JSON 数组')
|
||||
return False
|
||||
if len(fields) > 15:
|
||||
print('错误:单次最多创建 15 个字段,请拆分后重试')
|
||||
return False
|
||||
|
||||
for i, field in enumerate(fields):
|
||||
valid, error = validate_field_config(field)
|
||||
if not valid:
|
||||
print(f"错误:字段 #{i+1} 配置无效:{error}")
|
||||
return False
|
||||
|
||||
fields_json = build_fields_json(fields)
|
||||
result = run_dws([
|
||||
'aitable', 'field', 'create',
|
||||
'--base-id', base_id,
|
||||
'--table-id', table_id,
|
||||
'--fields', fields_json,
|
||||
'--format', 'json',
|
||||
])
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print(__doc__)
|
||||
print('用法示例:')
|
||||
print(' python bulk_add_fields.py basexxx tablexxx fields.json')
|
||||
sys.exit(1)
|
||||
|
||||
base_id = sys.argv[1]
|
||||
table_id = sys.argv[2]
|
||||
fields_file = sys.argv[3]
|
||||
|
||||
if not validate_resource_id(base_id):
|
||||
print('错误:无效的 baseId 格式')
|
||||
sys.exit(1)
|
||||
if not validate_resource_id(table_id):
|
||||
print('错误:无效的 tableId 格式')
|
||||
sys.exit(1)
|
||||
|
||||
success = bulk_add_fields(base_id, table_id, fields_file)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查询多人共同空闲时段,推荐最佳会议时间
|
||||
|
||||
用法:
|
||||
python calendar_free_slot_finder.py \
|
||||
--users userId1,userId2,userId3 \
|
||||
--date 2026-03-15 \
|
||||
--duration 60
|
||||
|
||||
python calendar_free_slot_finder.py \
|
||||
--users userId1,userId2 \
|
||||
--date 2026-03-15 \
|
||||
--start-hour 9 --end-hour 18 \
|
||||
--duration 30 --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
SLOT_STEP_MIN = 30
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def fmt_iso(dt: datetime) -> str:
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
def parse_busy_intervals(
|
||||
data: Any,
|
||||
) -> List[Tuple[datetime, datetime]]:
|
||||
intervals = []
|
||||
if not data:
|
||||
return intervals
|
||||
items = []
|
||||
if isinstance(data, list):
|
||||
items = data
|
||||
elif isinstance(data, dict):
|
||||
for user_data in data.values():
|
||||
if isinstance(user_data, list):
|
||||
items.extend(user_data)
|
||||
elif isinstance(user_data, dict):
|
||||
items.extend(
|
||||
user_data.get('busyTimes', [])
|
||||
)
|
||||
for item in items:
|
||||
start_str = item.get('startTime') or item.get('start', '')
|
||||
end_str = item.get('endTime') or item.get('end', '')
|
||||
if not start_str or not end_str:
|
||||
continue
|
||||
for fmt in (
|
||||
'%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S',
|
||||
'%Y-%m-%dT%H:%M%z',
|
||||
):
|
||||
try:
|
||||
s = datetime.strptime(start_str, fmt)
|
||||
e = datetime.strptime(end_str, fmt)
|
||||
if s.tzinfo is None:
|
||||
s = s.replace(tzinfo=TZ)
|
||||
if e.tzinfo is None:
|
||||
e = e.replace(tzinfo=TZ)
|
||||
intervals.append((s, e))
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
return intervals
|
||||
|
||||
|
||||
def find_free_slots(
|
||||
day_start: datetime, day_end: datetime,
|
||||
busy: List[Tuple[datetime, datetime]],
|
||||
duration_min: int,
|
||||
) -> List[Tuple[datetime, datetime]]:
|
||||
busy_sorted = sorted(busy, key=lambda x: x[0])
|
||||
merged: List[Tuple[datetime, datetime]] = []
|
||||
for s, e in busy_sorted:
|
||||
if merged and s <= merged[-1][1]:
|
||||
merged[-1] = (merged[-1][0], max(merged[-1][1], e))
|
||||
else:
|
||||
merged.append((s, e))
|
||||
|
||||
free: List[Tuple[datetime, datetime]] = []
|
||||
cursor = day_start
|
||||
for bs, be in merged:
|
||||
if cursor < bs:
|
||||
gap = (bs - cursor).total_seconds() / 60
|
||||
if gap >= duration_min:
|
||||
free.append((cursor, bs))
|
||||
cursor = max(cursor, be)
|
||||
if cursor < day_end:
|
||||
gap = (day_end - cursor).total_seconds() / 60
|
||||
if gap >= duration_min:
|
||||
free.append((cursor, day_end))
|
||||
return free
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='查询多人共同空闲时段'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--users', required=True, help='用户 ID 列表,逗号分隔'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--date', required=True, help='查询日期 YYYY-MM-DD'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--duration', type=int, default=60,
|
||||
help='会议时长(分钟),默认 60',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--start-hour', type=int, default=9,
|
||||
help='工作日开始小时,默认 9',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--end-hour', type=int, default=18,
|
||||
help='工作日结束小时,默认 18',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run', action='store_true', help='仅显示命令'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
date = datetime.strptime(args.date, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
print('错误:日期格式应为 YYYY-MM-DD')
|
||||
sys.exit(1)
|
||||
|
||||
day_start = date.replace(
|
||||
hour=args.start_hour, tzinfo=TZ
|
||||
)
|
||||
day_end = date.replace(hour=args.end_hour, tzinfo=TZ)
|
||||
|
||||
data = run_dws([
|
||||
'calendar', 'busy', 'search',
|
||||
'--users', args.users,
|
||||
'--start', fmt_iso(day_start),
|
||||
'--end', fmt_iso(day_end),
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
|
||||
if args.dry_run:
|
||||
return
|
||||
|
||||
busy = parse_busy_intervals(data)
|
||||
free = find_free_slots(day_start, day_end, busy, args.duration)
|
||||
|
||||
users_list = args.users.split(',')
|
||||
print(f"\n🕐 空闲时段查询 ({args.date})")
|
||||
print(f" 参与人: {len(users_list)} 人")
|
||||
print(f" 会议时长: {args.duration} 分钟")
|
||||
print(f" 工作时间: {args.start_hour}:00 ~ "
|
||||
f"{args.end_hour}:00")
|
||||
print('=' * 50)
|
||||
|
||||
if not free:
|
||||
print(' ❌ 该日无共同空闲时段')
|
||||
return
|
||||
|
||||
print(f"\n✅ 找到 {len(free)} 个可用时段:\n")
|
||||
for i, (s, e) in enumerate(free, 1):
|
||||
gap_min = int((e - s).total_seconds() / 60)
|
||||
label = '⭐ 推荐' if i == 1 else f' 备选{i-1}'
|
||||
print(f" {label} {s.strftime('%H:%M')} ~ "
|
||||
f"{e.strftime('%H:%M')} ({gap_min}分钟)")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
一键创建日程 + 添加参与者 + 搜索并预定空闲会议室
|
||||
|
||||
用法:
|
||||
python calendar_schedule_meeting.py \
|
||||
--title "Q1 复盘会" \
|
||||
--start "2026-03-15T14:00" \
|
||||
--end "2026-03-15T15:00" \
|
||||
--users userId1,userId2 \
|
||||
--book-room
|
||||
|
||||
python calendar_schedule_meeting.py --dry-run \
|
||||
--title "测试" --start "2026-03-15T14:00" --end "2026-03-15T15:00"
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return {'dry_run': True}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f" ✗ 错误:{result.stderr.strip()}")
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f" ✗ 错误:{e}")
|
||||
return None
|
||||
|
||||
|
||||
def normalize_time(time_str: str) -> str:
|
||||
for fmt in ('%Y-%m-%dT%H:%M', '%Y-%m-%d %H:%M',
|
||||
'%Y-%m-%dT%H:%M:%S'):
|
||||
try:
|
||||
dt = datetime.strptime(time_str, fmt)
|
||||
dt = dt.replace(tzinfo=TZ)
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
except ValueError:
|
||||
continue
|
||||
if '+' in time_str or time_str.endswith('Z'):
|
||||
return time_str
|
||||
raise ValueError(f"无法解析时间:{time_str}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='一键创建日程 + 添加参与者 + 预定会议室'
|
||||
)
|
||||
parser.add_argument('--title', required=True, help='日程标题')
|
||||
parser.add_argument('--start', required=True, help='开始时间')
|
||||
parser.add_argument('--end', required=True, help='结束时间')
|
||||
parser.add_argument('--desc', default='', help='日程描述')
|
||||
parser.add_argument('--users', default='', help='参与者 userId')
|
||||
parser.add_argument(
|
||||
'--book-room', action='store_true', help='自动预定会议室'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run', action='store_true', help='仅显示命令'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
start_iso = normalize_time(args.start)
|
||||
end_iso = normalize_time(args.end)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
sys.exit(1)
|
||||
|
||||
print('📅 创建日程...')
|
||||
create_args = [
|
||||
'calendar', 'event', 'create',
|
||||
'--title', args.title,
|
||||
'--start', start_iso,
|
||||
'--end', end_iso,
|
||||
'--format', 'json',
|
||||
]
|
||||
if args.desc:
|
||||
create_args.extend(['--desc', args.desc])
|
||||
|
||||
result = run_dws(create_args, dry_run=args.dry_run)
|
||||
if not result:
|
||||
sys.exit(1)
|
||||
|
||||
event_id = None
|
||||
if not args.dry_run and isinstance(result, dict):
|
||||
event_id = result.get('eventId') or result.get('id')
|
||||
print(f" ✓ 日程已创建" +
|
||||
(f" (eventId: {event_id})" if event_id else ""))
|
||||
|
||||
if args.users and event_id:
|
||||
print('\n👥 添加参与者...')
|
||||
r = run_dws([
|
||||
'calendar', 'participant', 'add',
|
||||
'--event', event_id,
|
||||
'--users', args.users,
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
if r:
|
||||
print(f" ✓ 已添加参与者: {args.users}")
|
||||
elif args.users and args.dry_run:
|
||||
run_dws([
|
||||
'calendar', 'participant', 'add',
|
||||
'--event', '<EVENT_ID>',
|
||||
'--users', args.users,
|
||||
'--format', 'json',
|
||||
], dry_run=True)
|
||||
|
||||
if args.book_room:
|
||||
print('\n🏢 搜索空闲会议室...')
|
||||
rooms_data = run_dws([
|
||||
'calendar', 'room', 'search',
|
||||
'--start', start_iso,
|
||||
'--end', end_iso,
|
||||
'--available',
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
|
||||
if not args.dry_run and rooms_data:
|
||||
rooms = (rooms_data if isinstance(rooms_data, list)
|
||||
else rooms_data.get('rooms', []))
|
||||
if rooms:
|
||||
room = rooms[0]
|
||||
room_id = room.get('roomId') or room.get('id')
|
||||
room_name = room.get('roomName') or room.get('name')
|
||||
print(f" 找到空闲会议室: {room_name}")
|
||||
if event_id and room_id:
|
||||
r = run_dws([
|
||||
'calendar', 'room', 'add',
|
||||
'--event', event_id,
|
||||
'--rooms', str(room_id),
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
if r:
|
||||
print(f" ✓ 已预定: {room_name}")
|
||||
else:
|
||||
print(' ⚠ 该时段无空闲会议室')
|
||||
|
||||
print('\n✅ 完成!')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查看今天/明天/本周的日程安排
|
||||
|
||||
用法:
|
||||
python calendar_today_agenda.py # 今天
|
||||
python calendar_today_agenda.py today # 今天
|
||||
python calendar_today_agenda.py tomorrow # 明天
|
||||
python calendar_today_agenda.py week # 本周
|
||||
python calendar_today_agenda.py --dry-run # 仅显示命令
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_range(scope: str):
|
||||
now = datetime.now(TZ)
|
||||
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if scope == 'today':
|
||||
return today, today + timedelta(days=1)
|
||||
elif scope == 'tomorrow':
|
||||
t = today + timedelta(days=1)
|
||||
return t, t + timedelta(days=1)
|
||||
elif scope == 'week':
|
||||
ws = today - timedelta(days=today.weekday())
|
||||
return ws, ws + timedelta(days=7)
|
||||
return today, today + timedelta(days=1)
|
||||
|
||||
|
||||
def fmt_iso(dt: datetime) -> str:
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
def fmt_time(iso_str: str) -> str:
|
||||
if not iso_str:
|
||||
return '??:??'
|
||||
try:
|
||||
for fmt in ('%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S'):
|
||||
try:
|
||||
dt = datetime.strptime(iso_str, fmt)
|
||||
return dt.strftime('%H:%M')
|
||||
except ValueError:
|
||||
continue
|
||||
return iso_str[:16]
|
||||
except Exception:
|
||||
return iso_str[:16]
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
args = [a for a in sys.argv[1:] if a != '--dry-run']
|
||||
scope = args[0] if args else 'today'
|
||||
if scope not in ('today', 'tomorrow', 'week'):
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
start, end = get_range(scope)
|
||||
data = run_dws([
|
||||
'calendar', 'event', 'list',
|
||||
'--start', fmt_iso(start),
|
||||
'--end', fmt_iso(end),
|
||||
'--format', 'json',
|
||||
], dry_run=dry_run)
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
events = []
|
||||
if isinstance(data, list):
|
||||
events = data
|
||||
elif isinstance(data, dict):
|
||||
events = data.get('events', data.get('result', []))
|
||||
|
||||
label = {'today': '今天', 'tomorrow': '明天', 'week': '本周'
|
||||
}.get(scope, scope)
|
||||
print(f"\n📅 {label}日程 ({start.strftime('%m-%d')} ~ "
|
||||
f"{end.strftime('%m-%d')})")
|
||||
print('=' * 50)
|
||||
|
||||
if not events:
|
||||
print(' ✅ 暂无日程,自由安排!')
|
||||
return
|
||||
|
||||
for e in events:
|
||||
title = e.get('summary') or e.get('title', '无标题')
|
||||
s = e.get('start', {})
|
||||
ed = e.get('end', {})
|
||||
start_t = fmt_time(
|
||||
s.get('dateTime', '') if isinstance(s, dict) else str(s)
|
||||
)
|
||||
end_t = fmt_time(
|
||||
ed.get('dateTime', '') if isinstance(ed, dict) else str(ed)
|
||||
)
|
||||
loc = e.get('location', {})
|
||||
loc_str = (loc.get('displayName', '')
|
||||
if isinstance(loc, dict) else str(loc or ''))
|
||||
line = f" 🕐 {start_t}-{end_t} {title}"
|
||||
if loc_str:
|
||||
line += f" 📍{loc_str}"
|
||||
print(line)
|
||||
|
||||
print(f"\n合计: {len(events)} 场日程")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
按部门名称搜索并列出所有成员(自动 deptId 解析)
|
||||
|
||||
用法:
|
||||
python contact_dept_members.py --query "技术部"
|
||||
python contact_dept_members.py --query "产品" --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from typing import List, Any, Optional
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='按部门名称搜索并列出所有成员'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--query', required=True, help='部门名称关键词'
|
||||
)
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f'🔍 搜索部门: {args.query}')
|
||||
dept_data = run_dws([
|
||||
'contact', 'dept', 'search',
|
||||
'--query', args.query, '--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
|
||||
if args.dry_run:
|
||||
run_dws([
|
||||
'contact', 'dept', 'list-members',
|
||||
'--ids', '<DEPT_ID>', '--format', 'json',
|
||||
], dry_run=True)
|
||||
return
|
||||
|
||||
if not dept_data:
|
||||
print('未找到匹配部门')
|
||||
sys.exit(1)
|
||||
|
||||
depts = (dept_data if isinstance(dept_data, list)
|
||||
else dept_data.get('result', dept_data.get('items', [])))
|
||||
if not depts:
|
||||
print('未找到匹配部门')
|
||||
sys.exit(1)
|
||||
|
||||
for dept in depts:
|
||||
dept_id = dept.get('id') or dept.get('deptId')
|
||||
dept_name = dept.get('name') or dept.get('deptName', '未知')
|
||||
if not dept_id:
|
||||
continue
|
||||
|
||||
print(f"\n📂 {dept_name} (ID: {dept_id})")
|
||||
print('-' * 40)
|
||||
|
||||
members_data = run_dws([
|
||||
'contact', 'dept', 'list-members',
|
||||
'--ids', str(dept_id), '--format', 'json',
|
||||
])
|
||||
if not members_data:
|
||||
print(' 无法获取成员列表')
|
||||
continue
|
||||
|
||||
members = (members_data if isinstance(members_data, list)
|
||||
else members_data.get('result',
|
||||
members_data.get('userlist', [])))
|
||||
if not members:
|
||||
print(' (暂无成员)')
|
||||
continue
|
||||
|
||||
for m in members:
|
||||
name = m.get('name') or m.get('userName', '未知')
|
||||
title = m.get('title') or m.get('position', '')
|
||||
uid = m.get('userId') or m.get('userid', '')
|
||||
line = f" 👤 {name}"
|
||||
if title:
|
||||
line += f" ({title})"
|
||||
if uid:
|
||||
line += f" [ID: {uid}]"
|
||||
print(line)
|
||||
|
||||
print(f" 共 {len(members)} 人")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
从 CSV / JSON 批量导入记录到钉钉 AI 表格(新版 schema)
|
||||
|
||||
用法:
|
||||
python import_records.py <baseId> <tableId> data.csv [batch_size]
|
||||
python import_records.py <baseId> <tableId> data.json [batch_size]
|
||||
|
||||
说明:
|
||||
- CSV 表头默认视为 fieldId
|
||||
- JSON 支持两种格式:
|
||||
1. [{"cells": {"fldxxx": "value"}}, ...]
|
||||
2. [{"fldxxx": "value"}, ...] # 会自动包装成 cells
|
||||
"""
|
||||
|
||||
import sys
|
||||
import csv
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict, Any, Optional, Tuple
|
||||
|
||||
JsonData = Union[List[Any], Dict[str, Any]]
|
||||
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_-]{8,128}$')
|
||||
MAX_RECORDS_PER_BATCH = 100
|
||||
DEFAULT_BATCH_SIZE = 50
|
||||
|
||||
|
||||
def resolve_safe_path(
|
||||
path: str, allowed_root: Optional[str] = None
|
||||
) -> Path:
|
||||
if allowed_root is None:
|
||||
allowed_root = os.environ.get('OPENCLAW_WORKSPACE', os.getcwd())
|
||||
allowed_root = Path(allowed_root).resolve()
|
||||
target_path = (
|
||||
Path(path).resolve()
|
||||
if Path(path).is_absolute()
|
||||
else (Path.cwd() / path).resolve()
|
||||
)
|
||||
try:
|
||||
target_path.relative_to(allowed_root)
|
||||
return target_path
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"路径超出允许范围:{path}\n"
|
||||
f"目标路径:{target_path}\n"
|
||||
f"允许根目录:{allowed_root}\n"
|
||||
f"提示:设置 OPENCLAW_WORKSPACE 环境变量或确保文件在工作目录内"
|
||||
)
|
||||
|
||||
|
||||
def validate_resource_id(resource_id: str) -> bool:
|
||||
return bool(
|
||||
resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip())
|
||||
)
|
||||
|
||||
|
||||
def validate_file_extension(
|
||||
filename: str, allowed_extensions: list
|
||||
) -> bool:
|
||||
return any(filename.lower().endswith(ext) for ext in allowed_extensions)
|
||||
|
||||
|
||||
def safe_csv_load(
|
||||
file_path: Path, max_size: int = MAX_FILE_SIZE
|
||||
) -> List[RecordDict]:
|
||||
file_size = file_path.stat().st_size
|
||||
if file_size > max_size:
|
||||
raise ValueError(
|
||||
f"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)"
|
||||
)
|
||||
with open(file_path, 'r', encoding='utf-8', newline='') as f:
|
||||
return list(csv.DictReader(f))
|
||||
|
||||
|
||||
def safe_json_load(
|
||||
file_path: Path, max_size: int = MAX_FILE_SIZE
|
||||
) -> JsonData:
|
||||
file_size = file_path.stat().st_size
|
||||
if file_size > max_size:
|
||||
raise ValueError(
|
||||
f"文件过大:{file_size:,} 字节 (限制:{max_size:,} 字节)"
|
||||
)
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def sanitize_record_value(
|
||||
value: Any,
|
||||
) -> Optional[Union[str, int, float, bool, list, dict]]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (bool, int, float, list, dict)):
|
||||
return value
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
if not value.strip():
|
||||
return None
|
||||
|
||||
value = value.strip()
|
||||
if value.lower() == 'true':
|
||||
return True
|
||||
if value.lower() == 'false':
|
||||
return False
|
||||
|
||||
try:
|
||||
if '.' in value:
|
||||
return float(value)
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return value
|
||||
|
||||
|
||||
def normalize_record(record: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if 'cells' in record and isinstance(record['cells'], dict):
|
||||
cells = record['cells']
|
||||
else:
|
||||
cells = record
|
||||
normalized = {}
|
||||
for key, value in cells.items():
|
||||
sanitized = sanitize_record_value(value)
|
||||
if sanitized is not None:
|
||||
normalized[key] = sanitized
|
||||
return {'cells': normalized}
|
||||
|
||||
|
||||
def validate_record(
|
||||
record: Dict[str, Any], headers: List[str]
|
||||
) -> Tuple[bool, str]:
|
||||
if not isinstance(record, dict):
|
||||
return False, '记录必须是对象'
|
||||
normalized = normalize_record(record)
|
||||
cells = normalized.get('cells', {})
|
||||
if not cells or not isinstance(cells, dict):
|
||||
return False, '记录必须包含非空 cells 对象'
|
||||
return True, ''
|
||||
|
||||
|
||||
def run_dws(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||
if not args:
|
||||
print('错误:空命令')
|
||||
return None
|
||||
cmd = ['dws'] + args
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=120
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}")
|
||||
return None
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"无法解析响应:{result.stdout[:200]}...")
|
||||
print(f"JSON 解析错误:{e}")
|
||||
return None
|
||||
except subprocess.TimeoutExpired:
|
||||
print('错误:命令执行超时(120 秒)')
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
print('错误:未找到 dws 命令,请确认已安装')
|
||||
return None
|
||||
|
||||
|
||||
def import_from_csv(
|
||||
base_id: str, table_id: str, csv_file: str,
|
||||
batch_size: int = DEFAULT_BATCH_SIZE,
|
||||
) -> bool:
|
||||
try:
|
||||
safe_path = resolve_safe_path(csv_file)
|
||||
except ValueError as e:
|
||||
print(f"路径验证失败:{e}")
|
||||
return False
|
||||
|
||||
if not validate_file_extension(csv_file, ALLOWED_CSV_EXTENSIONS):
|
||||
print(f"错误:只允许 {', '.join(ALLOWED_CSV_EXTENSIONS)} 文件")
|
||||
return False
|
||||
if not safe_path.exists():
|
||||
print(f"错误:文件不存在:{safe_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
rows = safe_csv_load(safe_path)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
return False
|
||||
except csv.Error as e:
|
||||
print(f"错误:CSV 格式无效:{e}")
|
||||
return False
|
||||
|
||||
if not rows:
|
||||
print('错误:CSV 文件为空或没有有效数据行')
|
||||
return False
|
||||
|
||||
records = [
|
||||
normalize_record(row)
|
||||
for row in rows
|
||||
if normalize_record(row)['cells']
|
||||
]
|
||||
return import_records(base_id, table_id, records, batch_size)
|
||||
|
||||
|
||||
def import_from_json(
|
||||
base_id: str, table_id: str, json_file: str,
|
||||
batch_size: int = DEFAULT_BATCH_SIZE,
|
||||
) -> bool:
|
||||
try:
|
||||
safe_path = resolve_safe_path(json_file)
|
||||
except ValueError as e:
|
||||
print(f"路径验证失败:{e}")
|
||||
return False
|
||||
|
||||
if not validate_file_extension(json_file, ALLOWED_JSON_EXTENSIONS):
|
||||
print(f"错误:只允许 {', '.join(ALLOWED_JSON_EXTENSIONS)} 文件")
|
||||
return False
|
||||
if not safe_path.exists():
|
||||
print(f"错误:文件不存在:{safe_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
records = safe_json_load(safe_path)
|
||||
except ValueError as e:
|
||||
print(f"错误:{e}")
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"错误:JSON 格式无效:{e}")
|
||||
return False
|
||||
|
||||
if not isinstance(records, list) or not records:
|
||||
print('错误:JSON 文件必须是非空数组')
|
||||
return False
|
||||
|
||||
for i, record in enumerate(records):
|
||||
valid, error = validate_record(record, [])
|
||||
if not valid:
|
||||
print(f"错误:记录 #{i+1} 格式无效:{error}")
|
||||
return False
|
||||
|
||||
return import_records(
|
||||
base_id, table_id,
|
||||
[normalize_record(r) for r in records], batch_size,
|
||||
)
|
||||
|
||||
|
||||
def import_records(
|
||||
base_id: str, table_id: str,
|
||||
records: List[Dict[str, Any]], batch_size: int,
|
||||
) -> bool:
|
||||
if batch_size <= 0:
|
||||
print('错误:batch_size 必须大于 0')
|
||||
return False
|
||||
if batch_size > MAX_RECORDS_PER_BATCH:
|
||||
batch_size = MAX_RECORDS_PER_BATCH
|
||||
|
||||
total_batches = (len(records) + batch_size - 1) // batch_size
|
||||
success = True
|
||||
|
||||
for i in range(0, len(records), batch_size):
|
||||
batch = records[i:i + batch_size]
|
||||
batch_num = (i // batch_size) + 1
|
||||
records_json = json.dumps(batch, ensure_ascii=False)
|
||||
result = run_dws([
|
||||
'aitable', 'record', 'create',
|
||||
'--base-id', base_id,
|
||||
'--table-id', table_id,
|
||||
'--records', records_json,
|
||||
'--format', 'json',
|
||||
])
|
||||
if result:
|
||||
print(
|
||||
f"[{batch_num}/{total_batches}] "
|
||||
f"✓ 已提交 {len(batch)} 条记录"
|
||||
)
|
||||
else:
|
||||
print(f"[{batch_num}/{total_batches}] ✗ 导入失败")
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4 or len(sys.argv) > 5:
|
||||
print(__doc__)
|
||||
print('用法示例:')
|
||||
print(
|
||||
' python import_records.py basexxx tablexxx data.csv 50'
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
base_id = sys.argv[1]
|
||||
table_id = sys.argv[2]
|
||||
input_file = sys.argv[3]
|
||||
batch_size = (
|
||||
int(sys.argv[4]) if len(sys.argv) == 5
|
||||
else DEFAULT_BATCH_SIZE
|
||||
)
|
||||
|
||||
if not validate_resource_id(base_id):
|
||||
print('错误:无效的 baseId 格式')
|
||||
sys.exit(1)
|
||||
if not validate_resource_id(table_id):
|
||||
print('错误:无效的 tableId 格式')
|
||||
sys.exit(1)
|
||||
|
||||
if input_file.lower().endswith('.csv'):
|
||||
success = import_from_csv(
|
||||
base_id, table_id, input_file, batch_size
|
||||
)
|
||||
elif input_file.lower().endswith('.json'):
|
||||
success = import_from_json(
|
||||
base_id, table_id, input_file, batch_size
|
||||
)
|
||||
else:
|
||||
print('错误:仅支持 .csv 或 .json 文件')
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查看今天收到的日志列表及详情
|
||||
|
||||
用法:
|
||||
python report_inbox_today.py
|
||||
python report_inbox_today.py --days 3 # 最近 3 天
|
||||
python report_inbox_today.py --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Any, Optional
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def to_iso(dt: datetime) -> str:
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='查看收到的日志'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--days', type=int, default=1, help='查询天数 (默认 1)'
|
||||
)
|
||||
parser.add_argument('--dry-run', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
now = datetime.now()
|
||||
start = now - timedelta(days=args.days)
|
||||
start = start.replace(hour=0, minute=0, second=0)
|
||||
|
||||
label = '今天' if args.days == 1 else f'最近 {args.days} 天'
|
||||
print(f'📓 查看{label}收到的日志...\n')
|
||||
|
||||
data = run_dws([
|
||||
'report', 'list',
|
||||
'--start', to_iso(start),
|
||||
'--end', to_iso(now),
|
||||
'--cursor', '0',
|
||||
'--size', '20',
|
||||
'--format', 'json',
|
||||
], dry_run=args.dry_run)
|
||||
|
||||
if args.dry_run:
|
||||
return
|
||||
if not data:
|
||||
print('未查到日志')
|
||||
return
|
||||
|
||||
reports = (data if isinstance(data, list)
|
||||
else data.get('result', data.get('reports', [])))
|
||||
if not reports:
|
||||
print(' ✅ 暂无收到的日志')
|
||||
return
|
||||
|
||||
print(f"📓 {label}日志 ({len(reports)} 条)")
|
||||
print('=' * 50)
|
||||
|
||||
for r in reports:
|
||||
rid = r.get('reportId') or r.get('id', '')
|
||||
creator = r.get('creatorName') or r.get('creator', '未知')
|
||||
template = r.get('templateName') or r.get('template', '')
|
||||
create_time = r.get('createTime', '')
|
||||
if isinstance(create_time, (int, float)):
|
||||
create_time = datetime.fromtimestamp(
|
||||
create_time / 1000
|
||||
).strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
print(f"\n 📝 {template or '日志'} - {creator}")
|
||||
print(f" 时间: {create_time}")
|
||||
print(f" ID: {rid}")
|
||||
|
||||
if rid:
|
||||
detail = run_dws([
|
||||
'report', 'detail',
|
||||
'--report-id', rid, '--format', 'json',
|
||||
])
|
||||
if detail and isinstance(detail, dict):
|
||||
contents = detail.get('contents', [])
|
||||
for c in contents[:3]:
|
||||
key = c.get('key') or c.get('title', '')
|
||||
val = c.get('value') or c.get('content', '')
|
||||
if key and val:
|
||||
print(f" {key}: {str(val)[:60]}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
从 JSON 文件批量创建待办(含优先级、截止时间、执行者)
|
||||
|
||||
用法:
|
||||
python todo_batch_create.py todos.json
|
||||
python todo_batch_create.py todos.json --dry-run
|
||||
|
||||
todos.json 格式:
|
||||
[
|
||||
{"title": "修复线上Bug", "executors": "userId1,userId2", "priority": 40},
|
||||
{"title": "写周报", "executors": "userId1", "due": "2026-03-15"},
|
||||
{"title": "代码评审", "executors": "userId1"},
|
||||
{"title": "每日站会", "executors": "userId1", "due": "2026-03-20",
|
||||
"recurrence": "DTSTART:20260320T020000Z\\nRRULE:FREQ=DAILY;INTERVAL=1"}
|
||||
]
|
||||
|
||||
字段说明:
|
||||
- title: 待办标题 (必填)
|
||||
- executors: 执行者 userId,多人逗号分隔 (必填)
|
||||
- priority: 优先级 10=低/20=普通/30=较高/40=紧急 (可选)
|
||||
- due: 截止日期 YYYY-MM-DD 或毫秒时间戳 (可选)
|
||||
- recurrence: 循环待办规则 (可选,需同时有 due);字符串内需含换行,与 dws --recurrence 一致
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
ALLOWED_PRIORITIES = {10, 20, 30, 40}
|
||||
DATE_PATTERN = re.compile(r'^\d{4}-\d{2}-\d{2}$')
|
||||
MAX_FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return {'dry_run': True}
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f" ✗ 错误:{result.stderr.strip()}")
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
print(' ✗ 命令执行超时', file=sys.stderr)
|
||||
return None
|
||||
except (json.JSONDecodeError, FileNotFoundError) as e:
|
||||
print(f" ✗ 错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def parse_due(due_value) -> Optional[str]:
|
||||
if not due_value:
|
||||
return None
|
||||
due_str = str(due_value)
|
||||
if due_str.isdigit() and len(due_str) >= 10:
|
||||
return due_str
|
||||
if DATE_PATTERN.match(due_str):
|
||||
dt = datetime.strptime(due_str, '%Y-%m-%d')
|
||||
dt = dt.replace(hour=23, minute=59, second=59)
|
||||
return str(int(dt.timestamp() * 1000))
|
||||
print(f" ⚠ 无法解析截止时间:{due_value},跳过")
|
||||
return None
|
||||
|
||||
|
||||
def validate_todo(item: Dict[str, Any], idx: int) -> bool:
|
||||
if not isinstance(item, dict):
|
||||
print(f" ✗ #{idx+1} 不是有效对象")
|
||||
return False
|
||||
if not item.get('title', '').strip():
|
||||
print(f" ✗ #{idx+1} 缺少 title")
|
||||
return False
|
||||
if not item.get('executors', '').strip():
|
||||
print(f" ✗ #{idx+1} 缺少 executors")
|
||||
return False
|
||||
priority = item.get('priority')
|
||||
if priority is not None and int(priority) not in ALLOWED_PRIORITIES:
|
||||
print(f" ✗ #{idx+1} 无效优先级:{priority}")
|
||||
return False
|
||||
recurrence = item.get('recurrence')
|
||||
if recurrence and not str(recurrence).strip():
|
||||
print(f" ✗ #{idx+1} recurrence 不能为空字符串")
|
||||
return False
|
||||
if recurrence and not item.get('due'):
|
||||
print(f" ✗ #{idx+1} 设置 recurrence 时必须提供 due")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
args = [a for a in sys.argv[1:] if a != '--dry-run']
|
||||
if not args:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
file_path = Path(args[0])
|
||||
if not file_path.exists():
|
||||
print(f"错误:文件不存在:{file_path}")
|
||||
sys.exit(1)
|
||||
if file_path.stat().st_size > MAX_FILE_SIZE:
|
||||
print(f"错误:文件过大 (限制 {MAX_FILE_SIZE // 1024}KB)")
|
||||
sys.exit(1)
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
todos = json.load(f)
|
||||
if not isinstance(todos, list) or not todos:
|
||||
print('错误:JSON 文件必须是非空数组')
|
||||
sys.exit(1)
|
||||
|
||||
for i, item in enumerate(todos):
|
||||
if not validate_todo(item, i):
|
||||
sys.exit(1)
|
||||
|
||||
print(f"📋 准备创建 {len(todos)} 条待办\n")
|
||||
success, fail = 0, 0
|
||||
for i, item in enumerate(todos):
|
||||
title = item['title'].strip()
|
||||
cmd_args = [
|
||||
'todo', 'task', 'create',
|
||||
'--title', title,
|
||||
'--executors', item['executors'].strip(),
|
||||
'--format', 'json',
|
||||
]
|
||||
priority = item.get('priority')
|
||||
if priority is not None:
|
||||
cmd_args.extend(['--priority', str(int(priority))])
|
||||
due = parse_due(item.get('due'))
|
||||
if due:
|
||||
cmd_args.extend(['--due', due])
|
||||
recurrence = item.get('recurrence')
|
||||
if recurrence:
|
||||
rr = str(recurrence).replace('\\n', '\n')
|
||||
cmd_args.extend(['--recurrence', rr])
|
||||
|
||||
result = run_dws(cmd_args, dry_run=dry_run)
|
||||
if result:
|
||||
print(f" ✓ [{i+1}/{len(todos)}] {title}")
|
||||
success += 1
|
||||
else:
|
||||
print(f" ✗ [{i+1}/{len(todos)}] {title}")
|
||||
fail += 1
|
||||
|
||||
print(f"\n完成: 成功 {success}, 失败 {fail}")
|
||||
sys.exit(0 if fail == 0 else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
查询今天/明天/本周未完成的待办并汇总输出
|
||||
|
||||
用法:
|
||||
python todo_daily_summary.py # 默认查今天
|
||||
python todo_daily_summary.py today # 今天的待办
|
||||
python todo_daily_summary.py tomorrow # 明天的待办
|
||||
python todo_daily_summary.py week # 本周的待办
|
||||
python todo_daily_summary.py --dry-run # 仅显示将执行的命令
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
PRIORITY_MAP = {10: '低', 20: '普通', 30: '较高', 40: '紧急'}
|
||||
PAGE_SIZE = 50
|
||||
MAX_PAGES = 10
|
||||
|
||||
|
||||
def run_dws(args: List[str], dry_run: bool = False) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
print('错误:命令执行超时', file=sys.stderr)
|
||||
return None
|
||||
except (json.JSONDecodeError, FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_date_range(scope: str):
|
||||
now = datetime.now()
|
||||
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if scope == 'today':
|
||||
return today_start, today_start + timedelta(days=1)
|
||||
elif scope == 'tomorrow':
|
||||
tmr = today_start + timedelta(days=1)
|
||||
return tmr, tmr + timedelta(days=1)
|
||||
elif scope == 'week':
|
||||
week_start = today_start - timedelta(days=today_start.weekday())
|
||||
return week_start, week_start + timedelta(days=7)
|
||||
return today_start, today_start + timedelta(days=1)
|
||||
|
||||
|
||||
def fetch_all_todos(
|
||||
dry_run: bool = False,
|
||||
) -> List[Dict[str, Any]]:
|
||||
all_todos: List[Dict[str, Any]] = []
|
||||
for page in range(1, MAX_PAGES + 1):
|
||||
data = run_dws([
|
||||
'todo', 'task', 'list',
|
||||
'--page', str(page),
|
||||
'--size', str(PAGE_SIZE),
|
||||
'--status', 'false',
|
||||
'--format', 'json',
|
||||
], dry_run=dry_run)
|
||||
if dry_run:
|
||||
return []
|
||||
if not data:
|
||||
break
|
||||
items = data if isinstance(data, list) else data.get(
|
||||
'result', data.get('todoCards', [])
|
||||
)
|
||||
if not items or not isinstance(items, list):
|
||||
break
|
||||
all_todos.extend(items)
|
||||
if len(items) < PAGE_SIZE:
|
||||
break
|
||||
return all_todos
|
||||
|
||||
|
||||
def format_priority(p) -> str:
|
||||
try:
|
||||
return PRIORITY_MAP.get(int(p), str(p))
|
||||
except (ValueError, TypeError):
|
||||
return '普通'
|
||||
|
||||
|
||||
def format_due(due_ms) -> str:
|
||||
if not due_ms:
|
||||
return '无截止时间'
|
||||
try:
|
||||
dt = datetime.fromtimestamp(int(due_ms) / 1000)
|
||||
return dt.strftime('%Y-%m-%d %H:%M')
|
||||
except (ValueError, TypeError, OSError):
|
||||
return str(due_ms)
|
||||
|
||||
|
||||
def filter_by_due(
|
||||
todos: List[Dict[str, Any]], start: datetime, end: datetime,
|
||||
) -> List[Dict[str, Any]]:
|
||||
start_ms = int(start.timestamp() * 1000)
|
||||
end_ms = int(end.timestamp() * 1000)
|
||||
result = []
|
||||
for t in todos:
|
||||
due = t.get('dueTime') or t.get('due')
|
||||
if not due:
|
||||
result.append(t)
|
||||
continue
|
||||
try:
|
||||
due_val = int(due)
|
||||
if start_ms <= due_val < end_ms:
|
||||
result.append(t)
|
||||
except (ValueError, TypeError):
|
||||
result.append(t)
|
||||
return result
|
||||
|
||||
|
||||
def print_summary(
|
||||
todos: List[Dict[str, Any]], scope: str,
|
||||
start: datetime, end: datetime,
|
||||
):
|
||||
scope_label = {
|
||||
'today': '今天', 'tomorrow': '明天', 'week': '本周',
|
||||
}.get(scope, scope)
|
||||
print(f"\n📋 {scope_label}未完成待办 "
|
||||
f"({start.strftime('%m-%d')} ~ {end.strftime('%m-%d')})")
|
||||
print('=' * 50)
|
||||
if not todos:
|
||||
print(' ✅ 暂无待办,轻松一下!')
|
||||
return
|
||||
urgent = [t for t in todos if format_priority(
|
||||
t.get('priority')) == '紧急']
|
||||
if urgent:
|
||||
print(f"\n🔴 紧急 ({len(urgent)} 条)")
|
||||
for t in urgent:
|
||||
title = t.get('subject') or t.get('title', '无标题')
|
||||
print(f" • {title} ⏰ {format_due(t.get('dueTime'))}")
|
||||
normal = [t for t in todos if t not in urgent]
|
||||
if normal:
|
||||
print(f"\n📌 其他 ({len(normal)} 条)")
|
||||
for t in normal:
|
||||
title = t.get('subject') or t.get('title', '无标题')
|
||||
pri = format_priority(t.get('priority'))
|
||||
print(f" • [{pri}] {title} ⏰ {format_due(t.get('dueTime'))}")
|
||||
print(f"\n合计: {len(todos)} 条待办")
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
args = [a for a in sys.argv[1:] if a != '--dry-run']
|
||||
scope = args[0] if args else 'today'
|
||||
if scope not in ('today', 'tomorrow', 'week'):
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
start, end = get_date_range(scope)
|
||||
todos = fetch_all_todos(dry_run=dry_run)
|
||||
if dry_run:
|
||||
return
|
||||
filtered = filter_by_due(todos, start, end)
|
||||
print_summary(filtered, scope, start, end)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
扫描已过截止时间但未完成的待办,输出逾期清单
|
||||
|
||||
用法:
|
||||
python todo_overdue_check.py
|
||||
python todo_overdue_check.py --dry-run
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
PAGE_SIZE = 50
|
||||
MAX_PAGES = 10
|
||||
PRIORITY_MAP = {10: '低', 20: '普通', 30: '较高', 40: '紧急'}
|
||||
|
||||
|
||||
def run_dws(
|
||||
args: List[str], dry_run: bool = False,
|
||||
) -> Optional[Any]:
|
||||
cmd = ['dws'] + args
|
||||
if dry_run:
|
||||
print(f"[dry-run] {' '.join(cmd)}")
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:{result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError,
|
||||
FileNotFoundError) as e:
|
||||
print(f"错误:{e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def fetch_all_undone(dry_run: bool = False) -> List[Dict[str, Any]]:
|
||||
all_todos: List[Dict[str, Any]] = []
|
||||
for page in range(1, MAX_PAGES + 1):
|
||||
data = run_dws([
|
||||
'todo', 'task', 'list',
|
||||
'--page', str(page), '--size', str(PAGE_SIZE),
|
||||
'--status', 'false', '--format', 'json',
|
||||
], dry_run=dry_run)
|
||||
if dry_run or not data:
|
||||
break
|
||||
items = (data if isinstance(data, list)
|
||||
else data.get('result', data.get('todoCards', [])))
|
||||
if not items or not isinstance(items, list):
|
||||
break
|
||||
all_todos.extend(items)
|
||||
if len(items) < PAGE_SIZE:
|
||||
break
|
||||
return all_todos
|
||||
|
||||
|
||||
def find_overdue(todos: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
now_ms = int(datetime.now().timestamp() * 1000)
|
||||
overdue = []
|
||||
for t in todos:
|
||||
due = t.get('dueTime') or t.get('due')
|
||||
if not due:
|
||||
continue
|
||||
try:
|
||||
if int(due) < now_ms:
|
||||
overdue.append(t)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
return overdue
|
||||
|
||||
|
||||
def days_overdue(due_ms) -> int:
|
||||
now = datetime.now()
|
||||
try:
|
||||
due_dt = datetime.fromtimestamp(int(due_ms) / 1000)
|
||||
return max(0, (now - due_dt).days)
|
||||
except (ValueError, TypeError, OSError):
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
todos = fetch_all_undone(dry_run=dry_run)
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
overdue = find_overdue(todos)
|
||||
overdue.sort(
|
||||
key=lambda t: int(t.get('dueTime') or t.get('due', 0))
|
||||
)
|
||||
|
||||
print(f"\n⏰ 逾期待办检查 ({datetime.now().strftime('%Y-%m-%d %H:%M')})")
|
||||
print('=' * 50)
|
||||
|
||||
if not overdue:
|
||||
print(' ✅ 没有逾期待办,继续保持!')
|
||||
return
|
||||
|
||||
for t in overdue:
|
||||
title = t.get('subject') or t.get('title', '无标题')
|
||||
due = t.get('dueTime') or t.get('due')
|
||||
days = days_overdue(due)
|
||||
pri = PRIORITY_MAP.get(
|
||||
int(t.get('priority', 20)), '普通'
|
||||
)
|
||||
due_str = datetime.fromtimestamp(
|
||||
int(due) / 1000
|
||||
).strftime('%Y-%m-%d')
|
||||
print(f" 🔴 [{pri}] {title}")
|
||||
print(f" 截止: {due_str} 逾期: {days} 天")
|
||||
|
||||
print(f"\n合计: {len(overdue)} 条逾期待办")
|
||||
sys.exit(1 if overdue else 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
上传附件到钉钉 AI 表格 attachment 字段
|
||||
|
||||
完整流程(内部自动执行 3 步):
|
||||
1. dws aitable attachment upload → 获取 uploadUrl + fileToken
|
||||
2. HTTP PUT 上传文件到 OSS
|
||||
3. 返回 fileToken,可直接用于 record create/update
|
||||
|
||||
用法:
|
||||
python upload_attachment.py <baseId> <filePath>
|
||||
|
||||
输出 (JSON):
|
||||
{ "fileToken": "ft_xxx", "fileName": "report.pdf", "size": 204800 }
|
||||
|
||||
然后在 record create/update 中使用:
|
||||
dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \
|
||||
--records '[{"cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]' --format json
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import mimetypes
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
RESOURCE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{8,128}$')
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB
|
||||
|
||||
|
||||
def validate_resource_id(resource_id: str) -> bool:
|
||||
return bool(resource_id and RESOURCE_ID_PATTERN.match(resource_id.strip()))
|
||||
|
||||
|
||||
def detect_mime_type(file_path: Path) -> str:
|
||||
"""根据文件扩展名推断 MIME type。"""
|
||||
mime_type, _ = mimetypes.guess_type(str(file_path))
|
||||
return mime_type or 'application/octet-stream'
|
||||
|
||||
|
||||
def run_dws(args: list) -> Optional[Dict[str, Any]]:
|
||||
"""调用 dws 命令并返回解析后的 JSON 结果。"""
|
||||
cmd = ['dws'] + args
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
||||
if result.returncode != 0:
|
||||
print(f"错误:dws 命令失败: {result.stderr.strip()}", file=sys.stderr)
|
||||
return None
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except json.JSONDecodeError:
|
||||
print(f"错误:无法解析 dws 响应: {result.stdout[:300]}", file=sys.stderr)
|
||||
return None
|
||||
except subprocess.TimeoutExpired:
|
||||
print('错误:dws 命令超时(60 秒)', file=sys.stderr)
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
print('错误:未找到 dws 命令,请确认已安装并在 PATH 中', file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def upload_to_oss(upload_url: str, file_path: Path, mime_type: str) -> bool:
|
||||
"""通过 HTTP PUT 上传文件到 OSS。"""
|
||||
file_data = file_path.read_bytes()
|
||||
req = Request(upload_url, data=file_data, method='PUT')
|
||||
req.add_header('Content-Type', mime_type)
|
||||
|
||||
try:
|
||||
with urlopen(req, timeout=120) as resp:
|
||||
if resp.status == 200:
|
||||
return True
|
||||
print(f"错误:OSS 上传失败,HTTP {resp.status}", file=sys.stderr)
|
||||
return False
|
||||
except HTTPError as e:
|
||||
print(f"错误:OSS 上传 HTTP 错误 {e.code}: {e.reason}", file=sys.stderr)
|
||||
return False
|
||||
except URLError as e:
|
||||
print(f"错误:OSS 上传网络错误: {e.reason}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def upload_attachment(base_id: str, file_path_str: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
执行完整的附件上传流程:
|
||||
1. prepare_attachment_upload → uploadUrl + fileToken
|
||||
2. PUT 文件到 OSS
|
||||
3. 返回 fileToken 信息
|
||||
"""
|
||||
# 验证文件
|
||||
file_path = Path(file_path_str).resolve()
|
||||
if not file_path.exists():
|
||||
print(f"错误:文件不存在: {file_path}", file=sys.stderr)
|
||||
return None
|
||||
if not file_path.is_file():
|
||||
print(f"错误:不是文件: {file_path}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
file_size = file_path.stat().st_size
|
||||
if file_size <= 0:
|
||||
print("错误:文件为空", file=sys.stderr)
|
||||
return None
|
||||
if file_size > MAX_FILE_SIZE:
|
||||
print(f"错误:文件过大 ({file_size:,} 字节,限制 {MAX_FILE_SIZE:,} 字节)", file=sys.stderr)
|
||||
return None
|
||||
|
||||
file_name = file_path.name
|
||||
mime_type = detect_mime_type(file_path)
|
||||
|
||||
# 步骤 1: prepare_attachment_upload
|
||||
print(f"步骤 1/3: 准备上传 {file_name} ({file_size:,} 字节, {mime_type})...", file=sys.stderr)
|
||||
dws_args = [
|
||||
'aitable', 'attachment', 'upload',
|
||||
'--base-id', base_id,
|
||||
'--file-name', file_name,
|
||||
'--size', str(file_size),
|
||||
'--mime-type', mime_type,
|
||||
'--format', 'json',
|
||||
]
|
||||
result = run_dws(dws_args)
|
||||
if not result:
|
||||
return None
|
||||
|
||||
status = result.get('status', '')
|
||||
if status != 'success':
|
||||
error = result.get('error', {})
|
||||
print(f"错误:准备上传失败: {error.get('message', json.dumps(error, ensure_ascii=False))}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
data = result.get('data', {})
|
||||
upload_url = data.get('uploadUrl', '')
|
||||
file_token = data.get('fileToken', '')
|
||||
|
||||
if not upload_url or not file_token:
|
||||
print(f"错误:返回数据缺少 uploadUrl 或 fileToken: {json.dumps(data, ensure_ascii=False)}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# 步骤 2: PUT 文件到 OSS
|
||||
print(f"步骤 2/3: 上传文件到 OSS...", file=sys.stderr)
|
||||
if not upload_to_oss(upload_url, file_path, mime_type):
|
||||
return None
|
||||
|
||||
# 步骤 3: 返回 fileToken
|
||||
print(f"步骤 3/3: 上传完成!", file=sys.stderr)
|
||||
output = {
|
||||
"fileToken": file_token,
|
||||
"fileName": file_name,
|
||||
"size": file_size,
|
||||
"mimeType": mime_type,
|
||||
}
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print(__doc__)
|
||||
print('用法:')
|
||||
print(' python upload_attachment.py <baseId> <filePath>')
|
||||
print()
|
||||
print('示例:')
|
||||
print(' python upload_attachment.py G1DKw2zgV2bEk6PMSBooNxlEVB5r9YAn ./report.pdf')
|
||||
print()
|
||||
print('然后在 record create 中使用返回的 fileToken:')
|
||||
print(' dws aitable record create --base-id <BASE_ID> --table-id <TABLE_ID> \\')
|
||||
print(' --records \'[{"cells":{"fldAttachId":[{"fileToken":"ft_xxx"}]}}]\' --format json')
|
||||
sys.exit(1)
|
||||
|
||||
base_id = sys.argv[1]
|
||||
file_path = sys.argv[2]
|
||||
|
||||
if not validate_resource_id(base_id):
|
||||
print('错误:无效的 baseId 格式', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
result = upload_attachment(base_id, file_path)
|
||||
if result is None:
|
||||
sys.exit(1)
|
||||
|
||||
# 正常输出到 stdout(JSON 格式,方便解析)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user