- Add testseam.Protect for save-and-restore seams with no up-front stub
value (e.g. os.Args mutated by the code under test).
- Migrate all 35 remaining manual prev/assign/t.Cleanup-restore trios to
testseam.Swap/Protect across app, auth, cli, event, helpers, output,
pipeline, and shortcut tests; restores can no longer be forgotten.
- check-schema-catalog.sh: fail closed when a manual seam restore
reappears in any *_test.go (internal/testseam exempt).
Seam injection is now a mechanism, not a convention.
* feat(output): add -f ndjson (implemented) and scaffold -f csv (#252)
`larksuite/cli` exposes --format ndjson / csv; dws only had json/table/raw/
pretty. This adds both as recognised global formats:
- ndjson: fully implemented (internal/output/ndjson.go) — top-level arrays
and well-known list wrappers ({items|results|data|records|...}) emit one
compact JSON document per line; anything else degrades to a single line.
Streaming-friendly counterpart to `-f json`.
- csv: scaffolded (internal/output/csv.go) — writeCSV currently returns a
clear "not implemented (#252)" error rather than silently degrading to
JSON. A TODO block spells out the planned implementation (reuse the table
renderer's extractRowsFromMap/rowsFromSlice flattening + encoding/csv).
Wired through Write() and normalizeFormat(); --format help string now lists
ndjson (csv to be added when writeCSV lands). Tests cover ndjson rendering
(array / wrapped-list / scalar) and pin the csv not-implemented contract.
Skeleton PR for #252 — ndjson is shippable as-is; csv is left for a
follow-up commit on this branch.
* feat(output): implement -f csv (#252)
Completes the CSV half of the format work. writeCSV mirrors the shape
decisions `-f table` already makes (reuses normalizePayload /
unwrapPrimaryObject / extractRowsFromMap / rowsFromSlice / formatValue) so
columns and value flattening are consistent between the two formats:
- a list of objects (bare or wrapped under items/results/data/records/...) →
header row + one row per element; union of keys sorted; missing values are
empty cells; nested objects/arrays render as compact JSON in the cell;
sibling metadata of the list (total, hasMore, ...) is dropped.
- a single object → two-column key,value CSV (keys sorted).
- a non-uniform list / scalar → single-column `value` CSV.
- empty / nil → empty document.
encoding/csv.Writer handles RFC-4180 quoting (commas, quotes, newlines);
cells go through formatValue (also strips terminal control sequences, same
as the table renderer). `--fields` projection composes for free since
WriteFiltered applies SelectFields before Write.
--format help now lists csv; FormatCSV doc comment dropped the WIP marker;
the not-implemented test is replaced with real coverage (list with
comma/CJK/nested-array-as-JSON, wrapped-list-with-metadata, single object,
scalar) plus a --fields composition test.
* feat(output): broadcast list metadata as trailing columns in -f csv (#252)
Per review preference: instead of dropping the list's sibling metadata
(total, hasMore, ...) when rendering {records:[...], total:N} as CSV, append
each meta key as a trailing column repeated on every row, so a CSV consumer
never silently loses it (CSV has no "footer table" the way the table
renderer does). Meta keys colliding with a data column are skipped; an empty
list still emits the header (data + meta) plus one row of empty data cells
carrying the meta values. New broadcastMeta helper; doc comment + tests
updated (incl. an empty-list-with-metadata case).
* feat(output): recognise real DingTalk envelope keys in -f csv/ndjson/table
之前 -f csv 和 -f ndjson 的 list 检测白名单只认 items/results/data/list/
records/tools/servers/products,但真实钉钉响应用的是 result(单数直接数组
或一层包裹)/documents/emailAccounts/todoCards/events/messages,导致大
部分 list 命令的 csv 输出退化成 key,value 二列、ndjson 退化成整包一行。
修复点:
1. preferredListKeys 扩展加上真实 envelope key(result/documents/
emailAccounts/todoCards/events/messages),并把它升级为 csv/table/
ndjson 共享的"单一事实源"——filter.go 的 findDataList 不再维护自己
的本地副本,直接复用这个列表。
2. extractRowsFromMap 改为委托 findDataList,自动获得"一层深度"的
wrapper 支持({result: {todoCards: [...]}} 这种 envelope 现在能识
别)。meta 合并:outer + inner 双层 sibling 拉通,outer 同名 key
优先(避免 inner 把外层 success/total 等覆盖掉)。
3. writeTableish 和 writeCSV 调整 unwrapPrimaryObject 和
extractRowsFromMap 的优先级——先试 list 检测,没命中再走 unwrap,
避免 {result: {todoCards: [...]}} 被 unwrap 剥掉外层后直接走
key,value 分支。
4. findDataList 允许"空数组+preferred key" 命中,保留原来"空 list +
metadata 仍渲染为表格 + meta 广播一行"的行为。
新增 TestTabularDetectsRealDingTalkEnvelopes:四个真实 envelope 形态
(contact user search 的 result 直接数组、doc search 的 documents 顶层、
mail mailbox list 的 emailAccounts、todo task list 的 result.todoCards
一层深度)验证 ndjson 行数和 csv 表头都符合预期。
真接口回归(已登录态跑过):
- dws contact user search -f csv → name/userId 列正常出表
- dws todo task list -f ndjson → 20 行一条任务,可 jq -r .subject 直接管
- dws doc search -f csv → 10 行 + nextPageToken 等 meta 广播尾列
- dws schema -f csv → 无回归
Closes part of #252 follow-up.
* feat(compat): merge same-name subcommands under shared parent
When multiple server entries attach to the same parent and their cli.command
collides with a subcommand already in the parent tree, the incoming subcommand's
children are merged recursively into the existing one instead of producing a
duplicate sibling. Leaf-name collisions resolve first-wins.
Fixes the duplicate `group` / `message` rows in `dws chat --help` that surfaced
when bot capabilities were distributed across chat.group.members and
chat.message subtrees.
* feat(ir): carry FlagOverlay and ToolAnnotations into canonical catalog
FlagOverlay mirrors the per-parameter CLI overlay (alias, transform,
transformArgs, env default, default value, hidden) sourced from
market.CLIToolOverride.Flags.
ToolAnnotations mirrors MCP 2025+ tool annotations with nullable hints
(destructive / read-only / idempotent / open-world) so absence means
"unknown" rather than "false".
BuildCatalog now propagates both through the canonical layer, with tests
covering group metadata and flag-overlay passthrough.
* feat(output): add -f pretty for schema-aware colored output
dws schema now supports -f pretty alongside json / raw. Pretty mode
partitions output by product / tool / parameter / enum and applies ANSI
color so humans can browse the catalog without piping through jq.
json / raw behaviour is unchanged.
docs/reference.md gains a Schema Introspection section documenting
dws schema and the new pretty format.
* feat(skills): refresh 13-product references; add devdoc / drive / oa
SKILL.md cli_version bumped to >=1.0.15; product index reordered to match
the v1.0.15 command surface.
New references:
- devdoc.md: Open Platform documentation search
- drive.md: DingTalk drive (promoted out of Coming soon)
- oa.md: OA approval end-to-end flow
Major rewrites for aitable (dashboard / chart share, import/export,
attachment flow), chat (bot capabilities merged into chat.message and
chat.group.members), calendar (event suggest / attachments), doc (comment
subtree, file create, upload/download flow), and minutes (list
mine/shared/all unified, record subcommands split).
Minor alignment touch-ups for contact / report / todo flag names.
* docs(release): sync v1.0.15 notes — 159 commands across 13 products
README Key Services fully refreshed: chat 23, calendar 14, aitable 41,
doc 21, minutes 19; drive promoted out of Coming soon; workbench and
standalone bot rows removed; Quick Start expanded to 7 examples spanning
doc / minutes / drive; Coming soon trimmed to mail / conference / aiapp /
live / wiki.
Adds docs/command-index.md, an auto-generated English listing of all 159
runtime commands with description and when-to-use guidance aimed at AI
agents. Replaces the ad-hoc command-index.pre.* / command-index.full.*
snapshots used during development.
CHANGELOG 1.0.15 records the compat subcommand-merging feature, the new
command index, and a flag-naming cleanup across chat / calendar / drive /
minutes / contact / devdoc.
- Support dot-notation for nested fields (e.g. --fields response.content)
- Support array index access in --fields (e.g. response.items[0])
- Add field path parsing and recursive extraction logic
- Expand test coverage for nested and edge-case filtering
- Add --fields for comma-separated field selection on JSON output
- Add --jq for jq expression filtering powered by gojq library
- Integrate filters into output formatter pipeline
- Add internal/validate with control char rejection, ANSI stripping,
URL query/fragment stripping, and terminal sanitization
- Refactor output/sanitize.go to delegate to validate package
- Update sanitize test to reflect ANSI escape stripping behavior