Releases: OpenWonderLabs/switchbot-openapi-cli
v3.3.2
Fixed
switchbot capabilitiescrashed on 3.3.1 withmissing:mcp list-tools, missing:mcp tools. Added the twoCOMMAND_METAentries that were forgotten when the subcommands landed in 3.3.0.
Added
tests/commands/capabilities-program-coverage.test.ts— registers everyregister*Commandfromsrc/index.tsand runscapabilities --compactsovalidateCommandMetaCoveragewalks the full command tree. The prior unit test only registeredcapabilitiesitself 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
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 --probepreflight 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 viacmd.optsWithGlobals(). Bare and explicit forms now behave identically.- Live-inventory policy validation contract pinned.
validateLoadedPolicyAgainstInventoryrelies on the offline base pass catchingundefined/ empty-string /<id>device refs, because the live validatorcontinues whenresolveInventoryDeviceId()returns null. A parametric test now pins the contract so weakening either side is caught immediately. writeAuditbest-effort contract pinned by tests. Several callers (thesrc/lib/devices.tsdry-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 raisesalias-live-device-not-foundandrule-live-unsupported-commandwith schema-pointed paths and actionablehintfields. - Catalog fidelity fixture + regression test.
tests/fixtures/catalog-fidelity.observed.jsonpins role andstatusFieldsfor 11 real device types;tests/devices/catalog-fidelity.test.tsfails loudly on silent catalog drift. - Shared envelope / stream-shape helpers (
tests/helpers/contracts.ts) codify the{schemaVersion, data}contract across command tests. Stream variants pinschemaVersion: "1.1"for watch / events.
Changed
test:release-smokerenamed totest:release-smoke:manualto stop future maintainers from assuming it is wired intoprepublishOnlyor CI — it is a manual-run convenience suite.
Removed
RELEASE_METADATA3.3.0 entry. The{schemaVersion, data}JSON envelope actually shipped in v2.0.0 (commit33d3825) 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, andupgrade-check/doctor release-notestests 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
Fixed — P0 bundled-asset loader
switchbot policy new,switchbot policy validate, and the MCPpolicy_new
tool no longer fail at runtime when installed from the packed tarball. Under
esbuild bundling,import.meta.urlpoints atdist/index.jsinstead 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.jsoninstead ofdist/policy/schema/v0.2.json, etc.).
Fix: a new top-levelsrc/embedded-assets.tsmodule, positioned at the
source-tree counterpart ofdist/index.js, now owns the two asset-loading
functions (readPolicySchemaJson,readPolicyExampleYaml). Because
embedded-assets.tsand 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.mjsnow exercises the loader paths end-to-end
against the installed tarball — in addition to the existing--version
check, it runsswitchbot policy new <tmp>/policy.yaml(asserts the template
was written) andswitchbot 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-onlyin thematched_oncolumn (renamed frommatched). A new
--strictflag restricts hits to type-name matches only and prints a
"(strict mode — try without --strict)" hint when nothing matches.switchbot status-sync startnow prints a multi-line hint when
OPENCLAW_TOKENorOPENCLAW_MODELis 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-runnow separates
"Planned (dry-run)" from "Skipped (offline)" in the human-readable output and
the summary line reportsplanned=N, skipped_offline=Malongside 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 --helpclarifies that the default output is a
human-readable table and that--jsonis 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)
v3.2.2 — release pipeline unified (relands 3.2.1)
3.2.1was deprecated on npm. The initial3.2.1publish shipped a broken bin (missing shebang / exec bit afternpm pack). It was rolled back frommainand is relanded here as3.2.2. This release contains every feature3.2.1intended to deliver, plus the release-pipeline hardening that makes that class of bug impossible to ship again. Install@switchbot/openapi-cli@3.2.2or later.
Changed — release pipeline (new in 3.2.2)
- Single release source:
npm run buildis now the only command that produces the published tarball. It runs a 5-stagescripts/build.mjsorchestrator (clean → typecheck → bundle → copy-assets → ensure-binary).prepublishOnly,verify:pre-commit,verify:pre-push,publish.yml,bundle-smoke, andpack-install-smokeall callnpm run buildby name — no job re-implements any step and no other script writes todist/. - Removed
npm run build:prodandnpm run clean(both folded intoscripts/build.mjs). - Added
npm run typecheck(tsc --noEmit) as the local "does it still compile?" escape hatch. - Split
scripts/copy-assets.mjsresponsibility into two scripts with one failure mode each:copy-assets.mjsonly copies policy assets, and the newscripts/ensure-binary.mjsasserts the shebang is present ondist/index.jsandchmod 0755s it.ensure-binary.mjsis a regression guard — it fails loudly if the esbuild banner drops the shebang, rather than silently repairing it. - Pre-publish
smoke:pack-installruns inpublish.ymlbeforenpm publish, and the same smoke runs locally viapre-pushhook (verify:pre-push) and on every PR in CI (pack-install-smoke). - New
npm-published-smoke.ymlworkflow verifies published tarballs on the npm registry, auto-promotesnext → lateston success, and auto-deprecates on package-install/offline smoke failures only (never on live API flakes). bundle-smokeCI 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.mdfor 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>.jsonwith statuspending; prints the assignedplanId.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 apendingplan toapproved; required beforeplan executewill run it.switchbot plan execute <planId>— execute anapprovedplan; marks itexecutedon completion; all steps are recorded in the audit log withplanIdtraceability.- MCP
send_commandnow returns ariskProfilefield per action:riskLevel,requiresConfirmation,supportsDryRun,idempotencyHint, andrecommendedMode— computed from actual device type and command at request time. - Rules engine now enforces
maxFiringsPerHour(sliding 3600 s count window),suppressIfAlreadyDesired(skipturnOn/turnOffwhen device's live state already matches), andhysteresis/requires_stable_for(fire only after trigger is continuously stable for the specified duration). All three are validated inrules lint.
Fixed (originally intended for 3.2.1)
rules lintnow validateshysteresis/requires_stable_forduration syntax and warns whenhysteresisandrequires_stable_forare both set.
Full changelog: CHANGELOG.md
v3.1.1
Bug fixes
- daemon:
cliEntrywas resolved relative tocommands/instead ofsrc/, causing the spawned child process to use the wrong entry path. Fixed to resolve one directory up tosrc/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.tswas writingd.name(display name) instead ofd.idto the generated action'sdevicefield. This causedrules conflictsto silently miss conflicts because the analyzer compared raw device IDs. Fixed to always emit device ID. - audit:
plan executenever wrote audit entries becausewriteAuditreturned early when--audit-logwas not passed. Fixed: when an entry carries aplanId, the default audit path (~/.switchbot/audit.log) is used automatically. - upgrade-check: If the npm registry's
dist-tags.latestwas 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 whenlatestcontains a-prerelease separator.
Internal improvements
extractDeviceIdFromActionmoved from a private helper inconflict-analyzer.tsto an exported function inaction.ts, shared by both the analyzer and the rule engine.HIGH_FREQ_EVENTSandisHighFreqEventextracted from an inline check inconflict-analyzer.tsinto an exported constant and function — making the high-frequency event set explicit, testable, and easy to extend.probeLivenessextracted from two near-identical inline blocks indaemon.tsinto a single helper with configurablefatalflag 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, andrules suggestdevice-ID output.
v3.1.0
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 aplanIdin the audit log for traceabilityplan rungenerates a UUIDplanIdand writes it to every audit entry produced during the runplan suggestnow 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;--prometheusfor Prometheus text formatswitchbot health serve— long-running HTTP server exposing/healthz(JSON) and/metrics(Prometheus text) on a configurable portswitchbot upgrade-check— checks npm registry for a newer version;--jsonoutput includesbreakingChange: truewhen the major version advances
Rules engine enhancements
switchbot rules explain <name>— show trigger, conditions, actions, cooldown, hysteresis, maxFiringsPerHour, suppressIfAlreadyDesired, and last-fired timeswitchbot rules conflicts— static analysis: opposing-action pairs, high-frequency MQTT without throttle, destructive commands, and (new) quiet-hours gap detection for event-driven rulesswitchbot rules doctor— combined lint + conflict analysis in one commandswitchbot rules summary/last-fired— aggregate and recent-fire audit views- Engine now enforces
maxFiringsPerHour,suppressIfAlreadyDesired, andhysteresis/requires_stable_for throttle.dedupe_windowfield supported
Circuit breaker
CircuitBreakerwired intoapi/client.ts; opens after 5 consecutive failures, auto-probes after 60 s
Doctor & config
doctor --jsonnow includesmaturityScore(0–100) andmaturityLabel(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)doctorreports 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 requestscenes validate/scenes simulatesubcommands
Stability fixes
--yesflag correctly bypasses the destructive-execution guard on direct commandsplan executemarks statusfailed(notexecuted) when a step errorsplan-storevalidatesplanIdas UUID v4 before any file-system accesspolicy restoreno longer exposes a--forcebypasshealth servereports port conflicts immediately with a clear--porthintupgrade-checksemver 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.03.0.0
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: booleanfield from all CLI/MCP output (schema export,devices describe,agent-bootstrap,explain, MCPcatalog_search). UsesafetyTier === 'destructive'instead. - Drop policy schema v0.1 support. Files with
version: "0.1"are rejected with a migration hint. Runswitchbot policy migrateon CLI ≤2.15 before upgrading. buildStatusSyncChildArgsno longer acceptsopenclawToken; pass via envOPENCLAW_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 keychainswitchbot policy— validate, new, migrate, diff, add-rule; v0.2 schema with typedautomation.rules[]switchbot rules— suggest, lint, list, run, reload, tail, replay, webhook-rotate-token; MQTT + cron + webhook triggers;time_between/device_state/all/any/notconditions; per-rule throttle + dry-run; SIGHUP hot-reloadswitchbot 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
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 theresourcesblock from compact output. In v2.7.0 the resources catalog (~12 KB) was added to the schema payload unconditionally, which pushedschema export --compact --usedpast the 15 KB agent-prompt budget enforced by CI. Theresourcesblock is still emitted under the full (non---compact) output, and remains available viacapabilities --json, which is the canonical source for CLI resource metadata. No behaviour change forcapabilities --jsonconsumers.
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 incapabilities.tsandagent-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— carriesproduct / domain / vendor / apiVersion / apiDocs / productCategories[8]at top level; subcommand help-json unchanged (root-only to keep per-command payloads tight) - Subcommand descriptions —
catalog / schema / history / plan / doctor / capabilitiesnow 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) safetyTierenum (5 tiers) —read | mutation | ir-fire-forget | destructive | maintenance; replaces legacydestructive: booleanflagDeviceCatalogEntry.statusQueries— read-tier catalog entries exposing queryable status fields; powerssafetyTier: 'read'andcapabilities.catalog.readOnlyQueryCountcapabilities.resources— new top-levelresourcesblock 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=yamland--format=tsvfor all non-streaming commands - doctor upgrades —
--section / --list / --fix / --yes / --probeflags; new checkscatalog-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 }acrossevents tailandevents 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: booleanon catalog entries — derived fromsafetyTier === 'destructive'DeviceCatalogEntry.statusFields— superseded bystatusQueriesbatch --plan— renamed to--emit-plan; old flag prints deprecation warningevents taillegacybody/remotefields — 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
Added
devices describenow shows a tip for device types that supportdevices expand(Air Conditioner, Curtain, Curtain 3, Blind Tilt, Relay Switch 2PM);--jsonoutput includes anexpandHintfield withcommand,flags, and a ready-to-runexamplestring
Fixed
--filter controlType=Xnow works correctly;controlTypewas documented as a filterable key but was missing from the canonical key registryfield-aliases.ts: removedcategoryfrom thecontrolTypealias list to prevent collision with the physical/IRcategoryfilter key- MCP stdio path now handles
SIGTERMandSIGINTwith the same graceful shutdown as the HTTP path (30 s force-exit timeout,isShuttingDownguard)
Changed
- Extracted
exitWithError()helper inoutput.ts; deduplicatedisJsonMode()acrossindex.tsandconfig.ts
v2.6.3
Fixed
- MCP
send_commanddry-run now strictly rejects unknown command names when catalog has a definitive match (#55) - MCP
send_commanddry-run rejects commands sent to read-only sensors (e.g. Meter) - Previous v2.6.2 fix used lenient
validateCommandwhich silently passed when catalog lookup was ambiguous