@switchbot/openapi-cli 2.6.4 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +385 -103
  2. package/dist/api/client.js +13 -12
  3. package/dist/commands/agent-bootstrap.js +67 -16
  4. package/dist/commands/auth.js +354 -0
  5. package/dist/commands/batch.js +26 -21
  6. package/dist/commands/capabilities.js +29 -21
  7. package/dist/commands/catalog.js +4 -3
  8. package/dist/commands/config.js +57 -37
  9. package/dist/commands/devices.js +63 -37
  10. package/dist/commands/doctor.js +539 -26
  11. package/dist/commands/events.js +115 -26
  12. package/dist/commands/expand.js +7 -15
  13. package/dist/commands/explain.js +10 -7
  14. package/dist/commands/history.js +12 -18
  15. package/dist/commands/identity.js +59 -0
  16. package/dist/commands/install.js +246 -0
  17. package/dist/commands/mcp.js +895 -15
  18. package/dist/commands/plan.js +111 -15
  19. package/dist/commands/policy.js +469 -0
  20. package/dist/commands/rules.js +657 -0
  21. package/dist/commands/schema.js +20 -12
  22. package/dist/commands/status-sync.js +131 -0
  23. package/dist/commands/uninstall.js +237 -0
  24. package/dist/commands/watch.js +15 -2
  25. package/dist/config.js +14 -0
  26. package/dist/credentials/backends/file.js +101 -0
  27. package/dist/credentials/backends/linux.js +129 -0
  28. package/dist/credentials/backends/macos.js +129 -0
  29. package/dist/credentials/backends/windows.js +215 -0
  30. package/dist/credentials/keychain.js +88 -0
  31. package/dist/credentials/prime.js +52 -0
  32. package/dist/devices/catalog.js +118 -11
  33. package/dist/devices/resources.js +270 -0
  34. package/dist/index.js +39 -4
  35. package/dist/install/default-steps.js +257 -0
  36. package/dist/install/preflight.js +212 -0
  37. package/dist/install/steps.js +67 -0
  38. package/dist/lib/command-keywords.js +17 -0
  39. package/dist/lib/devices.js +15 -5
  40. package/dist/policy/add-rule.js +124 -0
  41. package/dist/policy/diff.js +91 -0
  42. package/dist/policy/examples/policy.example.yaml +99 -0
  43. package/dist/policy/format.js +57 -0
  44. package/dist/policy/load.js +61 -0
  45. package/dist/policy/migrate.js +67 -0
  46. package/dist/policy/schema/v0.2.json +302 -0
  47. package/dist/policy/schema.js +18 -0
  48. package/dist/policy/validate.js +262 -0
  49. package/dist/rules/action.js +205 -0
  50. package/dist/rules/audit-query.js +89 -0
  51. package/dist/rules/cron-scheduler.js +186 -0
  52. package/dist/rules/destructive.js +52 -0
  53. package/dist/rules/engine.js +567 -0
  54. package/dist/rules/matcher.js +230 -0
  55. package/dist/rules/pid-file.js +95 -0
  56. package/dist/rules/quiet-hours.js +45 -0
  57. package/dist/rules/suggest.js +95 -0
  58. package/dist/rules/throttle.js +78 -0
  59. package/dist/rules/types.js +34 -0
  60. package/dist/rules/webhook-listener.js +223 -0
  61. package/dist/rules/webhook-token.js +90 -0
  62. package/dist/schema/field-aliases.js +95 -0
  63. package/dist/status-sync/manager.js +268 -0
  64. package/dist/utils/audit.js +12 -2
  65. package/dist/utils/help-json.js +54 -0
  66. package/dist/utils/output.js +17 -0
  67. package/package.json +12 -4
package/README.md CHANGED
@@ -6,25 +6,38 @@
6
6
  [![node](https://img.shields.io/node/v/@switchbot/openapi-cli.svg)](https://nodejs.org)
7
7
  [![CI](https://github.com/OpenWonderLabs/switchbot-openapi-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenWonderLabs/switchbot-openapi-cli/actions/workflows/ci.yml)
8
8
 
9
- Command-line interface for the [SwitchBot API v1.1](https://github.com/OpenWonderLabs/SwitchBotAPI).
10
- List devices, query live status, send control commands, run scenes, receive real-time events, and connect AI agents via the built-in MCP server — all from your terminal or shell scripts.
9
+ **SwitchBot** smart home CLI — control lights, locks, curtains, sensors, plugs, and IR appliances (TV/AC/fan) via the [SwitchBot Cloud API v1.1](https://github.com/OpenWonderLabs/SwitchBotAPI).
10
+ Run scenes, stream real-time events over MQTT, and plug AI agents into your home via the built-in MCP server — all from your terminal or shell scripts.
11
11
 
12
12
  - **npm package:** [`@switchbot/openapi-cli`](https://www.npmjs.com/package/@switchbot/openapi-cli)
13
13
  - **Source code:** [github.com/OpenWonderLabs/switchbot-openapi-cli](https://github.com/OpenWonderLabs/switchbot-openapi-cli)
14
14
  - **Releases / changelog:** [GitHub Releases](https://github.com/OpenWonderLabs/switchbot-openapi-cli/releases)
15
15
  - **Issues / feature requests:** [GitHub Issues](https://github.com/OpenWonderLabs/switchbot-openapi-cli/issues)
16
16
 
17
+ > Looking for the **conversational skill** that drives this CLI from a chat
18
+ > agent? A companion skill for third-party agent hosts is maintained in a
19
+ > separate repository.
20
+ > See [`docs/agent-guide.md`](./docs/agent-guide.md) for the authoritative
21
+ > surfaces (MCP, `agent-bootstrap`, `schema export`, `capabilities --json`)
22
+ > the skill consumes. Skill packaging + registry entry is tracked
23
+ > as Phase 3B — see [`docs/design/roadmap.md`](./docs/design/roadmap.md).
24
+
17
25
  ---
18
26
 
19
27
  ## Who is this for?
20
28
 
21
29
  Three entry points, same binary — pick the one that matches how you use it:
22
30
 
23
- | Audience | Where to start | What you get |
24
- |-----------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
25
- | **Human** | this README ([Quick start](#quick-start)) | Colored tables, helpful hints on errors, shell completion, `switchbot doctor` self-check. |
26
- | **Script**| [Output modes](#output-modes), [Scripting examples](#scripting-examples) | `--json`, `--format=tsv/yaml/id`, `--fields`, stable exit codes, `history replay`, audit log. |
27
- | **Agent** | [`docs/agent-guide.md`](./docs/agent-guide.md) | `switchbot mcp serve` (stdio MCP server), `schema export`, `plan run`, destructive-command guard. |
31
+ - **Human**: start with this README ([Quick start](#quick-start)).
32
+ You get colored tables, helpful error hints, shell completion, and
33
+ `switchbot doctor` self-check.
34
+ - **Script**: start with [Output modes](#output-modes) and
35
+ [Scripting examples](#scripting-examples).
36
+ You get `--json`, `--format=tsv/yaml/id`, `--fields`, stable exit codes,
37
+ `history replay`, and audit log support.
38
+ - **Agent**: start with [`docs/agent-guide.md`](./docs/agent-guide.md).
39
+ You get `switchbot mcp serve` (stdio MCP server), `schema export`,
40
+ `plan run`, and destructive-command guards.
28
41
 
29
42
  Under the hood every surface shares the same catalog, cache, and HMAC client — switching between them costs nothing.
30
43
 
@@ -37,6 +50,7 @@ Under the hood every surface shares the same catalog, cache, and HMAC client —
37
50
  - [Installation](#installation)
38
51
  - [Quick start](#quick-start)
39
52
  - [Credentials](#credentials)
53
+ - [Policy](#policy)
40
54
  - [Global options](#global-options)
41
55
  - [Commands](#commands)
42
56
  - [`config`](#config--credential-management)
@@ -46,6 +60,7 @@ Under the hood every surface shares the same catalog, cache, and HMAC client —
46
60
  - [`scenes`](#scenes--run-manual-scenes)
47
61
  - [`webhook`](#webhook--receive-device-events-over-http)
48
62
  - [`events`](#events--receive-device-events)
63
+ - [`status-sync`](#status-sync--mqttopenclaw-bridge)
49
64
  - [`plan`](#plan--declarative-batch-operations)
50
65
  - [`mcp`](#mcp--model-context-protocol-server)
51
66
  - [`doctor`](#doctor--self-check)
@@ -55,9 +70,10 @@ Under the hood every surface shares the same catalog, cache, and HMAC client —
55
70
  - [`schema`](#schema--export-catalog-as-json)
56
71
  - [`capabilities`](#capabilities--cli-manifest)
57
72
  - [`cache`](#cache--inspect-and-clear-local-cache)
73
+ - [`policy`](#policy--validate-scaffold-and-migrate-policyyaml)
58
74
  - [`completion`](#completion--shell-tab-completion)
59
75
  - [Output modes](#output-modes)
60
- - [Cache](#cache-1)
76
+ - [Cache](#cache)
61
77
  - [Exit codes & error codes](#exit-codes--error-codes)
62
78
  - [Environment variables](#environment-variables)
63
79
  - [Scripting examples](#scripting-examples)
@@ -76,7 +92,7 @@ Under the hood every surface shares the same catalog, cache, and HMAC client —
76
92
  - 🎨 **Dual output modes** — colorized tables by default; `--json` passthrough for `jq` and scripting
77
93
  - 🔐 **Secure credentials** — HMAC-SHA256 signed requests; config file written with `0600`; env-var override for CI
78
94
  - 🔍 **Dry-run mode** — preview every mutating request before it hits the API
79
- - 🧪 **Fully tested** — 692 Vitest tests, mocked axios, zero network in CI
95
+ - 🧪 **Fully tested** — 1765 Vitest tests, mocked axios, zero network in CI
80
96
  - ⚡ **Shell completion** — Bash / Zsh / Fish / PowerShell
81
97
 
82
98
  ## Requirements
@@ -113,6 +129,16 @@ switchbot --help
113
129
 
114
130
  ## Quick start
115
131
 
132
+ The fast path (credentials + policy + skill link, with rollback on failure):
133
+
134
+ ```bash
135
+ switchbot install --agent claude-code --skill-path ../switchbot-skill
136
+ # or preview first
137
+ switchbot install --dry-run
138
+ ```
139
+
140
+ Prefer the manual 4-step walk-through? Here it is:
141
+
116
142
  ```bash
117
143
  # 1. Save your credentials (one-time)
118
144
  switchbot config set-token <token> <secret>
@@ -120,16 +146,44 @@ switchbot config set-token <token> <secret>
120
146
  # 2. List every device on your account
121
147
  switchbot devices list
122
148
 
123
- # 3. Control a device
124
- switchbot devices command <deviceId> turnOn
149
+ # 3. Control a device, writing a structured entry to the audit log
150
+ switchbot devices command <deviceId> turnOn --audit-log
151
+
152
+ # 4. Confirm everything is healthy — network, catalog, credentials, cache.
153
+ # Any non-"ok" check prints with a hint; fix those first.
154
+ switchbot doctor --json | jq '.checks[] | select(.status!="ok")'
155
+ ```
156
+
157
+ Adding an AI agent or declarative automation? A few more one-liners
158
+ round out the first-day path:
159
+
160
+ ```bash
161
+ # 5. Cold-start snapshot an LLM can read before its first tool call.
162
+ switchbot agent-bootstrap --compact | jq '.identity, .devices.total'
163
+
164
+ # 6. Scaffold a policy.yaml (aliases, quiet hours, confirmations) and
165
+ # validate it. Safe to run — defaults apply if you never edit it.
166
+ switchbot policy new
167
+ switchbot policy validate
168
+
169
+ # 7. Stream real-time device events over MQTT (events land as JSONL).
170
+ switchbot events mqtt-tail --max 3 --json
171
+
172
+ # 8. Run the OpenClaw status bridge in the background.
173
+ switchbot status-sync start --openclaw-model home-agent
125
174
  ```
126
175
 
176
+ See [Policy](#policy) for the authoring flow, [Rules engine](#rules-engine)
177
+ for automations, and [`docs/agent-guide.md`](./docs/agent-guide.md)
178
+ for the agent surface.
179
+
127
180
  ## Credentials
128
181
 
129
182
  The CLI reads credentials in this order (first match wins):
130
183
 
131
184
  1. **Environment variables** — `SWITCHBOT_TOKEN` and `SWITCHBOT_SECRET`
132
- 2. **Config file** — `~/.switchbot/config.json` (written by `config set-token`, mode `0600`)
185
+ 2. **OS keychain** — native keychain (macOS Keychain / Windows Credential Manager / libsecret on Linux) when populated via `switchbot auth keychain set`
186
+ 3. **Config file** — `~/.switchbot/config.json` (written by `config set-token`, mode `0600`)
133
187
 
134
188
  Obtain the token and secret from the SwitchBot mobile app:
135
189
  **Profile → Preferences → Developer Options → Get Token**.
@@ -146,30 +200,129 @@ export SWITCHBOT_SECRET=...
146
200
  switchbot config show
147
201
  ```
148
202
 
203
+ ### OS keychain
204
+
205
+ Prefer native OS storage over the `0600` JSON on disk:
206
+
207
+ ```bash
208
+ # See which backend is active on this machine
209
+ switchbot auth keychain describe
210
+
211
+ # Move existing ~/.switchbot/config.json into the keychain.
212
+ # With --delete-file, the CLI deletes the source only when it contains
213
+ # nothing except token/secret; otherwise it scrubs those fields and keeps
214
+ # profile metadata such as labels and limits.
215
+ switchbot auth keychain migrate
216
+
217
+ # Or write credentials directly (TTY prompt or --stdin-file <path>)
218
+ switchbot auth keychain set
219
+
220
+ # Verify a profile has credentials without leaking the material
221
+ switchbot auth keychain get
222
+ ```
223
+
224
+ Backends: `security(1)` on macOS, `libsecret` / `secret-tool` on Linux,
225
+ Credential Manager (via PowerShell + Win32 `CredReadW`/`CredWriteW`) on
226
+ Windows. If no native backend is available, the file backend takes
227
+ over transparently so the CLI keeps working. `switchbot doctor`
228
+ surfaces which backend is active and warns when file-stored credentials
229
+ could be moved into a writable keychain.
230
+
231
+ ## Policy
232
+
233
+ `policy.yaml` is an optional per-user file that declares preferences
234
+ the CLI (and any connected AI agent) should honour: device aliases,
235
+ quiet-hours, confirmation overrides, audit-log location, and CLI
236
+ profile. The file lives at:
237
+
238
+ - Linux / macOS: default policy path resolved by the CLI
239
+ - Windows: default policy path resolved by the CLI
240
+
241
+ Everything in it is optional — if the file is missing, safe defaults
242
+ apply. Scaffold, edit, and validate:
243
+
244
+ ```bash
245
+ switchbot policy new # write a commented starter template
246
+ $EDITOR <policy-path>
247
+ switchbot policy validate # exit 0 if OK, otherwise line-accurate error
248
+ ```
249
+
250
+ Why most users want a policy file: it makes name resolution
251
+ deterministic. Without it, "turn on the bedroom light" falls through
252
+ the CLI's prefix/substring/fuzzy match strategies and can pick the
253
+ wrong device when two names collide. A one-line `aliases` entry
254
+ removes the ambiguity.
255
+
256
+ **Schema version.** The CLI requires **policy v0.2**. If you have an existing
257
+ v0.1 file from an earlier release, migrate it first:
258
+
259
+ ```bash
260
+ switchbot policy migrate # in-place upgrade, preserves comments
261
+ ```
262
+
263
+ The v0.2 schema adds a typed `automation.rules[]` block (triggers, conditions,
264
+ throttles, dry-run) used by the rules engine (see
265
+ [Rules engine](#rules-engine)). Full field-by-field reference, validation flow,
266
+ and error catalogue: [`docs/policy-reference.md`](./docs/policy-reference.md).
267
+ Five annotated starter files covering common setups live in
268
+ [`examples/policies/`](./examples/policies/).
269
+
270
+ ### Rules engine
271
+
272
+ With a policy.yaml (v0.2) you can declare automations that the CLI
273
+ executes for you. Supported triggers: **MQTT** (device events),
274
+ **cron** (schedule-driven), and **webhook** (local HTTP POST).
275
+ Supported conditions: `time_between` (quiet hours) and `device_state`
276
+ (live API check with per-tick dedup). Every fire is recorded in
277
+ `~/.switchbot/audit.log`. `rules run` is long-running; use
278
+ `rules reload` to hot-reload policy without dropping listeners.
279
+
280
+ ```bash
281
+ # 1. Author rules under `automation.rules`. See examples/policies/automation.yaml
282
+ # for a walkthrough covering the three trigger sources.
283
+
284
+ # 2. Static-check before running.
285
+ switchbot rules lint # exit 0 valid, 1 error
286
+ switchbot rules list --json | jq . # structured summary
287
+
288
+ # 3. Run the engine. --dry-run overrides every rule into audit-only mode;
289
+ # --max-firings bounds a demo session.
290
+ switchbot rules run --dry-run --max-firings 5
291
+
292
+ # 4. Edit policy.yaml in another shell, then hot-reload without restart.
293
+ switchbot rules reload # SIGHUP on Unix, sentinel file on Windows
294
+
295
+ # 5. Review recorded fires.
296
+ switchbot rules tail --follow # stream rule-* audit lines
297
+ switchbot rules replay --since 1h --json # per-rule fires/dries/throttled/errors
298
+ ```
299
+
300
+ See [`docs/design/phase4-rules.md`](./docs/design/phase4-rules.md) for
301
+ the engine's pipeline (subscribe → classify → match → conditions →
302
+ throttle → action → audit).
303
+
149
304
  ## Global options
150
305
 
151
- | Option | Description |
152
- | --------------------------- | ------------------------------------------------------------------------ |
153
- | `--json` | Print the raw JSON response instead of a formatted table |
154
- | `--format <fmt>` | Output format: `tsv`, `yaml`, `jsonl`, `json`, `id` |
155
- | `--fields <cols>` | Comma-separated column names to include (e.g. `deviceId,type`) |
156
- | `-v`, `--verbose` | Log HTTP request/response details to stderr |
157
- | `--dry-run` | Print mutating requests (POST/PUT/DELETE) without sending them |
158
- | `--timeout <ms>` | HTTP request timeout in milliseconds (default: `30000`) |
159
- | `--config <path>` | Override credential file location (default: `~/.switchbot/config.json`) |
160
- | `--profile <name>` | Use a named credential profile (`~/.switchbot/profiles/<name>.json`) |
161
- | `--cache <dur>` | Set list and status cache TTL, e.g. `5m`, `1h`, `off`, `auto` (default) |
162
- | `--cache-list <dur>` | Set list-cache TTL independently (overrides `--cache`) |
163
- | `--cache-status <dur>` | Set status-cache TTL independently (default off; overrides `--cache`) |
164
- | `--no-cache` | Disable all cache reads for this invocation |
165
- | `--retry-on-429 <n>` | Max 429 retry attempts (default: `3`) |
166
- | `--no-retry` | Disable automatic 429 retries |
167
- | `--backoff <strategy>` | Retry backoff: `exponential` (default) or `linear` |
168
- | `--no-quota` | Disable local request-quota tracking |
169
- | `--audit-log` | Append mutating commands to a JSONL audit log (default path: `~/.switchbot/audit.log`) |
170
- | `--audit-log-path <path>` | Custom audit log path; use together with `--audit-log` |
171
- | `-V`, `--version` | Print the CLI version |
172
- | `-h`, `--help` | Show help for any command or subcommand |
306
+ - `--json`: Print the raw JSON response instead of a formatted table.
307
+ - `--format <fmt>`: Output format: `tsv`, `yaml`, `jsonl`, `json`, `id`.
308
+ - `--fields <cols>`: Comma-separated column names to include (for example `deviceId,type`).
309
+ - `-v`, `--verbose`: Log HTTP request/response details to stderr.
310
+ - `--dry-run`: Print mutating requests (POST/PUT/DELETE) without sending them.
311
+ - `--timeout <ms>`: HTTP request timeout in milliseconds (default `30000`).
312
+ - `--config <path>`: Override credential file location (default `~/.switchbot/config.json`).
313
+ - `--profile <name>`: Use a named credential profile (`~/.switchbot/profiles/<name>.json`).
314
+ - `--cache <dur>`: Set list and status cache TTL, for example `5m`, `1h`, `off`, `auto` (default).
315
+ - `--cache-list <dur>`: Set list-cache TTL independently (overrides `--cache`).
316
+ - `--cache-status <dur>`: Set status-cache TTL independently (default off; overrides `--cache`).
317
+ - `--no-cache`: Disable all cache reads for this invocation.
318
+ - `--retry-on-429 <n>`: Max 429 retry attempts (default `3`).
319
+ - `--no-retry`: Disable automatic 429 retries.
320
+ - `--backoff <strategy>`: Retry backoff: `exponential` (default) or `linear`.
321
+ - `--no-quota`: Disable local request-quota tracking.
322
+ - `--audit-log`: Append mutating commands to a JSONL audit log (default path `~/.switchbot/audit.log`).
323
+ - `--audit-log-path <path>`: Custom audit log path; use together with `--audit-log`.
324
+ - `-V`, `--version`: Print the CLI version.
325
+ - `-h`, `--help`: Show help for any command or subcommand.
173
326
 
174
327
  Every subcommand supports `--help`, and most include a parameter-format reference and examples.
175
328
 
@@ -231,7 +384,7 @@ switchbot devices list --filter 'name~living'
231
384
  switchbot devices list --filter 'type=/Hub.*/'
232
385
  switchbot devices list --filter 'name~office,type=/Bulb|Strip/'
233
386
 
234
- # Filter by family / room (family & room info requires the 'src: OpenClaw'
387
+ # Filter by family / room (family & room info requires the platform source
235
388
  # header, which this CLI sends on every request)
236
389
  switchbot devices list --json | jq '.deviceList[] | select(.familyName == "Home")'
237
390
  switchbot devices list --json | jq '[.deviceList[], .infraredRemoteList[]] | group_by(.familyName)'
@@ -270,11 +423,16 @@ switchbot devices commands curtain # Case-insensitive, substring match
270
423
  Three commands accept `--filter`. They share one four-operator grammar,
271
424
  but each exposes its own key set:
272
425
 
273
- | Command | Operators | Supported keys |
274
- |-------------------------------------|-----------------------------------------------------------------------------------------------|---------------------------------------|
275
- | `devices list` | `=` (substring; **exact** for `category`), `!=` (negated), `~` (substring), `=/regex/` (case-insensitive regex) | `type`, `name`, `category`, `room` |
276
- | `devices batch` | same | `type`, `family`, `room`, `category` |
277
- | `events tail` / `events mqtt-tail` | same (tail only; mqtt-tail uses `--topic` instead) | `deviceId`, `type` |
426
+ - `devices list`
427
+ Operators: `=` (substring; **exact** for `category`), `!=` (negated),
428
+ `~` (substring), `=/regex/` (case-insensitive regex).
429
+ Keys: `type`, `name`, `category`, `room`.
430
+ - `devices batch`
431
+ Operators: same as `devices list`.
432
+ Keys: `type`, `family`, `room`, `category`.
433
+ - `events tail` / `events mqtt-tail`
434
+ Operators: same (tail only; mqtt-tail uses `--topic` instead).
435
+ Keys: `deviceId`, `type`.
278
436
 
279
437
  Clauses are comma-separated and AND-ed. No OR across clauses — use regex
280
438
  alternation (`=/A|B/`) for that. `category` is the one key that stays exact
@@ -441,7 +599,8 @@ switchbot events tail --port 8080 --path /hook --json
441
599
  Run `switchbot webhook setup https://your.host/hook` first to tell SwitchBot where to send events, then expose the local port via ngrok/cloudflared and point the webhook URL at it. `events tail` only runs the local receiver — tunnelling is up to you.
442
600
 
443
601
  Output (one JSON line per matched event):
444
- ```
602
+
603
+ ```json
445
604
  { "t": "2024-01-01T12:00:00.000Z", "remote": "1.2.3.4:54321", "path": "/", "body": {...}, "matched": true }
446
605
  ```
447
606
 
@@ -466,7 +625,8 @@ switchbot events mqtt-tail --for 30s --json
466
625
  Connects to the SwitchBot MQTT service automatically using the same credentials configured for the REST API (`SWITCHBOT_TOKEN` + `SWITCHBOT_SECRET`). No additional MQTT configuration is required — the client certificates are provisioned on first use.
467
626
 
468
627
  Output (one JSON line per message):
469
- ```
628
+
629
+ ```json
470
630
  { "t": "2024-01-01T12:00:00.000Z", "topic": "switchbot/abc123/status", "payload": {...} }
471
631
  ```
472
632
 
@@ -482,31 +642,65 @@ nohup switchbot events mqtt-tail --json >> ~/switchbot-events.log 2>&1 &
482
642
 
483
643
  Run `switchbot doctor` to verify MQTT credentials are configured correctly before connecting.
484
644
 
645
+ ### `status-sync` — MQTT/OpenClaw bridge
646
+
647
+ Use this command family when you want the CLI itself to own the lifecycle of a
648
+ long-running bridge that forwards SwitchBot MQTT shadow events into an OpenClaw
649
+ gateway. Internally it reuses `events mqtt-tail --sink openclaw`, but adds a
650
+ stable command surface for foreground execution, background startup, status
651
+ inspection, and shutdown.
652
+
653
+ ```bash
654
+ # Foreground mode for supervisors / containers
655
+ switchbot status-sync run --openclaw-model home-agent
656
+
657
+ # Background mode for a normal shell session
658
+ switchbot status-sync start --openclaw-model home-agent
659
+
660
+ # Inspect the current bridge
661
+ switchbot status-sync status --json
662
+
663
+ # Stop the running bridge
664
+ switchbot status-sync stop
665
+ ```
666
+
667
+ Required input:
668
+
669
+ - `OPENCLAW_MODEL` or `--openclaw-model <id>`
670
+ - `OPENCLAW_TOKEN` or `--openclaw-token <token>`
671
+
672
+ Optional input:
673
+
674
+ - `OPENCLAW_URL` or `--openclaw-url <url>`
675
+ - `--topic <pattern>` to narrow the MQTT subscription
676
+ - `SWITCHBOT_STATUS_SYNC_HOME` or `--state-dir <path>` for custom runtime state
677
+
678
+ Background mode writes these files under the state directory:
679
+
680
+ - `state.json` — current pid, start time, effective command
681
+ - `stdout.log` — child stdout
682
+ - `stderr.log` — child stderr
683
+
684
+ Foreground vs background:
685
+
686
+ - `status-sync run` keeps the bridge attached to the current terminal
687
+ - `status-sync start` detaches the bridge and returns immediately
688
+ - `status-sync status` reports whether the bridge is alive plus paths/logs
689
+ - `status-sync stop` terminates the managed bridge process tree
690
+
485
691
  #### `mqtt-tail` sinks — route events to external services
486
692
 
487
693
  By default `mqtt-tail` prints JSONL to stdout. Use `--sink` (repeatable) to route events to one or more destinations instead:
488
694
 
489
695
  | Sink | Required flags |
490
- |---|---|
696
+ | --- | --- |
491
697
  | `stdout` | (default when no `--sink` given) |
492
698
  | `file` | `--sink-file <path>` — append JSONL |
493
699
  | `webhook` | `--webhook-url <url>` — HTTP POST each event |
494
- | `openclaw` | `--openclaw-url`, `--openclaw-token` (or `$OPENCLAW_TOKEN`), `--openclaw-model` |
495
700
  | `telegram` | `--telegram-token` (or `$TELEGRAM_TOKEN`), `--telegram-chat <chatId>` |
496
701
  | `homeassistant` | `--ha-url <url>` + `--ha-webhook-id` (no auth) or `--ha-token` (REST event API) |
497
702
 
498
703
  ```bash
499
- # Push events to an OpenClaw agent (replaces the SwitchBot channel plugin)
500
- switchbot events mqtt-tail \
501
- --sink openclaw \
502
- --openclaw-token <token> \
503
- --openclaw-model my-home-agent
504
-
505
- # Write to file + push to OpenClaw simultaneously
506
- switchbot events mqtt-tail \
507
- --sink file --sink-file ~/.switchbot/events.jsonl \
508
- --sink openclaw --openclaw-token <token> --openclaw-model home
509
-
510
704
  # Generic webhook (n8n, Make, etc.)
511
705
  switchbot events mqtt-tail --sink webhook --webhook-url https://n8n.local/hook/abc
512
706
 
@@ -540,6 +734,9 @@ Supported shells: `bash`, `zsh`, `fish`, `powershell` (`pwsh` is accepted as an
540
734
  # Print the plan JSON Schema (give to your agent framework)
541
735
  switchbot plan schema
542
736
 
737
+ # Draft a candidate plan from natural language intent
738
+ switchbot plan suggest --intent "turn off all lights" --device <id1> --device <id2>
739
+
543
740
  # Validate a plan file without running it
544
741
  switchbot plan validate plan.json
545
742
 
@@ -549,9 +746,12 @@ switchbot --dry-run plan run plan.json
549
746
  # Run — pass --yes to allow destructive steps
550
747
  switchbot plan run plan.json --yes
551
748
  switchbot plan run plan.json --continue-on-error
749
+
750
+ # Run with per-step TTY confirmation for destructive steps (human-in-the-loop)
751
+ switchbot plan run plan.json --require-approval
552
752
  ```
553
753
 
554
- A plan file is a JSON document with `version`, `description`, and a `steps` array of `command`, `scene`, or `wait` steps. Steps execute sequentially; a failed step stops the run unless `--continue-on-error` is set. See [`docs/agent-guide.md`](./docs/agent-guide.md) for the full schema and agent integration patterns.
754
+ A plan file is a JSON document with `version`, `description`, and a `steps` array of `command`, `scene`, or `wait` steps. Steps execute sequentially; a failed step stops the run unless `--continue-on-error` is set. `--require-approval` prompts for each destructive step individually, letting you approve or reject without re-running the whole plan. See [`docs/agent-guide.md`](./docs/agent-guide.md) for the full schema and agent integration patterns.
555
755
 
556
756
  ### `devices watch` — poll status
557
757
 
@@ -575,7 +775,12 @@ Output is a JSONL stream of status-change events (with `--json`) or a refreshed
575
775
  switchbot mcp serve
576
776
  ```
577
777
 
578
- Exposes 8 MCP tools (`list_devices`, `describe_device`, `get_device_status`, `send_command`, `list_scenes`, `run_scene`, `search_catalog`, `account_overview`) plus a `switchbot://events` resource for real-time shadow updates.
778
+ Exposes MCP tools (`list_devices`, `describe_device`, `get_device_status`,
779
+ `send_command`, `list_scenes`, `run_scene`, `search_catalog`,
780
+ `account_overview`, `plan_suggest`, `plan_run`, `audit_query`,
781
+ `audit_stats`, `policy_diff`, `policy_validate`, `policy_new`,
782
+ `policy_migrate`) plus a `switchbot://events` resource for real-time
783
+ shadow updates.
579
784
  See [`docs/agent-guide.md`](./docs/agent-guide.md) for the full tool reference and safety rules (destructive-command guard).
580
785
 
581
786
  ### `doctor` — self-check
@@ -585,7 +790,7 @@ switchbot doctor
585
790
  switchbot doctor --json
586
791
  ```
587
792
 
588
- Runs 8 local checks (Node version, credentials, profiles, catalog, cache, quota file, clock, MQTT) and exits 1 if any check fails. `warn` results exit 0. The MQTT check reports `ok` when REST credentials are configured (auto-provisioned on first use). Use this to diagnose connectivity or config issues before running automation.
793
+ Runs local checks (Node version, credentials, profiles, catalog, cache, quota, clock, MQTT, policy, MCP) and exits 1 if any check fails. `warn` results exit 0. The MQTT check reports `ok` when REST credentials are configured (auto-provisioned on first use). Use this to diagnose connectivity or config issues before running automation.
589
794
 
590
795
  ### `quota` — API request counter
591
796
 
@@ -654,7 +859,45 @@ switchbot cache clear --key list
654
859
  switchbot cache clear --key status
655
860
  ```
656
861
 
862
+ ### `policy` — validate, scaffold, and migrate policy.yaml
863
+
864
+ Companion to the separate SwitchBot skill repository for third-party agent hosts. The skill reads behaviour (aliases, confirmations, quiet hours, audit path) from `policy.yaml`. This command group checks that file before the skill ever sees it, turning what used to be silent failures into line-accurate errors.
865
+
866
+ ```bash
867
+ # Write a starter policy at the default location
868
+ switchbot policy new # writes to the resolved default policy path
869
+ switchbot policy new ./custom/policy.yaml --force
870
+
871
+ # Validate (compiler-style errors with line:col + caret + hints)
872
+ switchbot policy validate
873
+ switchbot policy validate ./custom/policy.yaml
874
+ switchbot policy validate --json | jq '.data.errors'
875
+ switchbot policy validate --no-snippet # plain error list, no source preview
876
+
877
+ # Report the schema version the file declares
878
+ switchbot policy migrate
879
+ ```
657
880
 
881
+ Path resolution order: positional `[path]` > `SWITCHBOT_POLICY_PATH` env var > default policy path.
882
+
883
+ **Exit codes:** `0` valid / `1` invalid / `2` file-not-found / `3` yaml-parse / `4` internal / `5` file already exists (on `new`, overridden with `--force`) / `6` unsupported schema version (on `migrate`).
884
+
885
+ Example — editing an alias without quoting the deviceId:
886
+
887
+ ```console
888
+ $ switchbot policy validate
889
+ <policy-path>:14:11
890
+ 14 | bedroom light: 01-abc-12345
891
+ ^^^^^^^^^^^^^
892
+ error: /aliases/bedroom light does not match pattern ^[A-Z0-9]{2,}-[A-Z0-9-]+$
893
+ hint: paste the deviceId from `switchbot devices list --format=tsv`, e.g. 01-202407090924-26354212
894
+
895
+ ✗ 1 error in <policy-path> (schema v0.1)
896
+ ```
897
+
898
+ The default policy schema shipped with the CLI (`src/policy/schema/v0.2.json`) is mirrored as `examples/policy.schema.json` in the companion skill repo; a CI job on every push diffs the two to prevent drift.
899
+
900
+ ## Output modes
658
901
 
659
902
  - **Default** — ANSI-colored tables for `list`/`status`, key-value tables for details.
660
903
  - **`--json`** — raw API payload passthrough. Output is the exact JSON the SwitchBot API returned, ideal for `jq` and scripting. Errors are also JSON on stderr: `{ "error": { "code", "kind", "message", "hint?" } }`.
@@ -675,10 +918,10 @@ switchbot devices status <id> --format yaml
675
918
 
676
919
  The CLI maintains two local disk caches under `~/.switchbot/`:
677
920
 
678
- | File | Contents | Default TTL |
679
- | ---- | -------- | ----------- |
680
- | `devices.json` | Device metadata (id, name, type, category, hub, room…) | 1 hour |
681
- | `status.json` | Per-device status bodies | off (0) |
921
+ - `devices.json`: Device metadata (id, name, type, category, hub, room…).
922
+ Default TTL: 1 hour.
923
+ - `status.json`: Per-device status bodies.
924
+ Default TTL: off (0).
682
925
 
683
926
  The device-list cache powers offline validation (command name checks, destructive-command guard) and the MCP server's `send_command` tool. It is refreshed automatically on every `devices list` call.
684
927
 
@@ -718,32 +961,28 @@ switchbot cache clear --key status
718
961
 
719
962
  ## Exit codes & error codes
720
963
 
721
- | Code | Meaning |
722
- | ---- | ------------------------------------------------------------------------------------------------------------------------- |
723
- | `0` | Success (including `--dry-run` intercept when validation passes) |
724
- | `1` | Runtime error — API error, network failure, missing credentials |
725
- | `2` | Usage error — bad flag, missing/invalid argument, unknown subcommand, unknown device type, invalid URL, conflicting flags |
726
-
727
- Typical errors bubble up in the form `Error: <message>` on stderr. The SwitchBot-specific error codes that get mapped to readable English messages:
728
-
729
- | Code | Meaning |
730
- | ---- | ------------------------------------------- |
731
- | 151 | Device type error |
732
- | 152 | Device not found |
733
- | 160 | Command not supported by this device |
734
- | 161 | Device offline (BLE devices need a Hub) |
735
- | 171 | Hub offline |
736
- | 190 | Device internal error / server busy |
737
- | 401 | Authentication failed (check token/secret) |
738
- | 429 | Request rate too high (10,000 req/day cap) |
964
+ - `0`: Success (including `--dry-run` intercept when validation passes).
965
+ - `1`: Runtime error — API error, network failure, missing credentials.
966
+ - `2`: Usage error bad flag, missing/invalid argument, unknown subcommand,
967
+ unknown device type, invalid URL, conflicting flags.
968
+
969
+ Typical errors bubble up in the form `Error: <message>` on stderr. The
970
+ SwitchBot-specific error codes mapped to readable messages:
971
+
972
+ - `151`: Device type error.
973
+ - `152`: Device not found.
974
+ - `160`: Command not supported by this device.
975
+ - `161`: Device offline (BLE devices need a Hub).
976
+ - `171`: Hub offline.
977
+ - `190`: Device internal error / server busy.
978
+ - `401`: Authentication failed (check token/secret).
979
+ - `429`: Request rate too high (10,000 req/day cap).
739
980
 
740
981
  ## Environment variables
741
982
 
742
- | Variable | Description |
743
- | --------------------------- | ------------------------------------------------------------------ |
744
- | `SWITCHBOT_TOKEN` | API token takes priority over the config file |
745
- | `SWITCHBOT_SECRET` | API secret — takes priority over the config file |
746
- | `NO_COLOR` | Disable ANSI colors in all output (automatically respected) |
983
+ - `SWITCHBOT_TOKEN`: API token — takes priority over the config file.
984
+ - `SWITCHBOT_SECRET`: API secret — takes priority over the config file.
985
+ - `NO_COLOR`: Disable ANSI colors in all output (automatically respected).
747
986
 
748
987
  ## Scripting examples
749
988
 
@@ -766,37 +1005,69 @@ npm install
766
1005
 
767
1006
  npm run dev -- <args> # Run from TypeScript sources via tsx
768
1007
  npm run build # Compile to dist/
769
- npm test # Run the Vitest suite (692 tests)
1008
+ npm test # Run the Vitest suite (1765 tests)
770
1009
  npm run test:watch # Watch mode
771
1010
  npm run test:coverage # Coverage report (v8, HTML + text)
772
1011
  ```
773
1012
 
774
1013
  ### Project layout
775
1014
 
776
- ```
1015
+ ```text
777
1016
  src/
778
1017
  ├── index.ts # Commander entry; mounts all subcommands; global flags
779
1018
  ├── auth.ts # HMAC-SHA256 signature (token + t + nonce → sign)
780
- ├── config.ts # Credential load/save; env > file priority; --config override
1019
+ ├── config.ts # Credential load/save; env > keychain > file priority
781
1020
  ├── api/client.ts # axios instance + request/response interceptors;
782
1021
  │ # --verbose / --dry-run / --timeout wiring
1022
+ ├── credentials/
1023
+ │ ├── keychain.ts # Credential store interface + OS backend selection
1024
+ │ └── backends/ # macos.ts / linux.ts / windows.ts / file.ts
783
1025
  ├── devices/
784
1026
  │ ├── catalog.ts # Static device catalog (commands, params, status fields)
785
1027
  │ └── cache.ts # Disk + in-memory cache for device list and status
1028
+ ├── install/
1029
+ │ ├── steps.ts # Generic step runner with rollback support
1030
+ │ ├── preflight.ts # Pre-flight checks (Node, npm, network, agent)
1031
+ │ └── default-steps.ts # Concrete steps: credentials, keychain, policy, skill, doctor
1032
+ ├── policy/
1033
+ │ ├── validate.ts # Schema version dispatch + JSON Schema validation
1034
+ │ ├── migrate.ts # v0.1 → v0.2 migration
1035
+ │ ├── load.ts # YAML file loading + error handling
1036
+ │ ├── add-rule.ts # Rule injection into automation.rules[]
1037
+ │ ├── diff.ts # Structural + line diff
1038
+ │ └── schema/v0.2.json # Authoritative v0.2 JSON Schema
1039
+ ├── rules/
1040
+ │ ├── engine.ts # Main orchestrator (MQTT + cron + webhook)
1041
+ │ ├── matcher.ts # Trigger + condition matchers
1042
+ │ ├── action.ts # Command renderer + executor
1043
+ │ ├── throttle.ts # Per-rule throttle gate
1044
+ │ ├── cron-scheduler.ts # 5-field cron + days filter
1045
+ │ ├── webhook-listener.ts # HTTP listener (bearer token, localhost-only)
1046
+ │ ├── pid-file.ts # Hot-reload via SIGHUP or sentinel file
1047
+ │ ├── audit-query.ts # Audit log filtering + aggregation
1048
+ │ ├── suggest.ts # Heuristic-based rule YAML generation
1049
+ │ └── types.ts # Shared rule/trigger/condition/action types
1050
+ ├── status-sync/
1051
+ │ └── manager.ts # Spawn/stop logic, state file, OpenClaw bridge
786
1052
  ├── lib/
787
1053
  │ └── devices.ts # Shared logic: listDevices, describeDevice, isDestructiveCommand
788
1054
  ├── commands/
1055
+ │ ├── auth.ts # `auth keychain` subcommand group
789
1056
  │ ├── config.ts
790
1057
  │ ├── devices.ts
791
1058
  │ ├── expand.ts # `devices expand` — semantic flag builder
792
1059
  │ ├── explain.ts # `devices explain` — one-shot device summary
793
1060
  │ ├── device-meta.ts # `devices meta` — local aliases / hide flags
1061
+ │ ├── install.ts # `switchbot install` / `uninstall`
1062
+ │ ├── policy.ts # `policy validate/new/migrate/diff/add-rule`
1063
+ │ ├── rules.ts # `rules suggest/lint/list/run/reload/tail/replay`
794
1064
  │ ├── scenes.ts
1065
+ │ ├── status-sync.ts # `status-sync run/start/stop/status`
795
1066
  │ ├── webhook.ts
796
1067
  │ ├── watch.ts # `devices watch <deviceId>`
797
1068
  │ ├── events.ts # `events tail` / `events mqtt-tail`
798
1069
  │ ├── mcp.ts # `mcp serve` (MCP stdio/HTTP server)
799
- │ ├── plan.ts # `plan run/validate`
1070
+ │ ├── plan.ts # `plan run/validate/suggest`
800
1071
  │ ├── cache.ts # `cache show/clear`
801
1072
  │ ├── history.ts # `history show/replay`
802
1073
  │ ├── quota.ts # `quota status/reset`
@@ -807,11 +1078,11 @@ src/
807
1078
  │ └── completion.ts # `completion bash|zsh|fish|powershell`
808
1079
  └── utils/
809
1080
  ├── flags.ts # Global flag readers (isVerbose / isDryRun / getCacheMode / …)
810
- ├── output.ts # printTable / printKeyValue / printJson / handleError / buildErrorPayload
1081
+ ├── output.ts # printTable / printKeyValue / printJson / handleError
811
1082
  ├── format.ts # renderRows / filterFields / output-format dispatch
812
1083
  ├── audit.ts # JSONL audit log writer
813
1084
  └── quota.ts # Local daily-quota counter
814
- tests/ # Vitest suite (592 tests, mocked axios, no network)
1085
+ tests/ # Vitest suite (1765 tests, mocked axios, no network)
815
1086
  ```
816
1087
 
817
1088
  ### Release flow
@@ -836,18 +1107,29 @@ Bug reports, feature requests, and PRs are welcome.
836
1107
 
837
1108
  ## Roadmap
838
1109
 
839
- Tracked for a future v3.x line (OpenClaw B-17 / B-18 / B-19 / B-21) — each is a
840
- standalone track rather than a bug fix:
841
-
842
- - **Daemon mode** — long-running local process with a Unix/named-pipe socket so
843
- repeated MCP or plan invocations don't pay fresh-process startup every call.
844
- - **`npx @switchbot/mcp-server`** — split the MCP server into its own tiny
845
- published package so non-CLI users can `npx` it directly without installing
846
- the full CLI.
847
- - **`switchbot self-test`** scripted end-to-end harness that checks a live
848
- token + a representative device and prints a go/no-go report.
849
- - **Record / replay** capture raw request/response pairs into a fixture file
850
- and replay them offline for deterministic testing and CI.
1110
+ Phase 1 through Phase 4 are shipped. The authoritative phase/track table
1111
+ (including skill-side `autonomyLevel` L1/L2/L3 mapping) lives in
1112
+ [`docs/design/roadmap.md`](./docs/design/roadmap.md).
1113
+
1114
+ Shipped tracks summary:
1115
+
1116
+ - **Track β**: one-command install/uninstall surface (`switchbot install` / `switchbot uninstall`).
1117
+ - **Track γ**: rules v0.2 runtime increment (`days` + `all`/`any`/`not`).
1118
+ - **Track δ (L2)**: plan authoring + guarded execution (`plan suggest`, `plan run --require-approval`) and MCP review/execute tools (`plan_suggest`, `plan_run`, `audit_query`, `audit_stats`, `policy_diff`).
1119
+ - **Track ζ (L3)**: autonomous rule authoring (`rules suggest`, `policy add-rule`) with MCP parity (`rules_suggest`, `policy_add_rule`).
1120
+ - **Track ε**: cross-OS keychain CI matrix (macOS + Linux libsecret + Windows Credential Manager).
1121
+
1122
+ Backlog tracks still open:
1123
+
1124
+ 1. **Daemon mode** — long-running local process with Unix/named-pipe
1125
+ transport so repeated MCP or plan invocations avoid fresh-process
1126
+ startup cost.
1127
+ 2. **`npx @switchbot/mcp-server`** — split the MCP server into a tiny
1128
+ package so non-CLI users can run it directly with `npx`.
1129
+ 3. **`switchbot self-test`** — scripted end-to-end go/no-go checks for
1130
+ token/secret validity plus representative device control.
1131
+ 4. **Record / replay** — capture request/response fixtures and replay
1132
+ offline for deterministic integration tests and CI.
851
1133
 
852
1134
  ## License
853
1135