* fix: honor global --jq/--fields on product commands; round-2 QA fixes
Make the global --jq / --fields output filters actually work for the
product (MCP) commands. The helper Formatter used by every product
command ignored them, so they were silent no-ops there (they already
worked for `dws api`). Expose Fields()/JQ() on the ToolCaller interface
and apply the existing output.WriteFiltered path in the helper
Formatter's PrintJSON. The handful of bespoke utility commands
(auth/config/profile/...) still encode directly and are documented as
such.
Additional CLI fixes surfaced by the second real-machine QA pass:
- sheet write-image: emit clean JSON under --format json (suppress the
progress lines that leaked onto stdout, same as media-upload/export)
- sheet range batch-set-style: under --format json, collect per-item
results into a single JSON object instead of printing N separate ones
- chat download-media: create the output directory when missing and
strip URL-encoded path separators from the inferred filename so the
file actually lands instead of failing on a missing subdirectory
- pat chmod, aitable, sheet, chat, attendance: correct --help text
(real scope names, non-existent subcommands, flag requiredness,
alxs -> axls typo)
Helper scripts (mono and multi):
- minutes_extract_todos: parse dingtalkTodoList/actions (there is no
todos key), so todos are no longer silently dropped
- sync the multi copies of chat_export_messages / chat_history_with_user
(were crashing with AttributeError), minutes_list_parse /
minutes_recent_summary, and calendar_free_slot_finder to the fixed
mono versions
Skill docs (mono and multi): correct return-structure keys, flag names,
deprecated command routing (doc download -> drive download), enum values
and server-side limitations across products; update the global
reference to note --jq/--fields now apply to product commands.
* fix(skill): make skill setup --dry-run a no-op preview; doc/help fixups
skill setup ignored the global --dry-run flag and always wrote the skill
files (overwriting an existing install). Short-circuit into a preview
that lists the source, target dirs and selected sub-skills without
touching the filesystem.
Also correct a few doc/help mismatches found in the round-3 health check:
- attendance vacation balance/records quick-reference examples were
missing the required --leave-code flag
- mail mailbox list --help described the returned field as "mailboxes"
but the real field is "emailAccounts"
* docs: clarify --fields projects top-level/list keys, use --jq for nested
* docs: drop QA voice ("真机") and don't state env-specific quirks as absolute rules
The QA-driven doc/comment edits leaked test-process narration ("真机实测")
and this environment/account's quirks stated as universal rules into the
skill files, which are general-purpose instructions for any org/account.
Strip the "真机" narration everywhere; reword environment-specific findings
(PUBLIC sharing disabled by org policy, transient 1002, sender-open-dingtalk-id
behaviour) from absolute bans into conditional hints; keep genuinely
universal command behaviour, just without the QA voice.
Fix CLI command bugs surfaced by full real-machine QA:
- aitable: make chart/dashboard share update --enabled a string flag so
"--enabled false" disables instead of silently enabling (bool flag +
space-syntax help example inverted the action); clarify chart update
requires --config; make form get filter by view-id client-side so it
returns a single form; drop inline // comments from chart JSON examples
- chat: resolve conversation-info --user to openDingTalkId, register
--id/--conversation-id/--chat aliases; cap list-all-conversations
--limit at 100 and reject larger values instead of silent truncation;
detect webhook errcode failures instead of wrapping them as success;
remove duplicate group/members subcommand registration in help
- contact: register --dept/--depts as the primary dept flags to match
the RunE parsing (were only registered as --id/--ids)
- sheet: emit clean JSON for media-upload and export under --format json,
suppressing progress lines that leaked onto stdout
- wiki: correct node create --type enum (drop unsupported asheet, add
axls/able/appt/adraw/amind)
- ding: default message list --type to ALL since the server rejects an
empty type
Fix helper scripts (mono and multi):
- aitable import/export flag names and tableId length regex
- mail search --limit, contact dept response keys and userInfo nesting
- attendance_my_record whoami compatibility, calendar_schedule_meeting
event id unwrapping, drive_tree_list recursion via fileId, report
scripts migrated off deprecated report list/detail
Sync skill docs (mono and multi) to real-machine behavior across all
products: command indexes, flag names, enums, return-structure keys, and
cross-product intent routing; annotate genuinely server-side limitations
and the no-op global --jq/--fields flags.
Switches the discovery version code bamboo -> cedar and aligns the open edition CLI with dws-wukong across communication (calendar book/acl/attendee, minutes tag, mail folder/template/contact, chat file upload, todo add-attachment, attendance transforms) and structured-office (aitable advperm/view/section/workflow/record, sheet/drive/wiki/doc) domains. Includes output-envelope parity, parse_bool/attendance_class_check_time transforms, --calendar-id support, CHANGELOG 1.0.43 and README command-index refresh. cedar config validated on pre and prod endpoints.
* fix(drive): drop client-side Content-Type fallback on presigned OSS PUT
The drive helper used to set req.Header["Content-Type"] = fallbackMIME
whenever the prepare_upload response returned an empty headers map.
DingTalk drive uses OSS presigned URLs whose StringToSign is computed
against an empty Content-Type at signing time, so any client-side
Content-Type makes the signature OSS recomputes at PUT time differ
from the server's presignature → HTTP 403 SignatureDoesNotMatch.
This broke every `dws drive upload` for any file whose mime detects
to a non-empty value (image/png, application/pdf, etc.), which is
basically everything in practice.
Reproduction (against DingTalk drive):
dws drive upload --file any.png
→ [1/3] 获取上传凭证 any.png (X 字节, image/png)...
→ [2/3] 上传文件到 OSS...
→ OSS 上传失败 HTTP 403: SignatureDoesNotMatch
Fix: trust the server's headers map as authoritative. Empty map means
"no client-side headers needed" — do not infer and add Content-Type.
Removes the hasContentType / fallbackMIME path entirely; httpPutDriveFile
signature loses the fallbackMIME parameter.
Manual verification:
curl -X PUT -H "Content-Type:" --data-binary @file <same-presigned-url>
→ HTTP 200 (proves the only difference was the client-side Content-Type)
Note: aitable.go's upload-file helper deliberately keeps its
Set("Content-Type", mimeType) call because its OSS endpoint uses a
different signing mode (server includes the client-declared mime in
its signature computation, verified by running aitable upload-file
across 12 file types — all succeed). The two helpers must not be
unified without re-validating both endpoints.
Tests:
- TestHttpPutDriveFile_NoContentTypeWhenServerHeadersEmpty: guards
that an empty server headers map results in no Content-Type on PUT.
- TestHttpPutDriveFile_PassthroughServerHeaders: guards that server
headers (Content-Type, x-oss-*) are forwarded verbatim.
* feat(aitable): unhide attachment upload-file + clarify prepare-only command
AI agents that discover commands only via --help (e.g. Lobster, generic
LLM agents) currently hit a dead end when trying to upload an attachment
to an AITable:
- `dws aitable attachment` exposes only `upload` (prepare-only),
which returns uploadUrl + fileToken but does not actually upload.
- The real one-shot command `attachment upload-file` (which performs
prepare + HTTP PUT + return fileToken automatically) is marked
Hidden:true, so it is invisible in --help output.
Agents that don't read skills/references/products/aitable.md therefore
get stuck after step 1 — they call `upload`, receive an uploadUrl they
have no idea how to consume, attempt various wrong things (e.g. write
the uploadUrl into the record's attachment field as if it were a token),
fail, and fall back to "please use the UI to upload" messages, which
makes dws look broken even though the capability is fully implemented.
This is the same UX gap that lark-cli avoids with its highly visible
`base +record-upload-attachment` command — discoverability via --help
is the difference between "works for any agent" and "only works if the
agent reads our skill docs".
Changes:
- newAITableUploadFileCommand: remove Hidden:true so the command
appears in `dws aitable attachment --help`. Tighten Short to
explicitly mention the 3 steps it bundles. Long now also calls
out the prepare-only sibling and recommends this command as the
default for AI agents.
- newAITableAttachmentUploadCommand (prepare-only): add a Long
description that explicitly states this command is only step 1
of a 3-step flow, lists what an agent must do after (HTTP PUT,
then write fileToken into record attachment field with the exact
[{"fileToken":"ft_xxx"}] shape), and points to upload-file as
the recommended one-shot alternative. Updated Short to flag that
no file is uploaded by this command.
- aitable_upload_file_test: add TestAITableUploadFileCommandIsDiscoverable
to guard against re-introducing Hidden:true. The test includes a
rationale comment explaining the agent-discoverability gap.
The drive Content-Type fix in the preceding commit and this aitable
discoverability fix together restore end-to-end attachment upload
functionality for both human operators and AI agents that only read
--help.
* feat(root): show 'dws upgrade' guidance in dws --help when commands are missing
AI agents (and users) reading `dws --help` see only the discovered MCP
service list and utility command list, with no hint about what to do if
none of the listed commands fit their task. The natural failure mode is
to give up or hack around — but in many cases the right action is just
`dws upgrade`, because new capabilities and bugfixes ship continuously
and a missing command is usually a stale binary issue.
Two changes restore visibility of the root command's Long description so
this guidance can be surfaced:
- internal/app/root.go: set root.Long to a one-paragraph hint —
"if you hit a missing command, an error, or cannot complete the
task, try `dws upgrade` first; both the DingTalk OpenAPI surface
and dws CLI evolve continuously."
- internal/app/root_help.go: the custom SetHelpFunc that renders the
root help (renderRootHelp) replaces cobra's default template, which
had been silently dropping root.Long from --help output. Restore
rendering by appending root.Long (when non-empty) after the command
list, separated by a blank line. This matches cobra's default
behavior for the Long field while preserving the custom services /
utilities sections renderRootHelp introduced.
- internal/app/visibility_test.go: new TestRenderRootHelpIncludesLong
guards against re-introducing the regression. Uses a sentinel Long
string and asserts renderRootHelp output contains it verbatim. If a
future change rewrites the help renderer without preserving Long
rendering, this test fails immediately and reminds the author the
upgrade hint must stay visible.
Verified locally:
$ ./dws --help | tail -3
Use "dws <service> --help" for more information about a discovered MCP
service or "dws <command> --help" for utility commands.
提示: 如果遇到能力缺失、命令报错、新功能未注册、或无法完成任务,
请先用 'dws upgrade' 升级到最新版本后再试. 钉钉 OpenAPI 和 dws CLI
持续迭代, 新能力和 bugfix 会先在新版本上线.