Skip to content

Fix/review findings#2

Merged
chenliuyun merged 26 commits intomainfrom
fix/review-findings
Apr 18, 2026
Merged

Fix/review findings#2
chenliuyun merged 26 commits intomainfrom
fix/review-findings

Conversation

@chenliuyun
Copy link
Copy Markdown
Collaborator

♻️ Current situation

Describe the current situation. Explain current problems, if there are any. Be as descriptive as possible (e.g., including examples or code snippets).

💡 Proposed solution

Describe the proposed solution and changes. How does it affect the project? How does it affect the internal structure (e.g., refactorings)?

⚙️ Release Notes

Provide a summary of the changes or features from a user's point of view. If there are breaking changes, provide migration guides using code examples of the affected features.

➕ Additional Information

If applicable, provide additional context in this section.

Testing

Which tests were added? Which existing tests were adapted/changed? Which situations are covered, and what edge cases are missing?

Reviewer Nudging

Where should the reviewer start? what is a good entry point?

chenliuyun and others added 26 commits April 18, 2026 15:52
…rich describe with capabilities

- Extend DeviceCatalogEntry with role, readOnly; extend CommandSpec with
  idempotent, destructive, exampleParams
- Annotate all 36+ catalog entries: turnOn/turnOff idempotent across the
  board; Smart Lock unlock / Garage Door Opener turnOn+turnOff / Keypad
  createKey+deleteKey marked destructive; sensor+hub entries readOnly
- Add suggestedActions(): picks up to 3 idempotent, non-destructive,
  non-customize commands with exampleParams when available
- describe now surfaces capabilities {role, readOnly, commands, statusFields}
  with a source tag ('catalog' | 'live' | 'catalog+live') and suggestedActions
  in --json output
- Add --live opt-in flag on describe to merge /v1.1/devices/{id}/status into
  capabilities.liveStatus (IR devices no-op; /status failures captured, not fatal)
- Human-readable describe distinguishes physical vs IR fallback; physical
  missing from catalog suggests 'devices status', IR suggests '--type customize'
- describe output visually flags destructive commands with [!destructive]
  and a trailing warning; shows Role/ReadOnly when present

Tests: 329 total (+27 new). New tests/devices/catalog.test.ts covers schema
integrity, command annotations, role assignments, and suggestedActions
behaviour; describe tests cover capabilities/source, destructive surfacing,
ReadOnly display, and --live success / IR no-op / error-recovery paths.
…o src/lib

- Add `switchbot mcp serve` (stdio) using @modelcontextprotocol/sdk; exposes
  seven tools to AI agents over JSON-RPC:
    list_devices, get_device_status, send_command, list_scenes, run_scene,
    search_catalog, describe_device
- send_command has a built-in destructive-command guard: commands flagged
  `destructive: true` in the catalog (Smart Lock unlock, Garage Door Opener
  turnOn/turnOff, Keypad createKey/deleteKey) refuse to execute unless the
  caller passes `confirm: true`. Returns a structured error_code so the
  agent can re-issue with the confirmation.
- send_command also runs the existing catalog-backed validation (unknown
  command, unexpected-parameter) before hitting the API, returning a
  structured error_code instead of a raw API failure.
- Extract network + parsing core from commander actions into src/lib/
  (lib/devices.ts, lib/scenes.ts) so CLI and MCP share the same code path.
  Commander actions in src/commands/ are now thin wrappers that only do
  argument parsing, output formatting, and process.exit.

Tests: 342 (+13 MCP). tests/commands/mcp.test.ts covers tool listing, the
destructive-requires-confirm guard, confirm-bypass, unknown-command
rejection, cold-cache fallback (one list_devices then the command), and
the describe_device capabilities/source/live-merge shape.

The CLI surface is unchanged; all 329 prior tests still pass against the
new lib-backed command implementations.
… guard, concurrency pool

- New `devices batch <cmd> [param]` subcommand for bulk device control.
- Target resolution: --ids (csv), stdin ("-" or --stdin), --filter (DSL).
- Filter DSL: type|family|room|category with = (exact) or ~= (substring),
  comma-separated AND. IR remotes inherit family/room from their hub.
- Pre-flight destructive check: Smart Lock unlock / Garage open / Keypad
  create|delete-key blocked without --yes; exits 2 with per-device reasons.
- Concurrency pool (default 5) + 20-60ms jitter between starts (future
  home for 429 backoff in Step 7).
- --dry-run surfaces as "skipped" per device; summary carries dryRun:true.
- Structured --json output: { succeeded, failed[{deviceId,error}], summary }.
- 10 batch tests + 16 filter tests; 368/368 green.
Step 6 of the AI-agent roadmap: batch primitive — filter DSL, stdin
pipeline, destructive guard, concurrency pool. Unlocks
`switchbot devices list --format=id | switchbot devices batch turnOff -`
style agent workflows.
…ocal quota counter

Response interceptor now transparently retries HTTP 429 responses up to
--retry-on-429 <n> times (default 3). Retry delay honors the server's
Retry-After header when present; otherwise uses exponential backoff
(1s, 2s, 4s, ..., capped at 30s). --no-retry disables the behavior;
--backoff linear switches to a linear schedule.

Every request hit is counted in ~/.switchbot/quota.json, bucketed by
local date and endpoint pattern (device/scene IDs collapsed to :id).
New `switchbot quota status|reset` command surfaces today's usage and
the last 7 days. Recording is best-effort file I/O — a failed write
never breaks the real API call; --no-quota opts out entirely.

ApiError now carries { retryable, hint } metadata so agents can
differentiate transient failures (429 after retry exhaustion, 5xx)
from permanent ones (401, 152, 160).

33 new tests (retry math, quota bucketing, quota command, retry path).
401/401 green.
Step 7 of the AI-agent roadmap: transparent 429 backoff + quota
awareness. Agents hitting the 10k/day ceiling now get automatic
Retry-After-respecting retries and can introspect their own spend
via `switchbot quota status` without an extra API call.
…ow/path/diff/refresh

Agents and power-users can now extend or patch the built-in device catalog
without waiting on a CLI release by dropping a JSON array at
~/.switchbot/catalog.json. Overlay rules:
  - matching type replaces (partial merge — overlay keys win)
  - new type is appended (must supply category + commands)
  - { type: "X", remove: true } deletes the built-in

New 'switchbot catalog' command exposes the overlay:
  path     — where the file lives + load status
  show     — effective/built-in/overlay catalog, with per-type zoom and --json
  diff     — replaced/added/removed/ignored summary
  refresh  — clear the in-process overlay cache and re-read

getEffectiveCatalog() is wired through findCatalogEntry() and searchCatalog()
so every lookup respects the overlay transparently. All existing consumers
(devices types / commands / describe / batch / MCP search_catalog) inherit
it without further changes.

Tests: +32 (overlay merge semantics + command integration), suite 401 → 433.
- Per-device status cache at ~/.switchbot/status.json, keyed by deviceId
- List cache gains TTL check (listCacheAgeMs, isListCacheFresh); default 1h
- New global flags --cache <off|auto|5m|1h|...> and --no-cache
- Fresh list cache now short-circuits fetchDeviceList(); status cache
  short-circuits fetchDeviceStatus when TTL is enabled
- New 'switchbot cache show' + 'cache clear --key list|status|all'
- 40 new tests; full suite 462/462 green
The 'switchbot devices command <id> <cmd>' path now refuses to send
destructive catalog-annotated commands (Smart Lock unlock, Garage Door
Opener turn*, Keypad createKey/deleteKey, ...) unless --yes is passed.
--dry-run still previews without requiring --yes.

Guard only fires when the local cache knows the device's type; unknown
devices and --type=customize IR buttons pass through unchanged.

Matches the destructive-command behavior already in 'devices batch' and
MCP's send_command tool.
… local webhook receiver

- devices watch <id...> polls status on an interval (default 30s, min 1s),
  emits JSONL field-level diffs; first tick seeds with from:null;
  --fields/--max/--include-unchanged; parallel per-device fetches with
  isolated error reporting; SIGINT/SIGTERM abort.
- events tail starts a local HTTP receiver on --port/--path (default
  3000 / '/'); optional --filter "deviceId=..,type=.." with comma-pair
  grammar that inspects body.context.deviceMac/deviceId/deviceType;
  JSONL or human output; --max stops after N matched events; rejects
  non-POST (405), unmatched paths (404), oversize bodies >1MB (413).
- Lift parseDurationToMs out of flags.ts to share with watch.
- Export startReceiver from events.ts for direct http-level testing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e, --from-op)

- Global --profile <name> flag routes credentials to
  ~/.switchbot/profiles/<name>.json. Absent --profile → legacy
  ~/.switchbot/config.json for full backward compat.
- Resolution priority: --config > --profile > default.
- config set-token now accepts --from-env-file <path> (dotenv parser for
  SWITCHBOT_TOKEN/SWITCHBOT_SECRET; comments and quotes handled) and
  --from-op <ref> + --op-secret <ref> (shells out to the 1Password CLI).
  Positional token/secret are now optional when a --from-* source is used.
- New config list-profiles subcommand (sorted, JSON-aware).
- loadConfig missing-file hint is profile-aware.
- Export listProfiles() and profileFilePath() for test/tooling reuse.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… + audit log

- 'devices explain <id>': one-shot agent-friendly summary combining
  metadata + live status + commands (with idempotent/destructive flags)
  + children (IR remotes bound to a Hub) + suggested actions + warnings.
- 'doctor': self-check across Node version, credentials (env vs file),
  profiles, catalog, cache, quota, and clock. JSON-aware; exit 1 on fail.
- 'schema export [--type <t>]': dump the full device catalog as JSON
  for prompt engineering / docs generation. Always emits JSON.
- 'history show' / 'history replay <n>': read the audit JSONL log and
  re-run a recorded command. Global '--audit-log [path]' enables
  append-on-execute in executeCommand (dry-run commands still logged;
  errors are captured with result:error).
- writeAudit() is best-effort: failures never break the actual command.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…docs

Round out the agent story with a declarative plan runner. The plan JSON
schema is fixed — agents emit plans, the CLI validates them, runs each
step sequentially, and honours the destructive-command guard + --dry-run
+ --audit-log from earlier phases. No LLM inside the CLI.

Three new subcommands:
  plan schema            print JSON Schema for the plan format
  plan validate [file]   validate a plan file or stdin (exit 2 on error)
  plan run [file]        validate + execute; --yes allows destructive
                         steps, --continue-on-error to keep running

Plan steps: { type: command, deviceId, command, parameter? } |
            { type: scene, sceneId }                          |
            { type: wait, ms }                                (0..600000ms)

README gets a top-of-file "Who is this for?" table pointing Human /
Script / Agent users at their entry points. docs/agent-guide.md walks
agents through MCP server, plan runner, direct JSON, catalog, safety
rails, and token budgeting.

14 new tests (validatePlan unit + schema/validate/run integration).
Cap the 14-step AI-agent optimization roadmap. Major bump because the
plan runner + MCP server + agent guide formally establish "Agent" as a
first-class user of this CLI, alongside Human and Script. No breaking
changes to existing commands — every flag and exit code from v1.x
stands.

Step 14 landed:
  - switchbot plan schema | validate | run
  - README rewrite (top-level "Who is this for?" table)
  - docs/agent-guide.md (MCP / plan / direct / safety / observability)

Cumulative v1 → v2 highlights: catalog coverage for 9 new device types
(Step 1), --format/--fields (Step 3), MCP server (Step 5), batch (Step
6), 429 backoff + quota (Step 7), catalog refresh (Step 8), status
cache (Step 9), destructive-command guard (Step 10), devices watch +
events tail (Step 11), profiles + secret sources (Step 12), explain +
doctor + schema export + history + audit-log (Step 13), plan runner +
docs (Step 14).
…ent output

New src/utils/format.ts with parseFormat(), renderRows(), resolveFormat(),
and resolveFields(). Supports table, json, jsonl, tsv, yaml, and id formats.

--fields filters columns in list commands; --json remains backward-compatible
(outputs raw API body). Updated devices list/types, scenes list, catalog show
to use the new renderer. Unified watch --fields to read from global getFields().

29 new tests (18 unit + 11 integration); 559/559 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…egory filters, cache improvements

- Add 'switchbot capabilities' command with full CLI manifest (commands, subcommands,
  args, flags, identity block, catalog stats) for agent bootstrap
- Add description field to all 42 DeviceCatalogEntry types in catalog
- Add --role and --category filters to 'schema export'
- Expand MCP server instructions with product context, device categories, constraints,
  and recommended bootstrap sequence
- Add in-memory hot-cache (resetListCache) and GC to status cache (evict entries > 24h)
- Add independent --cache-list / --cache-status TTL overrides
- Fix enableCloudService defaulting (false → true)
- Update README with new commands, cache section, and project layout
- 606 tests passing
@chenliuyun chenliuyun merged commit f249073 into main Apr 18, 2026
3 checks passed
@chenliuyun chenliuyun deleted the fix/review-findings branch April 18, 2026 18:29
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
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