Skip to content

Releases: OpenWonderLabs/switchbot-openapi-cli

v3.3.2

26 Apr 14:50
b30cdcf

Choose a tag to compare

Fixed

  • switchbot capabilities crashed on 3.3.1 with missing:mcp list-tools, missing:mcp tools. Added the two COMMAND_META entries that were forgotten when the subcommands landed in 3.3.0.

Added

  • tests/commands/capabilities-program-coverage.test.ts — registers every register*Command from src/index.ts and runs capabilities --compact so validateCommandMetaCoverage walks the full command tree. The prior unit test only registered capabilities itself and missed the gap.

Full changelog: https://github.com/OpenWonderLabs/switchbot-openapi-cli/blob/main/CHANGELOG.md
Install: npm install -g @switchbot/openapi-cli@3.3.2

v3.3.1 — real-device follow-ups and hardened JSON contracts

26 Apr 14:13

Choose a tag to compare

Patch release bundling the real-device smoke findings from the 3.3.0 rollout, with hardened JSON / stream contracts and expanded regression coverage (2043/2043 passing, +66 tests vs 3.3.0).

Fixed

  • status-sync start --probe preflight was a false-negative. The probe hit the OpenClaw base URL with GET and accepted any response (including 401 / 404 / 5xx from upstream proxies) as success, so misconfigurations only surfaced after the detached daemon had already started and was silently dropping writes. The probe now POSTs to the same endpoint the sink uses at runtime (/v1/chat/completions), with a body the server actually parses, and separates network-error handling from HTTP-error handling. Status-specific hints (401/403 → token, 404 → URL path, 400/422 → model, 5xx → server) point at the misconfigured input.
  • switchbot schema --type / switchbot health --prometheus (bare fallback forms) silently ignored their flags. Commander v12 routes parsing of a parent/child dual-declared option to the parent command, leaving the subcommand's action with empty opts. Options are now declared on the root command only; subcommand actions pull them via cmd.optsWithGlobals(). Bare and explicit forms now behave identically.
  • Live-inventory policy validation contract pinned. validateLoadedPolicyAgainstInventory relies on the offline base pass catching undefined / empty-string / <id> device refs, because the live validator continues when resolveInventoryDeviceId() returns null. A parametric test now pins the contract so weakening either side is caught immediately.
  • writeAudit best-effort contract pinned by tests. Several callers (the src/lib/devices.ts dry-run path in particular) relied on audit log writes never throwing. ENOSPC / EACCES / mkdirSync failures are now asserted non-throwing.

Added

  • Live-inventory policy validation tier. validateLoadedPolicyAgainstInventory(policy, devices) runs after the always-on offline pass and raises alias-live-device-not-found and rule-live-unsupported-command with schema-pointed paths and actionable hint fields.
  • Catalog fidelity fixture + regression test. tests/fixtures/catalog-fidelity.observed.json pins role and statusFields for 11 real device types; tests/devices/catalog-fidelity.test.ts fails loudly on silent catalog drift.
  • Shared envelope / stream-shape helpers (tests/helpers/contracts.ts) codify the {schemaVersion, data} contract across command tests. Stream variants pin schemaVersion: "1.1" for watch / events.

Changed

  • test:release-smoke renamed to test:release-smoke:manual to stop future maintainers from assuming it is wired into prepublishOnly or CI — it is a manual-run convenience suite.

Removed

  • RELEASE_METADATA 3.3.0 entry. The {schemaVersion, data} JSON envelope actually shipped in v2.0.0 (commit 33d3825) and has been in every 3.x release; flagging 3.3.0 as the breaking boundary would fire upgrade-check warnings on every 3.2.x → 3.3.x upgrade for no reason. The registry is back to empty, and upgrade-check / doctor release-notes tests now pin that contract.

Install / Upgrade

npm install -g @switchbot/openapi-cli@3.3.1
# or
switchbot upgrade-check

See CHANGELOG.md for the full list.

v3.3.0 — fix bundled-asset P0, UX polish, and loader regression guards

26 Apr 04:16
40ec757

Choose a tag to compare

Fixed — P0 bundled-asset loader

  • switchbot policy new, switchbot policy validate, and the MCP policy_new
    tool no longer fail at runtime when installed from the packed tarball. Under
    esbuild bundling, import.meta.url points at dist/index.js instead of the
    original source file, so the three call-sites that loaded embedded assets via
    new URL('<relative>', import.meta.url) resolved to non-existent paths
    (dist/schema/v0.2.json instead of dist/policy/schema/v0.2.json, etc.).
    Fix: a new top-level src/embedded-assets.ts module, positioned at the
    source-tree counterpart of dist/index.js, now owns the two asset-loading
    functions (readPolicySchemaJson, readPolicyExampleYaml). Because
    embedded-assets.ts and the bundle entry sit at the same relative depth,
    ./policy/schema/... and ./policy/examples/... resolve identically under
    tsx (dev) and under the bundle (prod) — no runtime fallback needed. All
    three call-sites (src/policy/schema.ts, src/commands/policy.ts,
    src/commands/mcp.ts) now route through those two helpers.
  • scripts/smoke-pack-install.mjs now exercises the loader paths end-to-end
    against the installed tarball — in addition to the existing --version
    check, it runs switchbot policy new <tmp>/policy.yaml (asserts the template
    was written) and switchbot policy validate <path> --json (asserts the
    schema loads and validates). The exact bug class that slipped through 3.2.2
    would now fail the smoke before publish.

Changed — UX polish

  • switchbot catalog search <keyword> now ranks hits in three tiers: exact
    type / exact alias matches first, role and command-name matches next,
    alias-substring-only matches last. Alias-only rows are explicitly labelled
    alias-only in the matched_on column (renamed from matched). A new
    --strict flag restricts hits to type-name matches only and prints a
    "(strict mode — try without --strict)" hint when nothing matches.
  • switchbot status-sync start now prints a multi-line hint when
    OPENCLAW_TOKEN or OPENCLAW_MODEL is missing — it names the flag, the env
    var, a short pointer to the admin-issued token, and the recommended verify
    step (switchbot status-sync status).
  • switchbot devices batch ... --skip-offline --dry-run now separates
    "Planned (dry-run)" from "Skipped (offline)" in the human-readable output and
    the summary line reports planned=N, skipped_offline=M alongside existing
    totals. No [dry-run] Would POST ... line is emitted for offline-skipped
    devices (JSON mode already separated these keys; no schema change).
  • switchbot devices watch --help clarifies that the default output is a
    human-readable table and that --json is the agent-friendly JSON-Lines form,
    with the seed-tick ("from": null) note surfaced near the top.

v3.2.2 — release pipeline unified (relands 3.2.1)

26 Apr 02:32
8b43539

Choose a tag to compare

v3.2.2 — release pipeline unified (relands 3.2.1)

3.2.1 was deprecated on npm. The initial 3.2.1 publish shipped a broken bin (missing shebang / exec bit after npm pack). It was rolled back from main and is relanded here as 3.2.2. This release contains every feature 3.2.1 intended to deliver, plus the release-pipeline hardening that makes that class of bug impossible to ship again. Install @switchbot/openapi-cli@3.2.2 or later.

Changed — release pipeline (new in 3.2.2)

  • Single release source: npm run build is now the only command that produces the published tarball. It runs a 5-stage scripts/build.mjs orchestrator (clean → typecheck → bundle → copy-assets → ensure-binary). prepublishOnly, verify:pre-commit, verify:pre-push, publish.yml, bundle-smoke, and pack-install-smoke all call npm run build by name — no job re-implements any step and no other script writes to dist/.
  • Removed npm run build:prod and npm run clean (both folded into scripts/build.mjs).
  • Added npm run typecheck (tsc --noEmit) as the local "does it still compile?" escape hatch.
  • Split scripts/copy-assets.mjs responsibility into two scripts with one failure mode each: copy-assets.mjs only copies policy assets, and the new scripts/ensure-binary.mjs asserts the shebang is present on dist/index.js and chmod 0755s it. ensure-binary.mjs is a regression guard — it fails loudly if the esbuild banner drops the shebang, rather than silently repairing it.
  • Pre-publish smoke:pack-install runs in publish.yml before npm publish, and the same smoke runs locally via pre-push hook (verify:pre-push) and on every PR in CI (pack-install-smoke).
  • New npm-published-smoke.yml workflow verifies published tarballs on the npm registry, auto-promotes next → latest on success, and auto-deprecates on package-install/offline smoke failures only (never on live API flakes).
  • bundle-smoke CI job is now a blocking matrix across Node 18/20/22 (was single-node Node 20, advisory), so the esbuild bundle must start cleanly on every supported Node version before a PR can merge.
  • See docs/release-pipeline.md for the full gate sequence and invariants.

Added — plan resource model, MCP risk profiles, rules safety primitives (originally intended for 3.2.1)

  • switchbot plan save [file] — persist a validated plan to ~/.switchbot/plans/<planId>.json with status pending; prints the assigned planId.
  • switchbot plan list — table of saved plans with status, creation time, and step count.
  • switchbot plan review <planId> — show full step list and current status of a saved plan.
  • switchbot plan approve <planId> — transition a pending plan to approved; required before plan execute will run it.
  • switchbot plan execute <planId> — execute an approved plan; marks it executed on completion; all steps are recorded in the audit log with planId traceability.
  • MCP send_command now returns a riskProfile field per action: riskLevel, requiresConfirmation, supportsDryRun, idempotencyHint, and recommendedMode — computed from actual device type and command at request time.
  • Rules engine now enforces maxFiringsPerHour (sliding 3600 s count window), suppressIfAlreadyDesired (skip turnOn/turnOff when device's live state already matches), and hysteresis / requires_stable_for (fire only after trigger is continuously stable for the specified duration). All three are validated in rules lint.

Fixed (originally intended for 3.2.1)

  • rules lint now validates hysteresis / requires_stable_for duration syntax and warns when hysteresis and requires_stable_for are both set.

Full changelog: CHANGELOG.md

v3.1.1

25 Apr 08:26
8c0169f

Choose a tag to compare

Bug fixes

  • daemon: cliEntry was resolved relative to commands/ instead of src/, causing the spawned child process to use the wrong entry path. Fixed to resolve one directory up to src/index.js / dist/index.js. Also added a 300 ms liveness probe so start fails fast when the child exits immediately, and includes the last 20 log lines in the error message for faster diagnosis.
  • rules suggest: suggest.ts was writing d.name (display name) instead of d.id to the generated action's device field. This caused rules conflicts to silently miss conflicts because the analyzer compared raw device IDs. Fixed to always emit device ID.
  • audit: plan execute never wrote audit entries because writeAudit returned early when --audit-log was not passed. Fixed: when an entry carries a planId, the default audit path (~/.switchbot/audit.log) is used automatically.
  • upgrade-check: If the npm registry's dist-tags.latest was accidentally tagged as a prerelease (e.g. 4.0.0-rc.1), the check would treat it as a stable release and report a false-positive upgrade prompt. Fixed by skipping the check when latest contains a - prerelease separator.

Internal improvements

  • extractDeviceIdFromAction moved from a private helper in conflict-analyzer.ts to an exported function in action.ts, shared by both the analyzer and the rule engine.
  • HIGH_FREQ_EVENTS and isHighFreqEvent extracted from an inline check in conflict-analyzer.ts into an exported constant and function — making the high-frequency event set explicit, testable, and easy to extend.
  • probeLiveness extracted from two near-identical inline blocks in daemon.ts into a single helper with configurable fatal flag and log-tail support on failure.

Tests

  • 1882 tests (+26 vs 3.1.0): new coverage for daemon liveness/log-tail, plan audit, upgrade-check prerelease guard, extractDeviceIdFromAction, HIGH_FREQ_EVENTS/isHighFreqEvent, and rules suggest device-ID output.

v3.1.0

25 Apr 04:41

Choose a tag to compare

What's new in v3.1.0

This release bundles everything developed since v3.0.0 — agent/automation safety, long-running daemon, health monitoring, and smarter rules analysis.

Plan resource model & guarded execution

  • plan save / list / review / approve / execute — full save-approve-execute lifecycle for destructive plans; every step stamped with a planId in the audit log for traceability
  • plan run generates a UUID planId and writes it to every audit entry produced during the run
  • plan suggest now drafts a candidate plan from natural-language intent

Daemon

  • switchbot daemon start/stop/status — detached background process running the rules engine; cross-platform (SIGHUP on Unix, sentinel file on Windows)

Health & observability

  • switchbot health check — one-shot quota / audit error-rate / circuit-breaker report; --prometheus for Prometheus text format
  • switchbot health serve — long-running HTTP server exposing /healthz (JSON) and /metrics (Prometheus text) on a configurable port
  • switchbot upgrade-check — checks npm registry for a newer version; --json output includes breakingChange: true when the major version advances

Rules engine enhancements

  • switchbot rules explain <name> — show trigger, conditions, actions, cooldown, hysteresis, maxFiringsPerHour, suppressIfAlreadyDesired, and last-fired time
  • switchbot rules conflicts — static analysis: opposing-action pairs, high-frequency MQTT without throttle, destructive commands, and (new) quiet-hours gap detection for event-driven rules
  • switchbot rules doctor — combined lint + conflict analysis in one command
  • switchbot rules summary / last-fired — aggregate and recent-fire audit views
  • Engine now enforces maxFiringsPerHour, suppressIfAlreadyDesired, and hysteresis / requires_stable_for
  • throttle.dedupe_window field supported

Circuit breaker

  • CircuitBreaker wired into api/client.ts; opens after 5 consecutive failures, auto-probes after 60 s

Doctor & config

  • doctor --json now includes maturityScore (0–100) and maturityLabel (production-ready / mostly-ready / needs-work / not-ready)
  • config agent-profile — print or write the recommended AI-agent profile template (~/.switchbot/profiles/agent.json, mode 0600)
  • doctor reports PATH discoverability, npm global bin reachability, and keychain-vs-file credential hint

Policy

  • policy backup / policy restore — snapshot and restore the active policy file
  • Schema v0.2 extended with cooldown, requires_stable_for, throttle.dedupe_window

Scenes

  • scenes explain <sceneId> — risk profile and execution hint without sending the request
  • scenes validate / scenes simulate subcommands

Stability fixes

  • --yes flag correctly bypasses the destructive-execution guard on direct commands
  • plan execute marks status failed (not executed) when a step errors
  • plan-store validates planId as UUID v4 before any file-system access
  • policy restore no longer exposes a --force bypass
  • health serve reports port conflicts immediately with a clear --port hint
  • upgrade-check semver correctly handles prerelease tags (3.2.1-rc.1 < 3.2.1)

Testing

1856 Vitest tests — all passing.


Upgrade:

npm install -g @switchbot/openapi-cli@3.1.0

3.0.0

24 Apr 07:38
d5c04a6

Choose a tag to compare

Major release — breaking changes, full feature parity across all branches.

Includes all features: one-command install/uninstall, L3 autonomous rule authoring, plan suggest + --require-approval, MCP policy tools, and rules engine enhancements.

Breaking changes

  • Remove destructive: boolean field from all CLI/MCP output (schema export, devices describe, agent-bootstrap, explain, MCP catalog_search). Use safetyTier === 'destructive' instead.
  • Drop policy schema v0.1 support. Files with version: "0.1" are rejected with a migration hint. Run switchbot policy migrate on CLI ≤2.15 before upgrading.
  • buildStatusSyncChildArgs no longer accepts openclawToken; pass via env OPENCLAW_TOKEN.

What's new

  • switchbot install / uninstall — one-command setup with preflight, rollback, and 4-backend keychain (macOS / Linux / Windows / file)
  • switchbot auth keychain — describe, get, set, delete, migrate credentials in native OS keychain
  • switchbot policy — validate, new, migrate, diff, add-rule; v0.2 schema with typed automation.rules[]
  • switchbot rules — suggest, lint, list, run, reload, tail, replay, webhook-rotate-token; MQTT + cron + webhook triggers; time_between / device_state / all/any/not conditions; per-rule throttle + dry-run; SIGHUP hot-reload
  • switchbot status-sync — run, start, stop, status; background OpenClaw MQTT bridge
  • MCP: plan_suggest, plan_run, audit_query, audit_stats, policy_diff, policy_validate, policy_new, policy_migrate
  • doctor: policy check, MCP check, keychain backend detection, quota + catalog-schema parity checks
  • 1765 tests (up from 1624)

Migration

- if (spec.destructive) { ... }
+ if (spec.safetyTier === 'destructive') { ... }

Policy v0.1 → v0.2 (run on CLI ≤2.15 before upgrading):

switchbot policy migrate

v2.7.2

21 Apr 16:21
62f3ad7

Choose a tag to compare

Consolidated v2.7 release. This single tag bundles three semver-logical cuts that were developed and merged together (PR #28). Neither v2.7.0 nor v2.7.1 were ever published as standalone tags — the CI size-budget fix (v2.7.2) was folded in before release.

v2.7.2 — CI size-budget fix

  • schema export --compact — dropped the resources block from compact output. In v2.7.0 the resources catalog (~12 KB) was added to the schema payload unconditionally, which pushed schema export --compact --used past the 15 KB agent-prompt budget enforced by CI. The resources block is still emitted under the full (non---compact) output, and remains available via capabilities --json, which is the canonical source for CLI resource metadata. No behaviour change for capabilities --json consumers.

v2.7.1 — help identity / AI discoverability

Top-level --help / --help --json and every subcommand description now lead with the SwitchBot product category (smart home: lights, locks, curtains, sensors, plugs, IR appliances) so AI agents reading help text can identify scope without parsing the catalog.

  • New src/commands/identity.ts — single source of truth (IDENTITY + PRODUCT_TAGLINE); replaces duplicated constants in capabilities.ts and agent-bootstrap.ts
  • Top-level switchbot --help — rewritten from "Command-line tool for SwitchBot API v1.1" to "SwitchBot smart home CLI — control lights, locks, curtains, sensors, plugs, and IR appliances (TV/AC/fan) via Cloud API v1.1; run scenes, stream real-time events, and integrate AI agents via MCP."
  • Root --help --json — carries product / domain / vendor / apiVersion / apiDocs / productCategories[8] at top level; subcommand help-json unchanged (root-only to keep per-command payloads tight)
  • Subcommand descriptionscatalog / schema / history / plan / doctor / capabilities now self-identify with "SwitchBot"
  • README intro — leads with product category, not the API version
  • agent-bootstrap --json — additive identity fields: apiDocs, deviceCategories, productCategories, agentGuide

No "BLE" in tagline/README/CHANGELOG by design: the CLI only talks to the SwitchBot Cloud API over HTTPS. BLE-only devices are reached through a SwitchBot Hub, which the Cloud API handles transparently.

v2.7.0 — AI-first maturity (headline release)

Broader field-alias coverage, richer capability metadata, agent-discoverable resource surfaces.

Added

  • Field aliases — registry expanded from ~10 to ~51 canonical keys (~98% coverage of catalog statusFields + webhook payload fields)
  • safetyTier enum (5 tiers)read | mutation | ir-fire-forget | destructive | maintenance; replaces legacy destructive: boolean flag
  • DeviceCatalogEntry.statusQueries — read-tier catalog entries exposing queryable status fields; powers safetyTier: 'read' and capabilities.catalog.readOnlyQueryCount
  • capabilities.resources — new top-level resources block exposing scenes (list/execute/describe), webhooks (4 endpoints + 15 event specs + constraints), and keypad keys (4 types). Each endpoint/event declares its safety tier
  • Multi-format output--format=yaml and --format=tsv for all non-streaming commands
  • doctor upgrades--section / --list / --fix / --yes / --probe flags; new checks catalog-schema, audit, mcp (dry-run); live MQTT probe (5 s timeout under --probe)
  • Streaming JSON contract — every streaming command emits a { schemaVersion, stream: true, eventKind, cadence } header as its first NDJSON line (docs/json-contract.md)
  • Events envelope — unified { schemaVersion, t, source, deviceId, topic, type, payload } across events tail and events mqtt-tail
  • MCP tool schema completeness — every tool input schema now carries .describe() annotations
  • Help-JSON contract test — table-driven coverage for all 16 top-level commands
  • batch --emit-plan — canonical alias for deprecated --plan

Changed

  • Error envelope — all error paths route through exitWithError() / handleError()
  • Quota accounting — records on attempt (request interceptor) instead of on success, so timeouts / 4xx / 5xx count against daily quota

Deprecated (all removed in v3.0)

  • destructive: boolean on catalog entries — derived from safetyTier === 'destructive'
  • DeviceCatalogEntry.statusFields — superseded by statusQueries
  • batch --plan — renamed to --emit-plan; old flag prints deprecation warning
  • events tail legacy body / remote fields — superseded by unified envelope

Fixed

  • Quota counter no longer under-counts requests that fail at the transport or server layer

Install: npm install -g @switchbot/openapi-cli

Full changelog: CHANGELOG.md

Compare: v2.6.4...v2.7.2

2.6.4

21 Apr 09:58
c4e869e

Choose a tag to compare

Added

  • devices describe now shows a tip for device types that support devices expand (Air Conditioner, Curtain, Curtain 3, Blind Tilt, Relay Switch 2PM); --json output includes an expandHint field with command, flags, and a ready-to-run example string

Fixed

  • --filter controlType=X now works correctly; controlType was documented as a filterable key but was missing from the canonical key registry
  • field-aliases.ts: removed category from the controlType alias list to prevent collision with the physical/IR category filter key
  • MCP stdio path now handles SIGTERM and SIGINT with the same graceful shutdown as the HTTP path (30 s force-exit timeout, isShuttingDown guard)

Changed

  • Extracted exitWithError() helper in output.ts; deduplicated isJsonMode() across index.ts and config.ts

v2.6.3

21 Apr 04:22
b01f8b4

Choose a tag to compare

Fixed

  • MCP send_command dry-run now strictly rejects unknown command names when catalog has a definitive match (#55)
  • MCP send_command dry-run rejects commands sent to read-only sensors (e.g. Meter)
  • Previous v2.6.2 fix used lenient validateCommand which silently passed when catalog lookup was ambiguous