Skip to content

fix: UX parser hardening (v2.2.1)#13

Merged
chenliuyun merged 5 commits intomainfrom
fix/ux-parser-hardening
Apr 20, 2026
Merged

fix: UX parser hardening (v2.2.1)#13
chenliuyun merged 5 commits intomainfrom
fix/ux-parser-hardening

Conversation

@chenliuyun
Copy link
Copy Markdown
Collaborator

Summary

Patch release 2.2.1 fixing three token-swallowing UX bugs reported against 2.2.0, plus ls alias and soft hint for uncached device IDs. Also extends the argParser hardening pattern to every remaining string/int/enum option across the CLI, and updates shell completion scripts to cover all 15 subcommands and new global flags.

Bugs fixed (from 2.2.0 regression report)

  • B.1 switchbot --profile devices list no longer swallows devices as the profile value. Now exits 2 with --profile requires a value but got "devices", which is a subcommand name.
  • B.2 switchbot --timeout devices list (missing value) now exits 2 with --timeout requires a numeric value, got "devices".
  • B.3 switchbot events mqtt-tail --max --help now exits 2 instead of silently hanging; subcommand argParser errors now correctly surface as exit 2 via recursive exitOverride.

New UX

  • switchbot devices ls — short alias for devices list.
  • switchbot devices command <id> ... prints a one-line soft hint to stderr when the device ID isn't in the local cache (non-blocking).
  • MQTT state='failed' (e.g. AWS IoT credential expiry) now triggers process.exit(1) instead of the confusing Node "Unsettled Top-Level Await" message.

Commits

  1. d133354 fix(cli): harden arg parsing to prevent token-swallowing on required-value flags
  2. 62910d9 feat(devices): add 'ls' alias for 'list' and soft hint for uncached devices
  3. a4f8cdc chore(release): bump to 2.2.1 and document UX parser hardening
  4. 4aeb057 fix(cli): extend arg-parser hardening to remaining commands
  5. 10cb99b fix(completion): cover all subcommands and new global flags

Test plan

  • npm run build — clean TS compile
  • npm test — 720/720 tests passing (added 14 arg-parser factory tests + regression cases for each B.1/B.2/B.3 bug + ls alias + soft hint on/off)
  • Manual regression:
    • switchbot --profile devices list → exit 2, "subcommand name" hint
    • switchbot --timeout devices list → exit 2, "must be an integer"
    • switchbot events mqtt-tail --max --help → exit 2, "requires a numeric value"
    • switchbot --profile=home devices list --help → exit 0 (still works)
    • switchbot devices ls --help → exit 0, shows list|ls

Compatibility

No breaking changes. No new public API, no new subcommands, no new dependencies. Semver patch.

chenliuyun added 5 commits April 20, 2026 07:53
…value flags

Commander's default behavior is to accept the next argv token as the value of a
required-value option, even if that token looks like another flag or a
subcommand name. This made commands like `switchbot --profile devices list` or
`switchbot events mqtt-tail --max --help` silently swallow `devices`/`--help`
and then fail confusingly downstream.

Introduce a small set of argParser factories (intArg, durationArg, stringArg,
enumArg) in src/utils/arg-parsers.ts that fail fast with a clear
InvalidArgumentError during parse:
- intArg rejects anything not a valid integer (incl. `-`-prefixed tokens)
- durationArg requires a parseable duration string
- stringArg rejects `--`-prefixed values and, when `disallow` is set, any
  known top-level subcommand name (fixes `--profile devices list`)
- enumArg restricts values to a fixed set

Wire these into global flags in src/index.ts and into `events mqtt-tail --max`,
`events tail --port/--max`, and `devices watch --interval/--max`.

Also propagate `exitOverride` to every registered subcommand so subcommand
argParser errors map to exit code 2 (previously produced exit 1 because the
root override wasn't inherited), and also add a fallback CommanderError catch
around `parseAsync` for errors that skip the handler entirely.

Includes a regression test for the `events mqtt-tail --max --help` case and
new coverage for the argParser factories.
…evices

- `switchbot devices ls` now resolves to `devices list` (common shorthand).
- When a user runs `devices command <id> <cmd>` against a deviceId that isn't
  in the local cache, print a one-line hint to stderr suggesting they run
  `devices list` first so command validation can kick in. Non-blocking: the
  request still goes through.
Patch release covering:
- Token-swallowing fix on required-value flags (--profile, --timeout, --max, …).
- `devices ls` alias + soft hint when commanding an uncached device.
- Correct exit code (2) on subcommand argParser errors.

README gets a short tip explaining the `--flag=value` escape hatch for when a
user really does want a value that looks like another flag.
Extends the argParser factories from commit d133354 to every remaining
required-value option across the command surface so no flag can silently
swallow the next argv token:

- batch:    --filter / --ids / --concurrency / --type / --idempotency-key-prefix
- cache:    --key as enumArg
- catalog:  --source as enumArg
- config:   --name / --profile-name / --token / --secret
- device-meta, expand, history, mcp, schema, webhook: all string/int/enum
  options now use the corresponding factory
- events (sinks): --path / --filter / --topic + every --*-url / --*-token /
  --*-chat / --*-webhook-id / --*-event-type
- watch:    remaining string options

Tests updated where the argParser-formatted error message replaces the
previous hand-rolled UsageError text. Net behavior is unchanged on the happy
path; invalid values now fail earlier (parse phase) with a consistent
"option '--X <Y>' argument 'Z' is invalid" prefix.
The previous completion scripts only enumerated `config / devices / scenes /
webhook / completion` and a tiny subset of global flags — anything typed at
`switchbot <Tab>` for `mcp`, `events`, `quota`, `catalog`, `cache`, `doctor`,
`schema`, `history`, `plan`, or `capabilities` produced no suggestions.

Update all four shell scripts (bash / zsh / fish / powershell) to enumerate:
- every top-level subcommand
- every subcommand of `events`, `quota`, `catalog`, `cache`, `history`, `plan`
- the new `ls` alias for `devices`
- every global flag introduced since v2.0 (`--format`, `--fields`,
  `--retry-on-429`, `--backoff`, `--no-retry`, `--no-quota`, `--cache`,
  `--no-cache`, `--profile`, `--audit-log`, `--audit-log-path`)

Tests assert the generated scripts contain the new command names and flags.
@chenliuyun chenliuyun merged commit be3a56f into main Apr 20, 2026
3 checks passed
@chenliuyun chenliuyun deleted the fix/ux-parser-hardening branch April 20, 2026 00:32
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
C1 (#14): update --idempotency-key / --idempotency-key-prefix help text
  in devices.ts and batch.ts to mention process-local scope, per-process
  cache semantics, and that independent CLI invocations do not share cache.

C2 (#15): mcp --help "eight tools" → "eleven tools"; list all 11 by name
  including get_device_history, query_device_history, aggregate_device_history.

C3 (#17): add `scenes describe <sceneId>` subcommand. Returns sceneId,
  sceneName, stepCount:null, and a note explaining v1.1 API limitation.
  Exits 2 with scene_not_found + candidate list on unknown sceneId.
  Adds 'scenes describe' to COMMAND_META in capabilities.ts.
  Adds 2 tests (known + unknown scene).

C4 (#13): add JSDoc comment on hints field in agent-bootstrap.ts clarifying
  empty array semantics. Add 'hints' field to cliAddedFields in schema.ts.

C5 (#16): create docs/verbose-redaction.md documenting masked headers
  (authorization, token, sign, nonce, x-api-key, cookie, set-cookie,
  x-auth-token, t) and the --trace-unsafe opt-out flag.

C6 (#18): plan schema output now includes agentNotes.deviceNameStrategy
  documenting that deviceName uses require-unique resolution and plans
  should pin deviceId for determinism.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
Document every fix landed in this branch beyond the history-aggregate
feature: bugs #1, #4, #5, #6, #8, #9, #10, #11, #12, #13, #14, #15,
#16, #17, #18 from the OpenClaw v2.4.0 smoke-test report. Call out
the deferred items (#2, #7) explicitly so readers don't assume they
were overlooked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
C1 (#14): update --idempotency-key / --idempotency-key-prefix help text
  in devices.ts and batch.ts to mention process-local scope, per-process
  cache semantics, and that independent CLI invocations do not share cache.

C2 (#15): mcp --help "eight tools" → "eleven tools"; list all 11 by name
  including get_device_history, query_device_history, aggregate_device_history.

C3 (#17): add `scenes describe <sceneId>` subcommand. Returns sceneId,
  sceneName, stepCount:null, and a note explaining v1.1 API limitation.
  Exits 2 with scene_not_found + candidate list on unknown sceneId.
  Adds 'scenes describe' to COMMAND_META in capabilities.ts.
  Adds 2 tests (known + unknown scene).

C4 (#13): add JSDoc comment on hints field in agent-bootstrap.ts clarifying
  empty array semantics. Add 'hints' field to cliAddedFields in schema.ts.

C5 (#16): create docs/verbose-redaction.md documenting masked headers
  (authorization, token, sign, nonce, x-api-key, cookie, set-cookie,
  x-auth-token, t) and the --trace-unsafe opt-out flag.

C6 (#18): plan schema output now includes agentNotes.deviceNameStrategy
  documenting that deviceName uses require-unique resolution and plans
  should pin deviceId for determinism.
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
Document every fix landed in this branch beyond the history-aggregate
feature: bugs #1, #4, #5, #6, #8, #9, #10, #11, #12, #13, #14, #15,
#16, #17, #18 from the OpenClaw v2.4.0 smoke-test report. Call out
the deferred items (#2, #7) explicitly so readers don't assume they
were overlooked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant