* feat(transform): add file_read transform for UTF-8 file / stdin content (#277)
Introduces a new ApplyTransform case "file_read":
- Input: a string flag value treated as a UTF-8 file path
- Special case: the path "-" reads from stdin
- Output: the file contents as a string
- Errors surface as validation errors (exit 2) — non-UTF-8, missing
file, empty path, and non-string input all reject cleanly
This is a foundational primitive intended to compose with envelope
schema features so a CLI flag like --content-file can carry a path
that ultimately feeds a string-typed MCP parameter with the file's
contents.
## Scope note — MapsTo intentionally NOT included
The original proposal in #277 paired this transform with a new
CLIFlagOverride.MapsTo field that retargets a flag's value to a
different MCP parameter (e.g. --content / --content-file both feed
the upstream `markdown` param). Pre-production end-to-end validation
(see #282) showed that the MCP registry server-side schema does not
currently recognise `mapsTo` and strips it on serialisation. Every
other new envelope field (hidden / required / transform /
mutuallyExclusive / requireOneOf) survives — only mapsTo is dropped.
Shipping MapsTo without server-side support would land dead client
code. The MapsTo field + sourceFlag transform guard were therefore
removed from this PR; they will return in a follow-up PR once #282
(server-side schema acknowledgement of mapsTo) is resolved. The
file_read transform stays here because it composes with multiple
mechanisms beyond MapsTo and is independently testable.
## Tests
Six new cases in internal/compat/transform_test.go cover the
contract: literal file, stdin via "-", missing file, non-UTF-8
input, empty path, non-string input. All pass under both `go test`
and `go test -coverprofile`.
Refs #277, blocked-by #282
* feat(schema): add CLIFlagOverride.MapsTo for sibling-flag routing (#277#282)
Adds the `MapsTo` field on `CLIFlagOverride` and wires the dispatch loop
in `compat.buildOverrideBindings` so a flag's final value (post-transform
or literal) is routed into a different MCP parameter slot than its own
property name. This lets two sibling CLI flags feed a single upstream
parameter — the canonical case being `--content` (literal) + `--content-file`
(transform: file_read) both mapping to `markdown`.
Tool-level `CLIToolOverride.MutuallyExclusive` (cobra MarkFlagsMutuallyExclusive)
is the right partner for guarding "set one, not both" at parse time; no
new exclusion machinery is added.
Closes the client-side gap previously misattributed to a server-side
mapsTo strip in #282. Once a doc envelope with mapsTo lands in pre-prod,
end-to-end `--content-file` becomes shippable, finishing #277 Step 1b.
Test coverage (internal/compat/dynamic_commands_test.go):
- MapsTo without transform: literal --content → params[markdown]
- MapsTo with file_read transform: --content-file path → params[markdown]
- Sibling flags both mapsTo same target, only one set: clean routing
- Sibling flags both set: rejected by tool-level MutuallyExclusive (regression)
Backward-compat: empty MapsTo preserves existing params[propertyName] write
semantics for every existing envelope.
Two generic CLI envelope schema enhancements that close gaps surfaced by
the cli_to_mcp test suite without resorting to product-specific helpers:
1. CLIToolOverride.CLIAliases (registry.go + dynamic_commands.go)
- Lets a single MCP tool register additional cobra command aliases via
envelope JSON (e.g. `range read` accepts `range get`, `member list`
accepts `member ls`). Plumbs through the existing Route.Aliases ->
cobra.Command.Aliases path; conflicts with siblings are silently
skipped by cobra.
2. json_parse_strict transform (transform.go)
- Strict JSON variant of json_parse that does NOT fall back to YAML.
Use when the upstream tool requires a structured array/object value
and silently coercing malformed input to a scalar string would mask
a real user error (observed: filter-view --criteria 'NOT_VALID_JSON'
was being accepted and quietly creating an empty-criteria view).
* fix(compat): accept YAML flow as a fallback for json_parse transform
Users who run `dws aitable table create --fields [{fieldName: 标题, ...}]`
hit a shell glob error (`zsh: bad pattern: [{fieldName:`) because the
unquoted brackets are interpreted as a zsh pattern. Even when the shell
is placated with outer quotes, the strict-JSON parser still rejected the
natural form that drops quotes around keys and string values.
transformJSONParse now tries strict JSON first (unchanged fast path) and,
on failure, falls back to yaml.Unmarshal. YAML's flow syntax is a
superset of JSON that accepts `{key: value}` without surrounding quotes,
so a single set of outer quotes is all the user needs:
dws aitable table create --fields '[{fieldName: 标题, type: text}]'
Already-valid strict JSON is unaffected. The @file syntax (`--fields
@path.json`) continues to work through the same transform.
The terminal error message, if both parsers fail, now points users at
the two working forms (quoted YAML flow or @file).
* fix(helpers): route chat send-by-bot to bot product
`chat message send-by-bot` previously stamped its invocation with
CanonicalProduct="chat", routing it through the chat product's auth /
server dispatch. The command is semantically a bot operation — switch
it to "bot" so auth client selection and downstream routing match the
actual MCP server. Extends chat_test.go with single-chat and group-chat
routing assertions.