@switchbot/openapi-cli 2.7.2 → 3.1.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 (69) hide show
  1. package/README.md +481 -103
  2. package/dist/api/client.js +23 -1
  3. package/dist/commands/agent-bootstrap.js +47 -2
  4. package/dist/commands/auth.js +354 -0
  5. package/dist/commands/batch.js +20 -4
  6. package/dist/commands/capabilities.js +155 -65
  7. package/dist/commands/config.js +109 -0
  8. package/dist/commands/daemon.js +367 -0
  9. package/dist/commands/devices.js +62 -11
  10. package/dist/commands/doctor.js +417 -8
  11. package/dist/commands/events.js +3 -3
  12. package/dist/commands/explain.js +1 -2
  13. package/dist/commands/health.js +113 -0
  14. package/dist/commands/install.js +246 -0
  15. package/dist/commands/mcp.js +888 -7
  16. package/dist/commands/plan.js +379 -103
  17. package/dist/commands/policy.js +586 -0
  18. package/dist/commands/rules.js +875 -0
  19. package/dist/commands/scenes.js +140 -0
  20. package/dist/commands/schema.js +0 -2
  21. package/dist/commands/status-sync.js +131 -0
  22. package/dist/commands/uninstall.js +237 -0
  23. package/dist/commands/upgrade-check.js +88 -0
  24. package/dist/config.js +14 -0
  25. package/dist/credentials/backends/file.js +101 -0
  26. package/dist/credentials/backends/linux.js +129 -0
  27. package/dist/credentials/backends/macos.js +129 -0
  28. package/dist/credentials/backends/windows.js +215 -0
  29. package/dist/credentials/keychain.js +88 -0
  30. package/dist/credentials/prime.js +52 -0
  31. package/dist/devices/catalog.js +4 -10
  32. package/dist/index.js +30 -1
  33. package/dist/install/default-steps.js +257 -0
  34. package/dist/install/preflight.js +212 -0
  35. package/dist/install/steps.js +67 -0
  36. package/dist/lib/command-keywords.js +17 -0
  37. package/dist/lib/daemon-state.js +46 -0
  38. package/dist/lib/destructive-mode.js +12 -0
  39. package/dist/lib/devices.js +1 -1
  40. package/dist/lib/plan-store.js +68 -0
  41. package/dist/policy/add-rule.js +124 -0
  42. package/dist/policy/diff.js +91 -0
  43. package/dist/policy/examples/policy.example.yaml +99 -0
  44. package/dist/policy/format.js +57 -0
  45. package/dist/policy/load.js +61 -0
  46. package/dist/policy/migrate.js +67 -0
  47. package/dist/policy/schema/v0.2.json +331 -0
  48. package/dist/policy/schema.js +18 -0
  49. package/dist/policy/validate.js +262 -0
  50. package/dist/rules/action.js +205 -0
  51. package/dist/rules/audit-query.js +89 -0
  52. package/dist/rules/conflict-analyzer.js +203 -0
  53. package/dist/rules/cron-scheduler.js +186 -0
  54. package/dist/rules/destructive.js +52 -0
  55. package/dist/rules/engine.js +757 -0
  56. package/dist/rules/matcher.js +230 -0
  57. package/dist/rules/pid-file.js +95 -0
  58. package/dist/rules/quiet-hours.js +45 -0
  59. package/dist/rules/suggest.js +95 -0
  60. package/dist/rules/throttle.js +116 -0
  61. package/dist/rules/types.js +34 -0
  62. package/dist/rules/webhook-listener.js +223 -0
  63. package/dist/rules/webhook-token.js +90 -0
  64. package/dist/status-sync/manager.js +268 -0
  65. package/dist/utils/audit.js +12 -2
  66. package/dist/utils/health.js +101 -0
  67. package/dist/utils/output.js +72 -23
  68. package/dist/utils/retry.js +81 -0
  69. package/package.json +12 -4
package/README.md CHANGED
@@ -14,17 +14,30 @@ Run scenes, stream real-time events over MQTT, and plug AI agents into your home
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,18 +60,22 @@ 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)
67
+ - [`health`](#health--runtime-health-report)
68
+ - [`upgrade-check`](#upgrade-check--version-check)
52
69
  - [`quota`](#quota--api-request-counter)
53
70
  - [`history`](#history--audit-log)
54
71
  - [`catalog`](#catalog--device-type-catalog)
55
72
  - [`schema`](#schema--export-catalog-as-json)
56
73
  - [`capabilities`](#capabilities--cli-manifest)
57
74
  - [`cache`](#cache--inspect-and-clear-local-cache)
75
+ - [`policy`](#policy--validate-scaffold-and-migrate-policyyaml)
58
76
  - [`completion`](#completion--shell-tab-completion)
59
77
  - [Output modes](#output-modes)
60
- - [Cache](#cache-1)
78
+ - [Cache](#cache)
61
79
  - [Exit codes & error codes](#exit-codes--error-codes)
62
80
  - [Environment variables](#environment-variables)
63
81
  - [Scripting examples](#scripting-examples)
@@ -76,7 +94,7 @@ Under the hood every surface shares the same catalog, cache, and HMAC client —
76
94
  - 🎨 **Dual output modes** — colorized tables by default; `--json` passthrough for `jq` and scripting
77
95
  - 🔐 **Secure credentials** — HMAC-SHA256 signed requests; config file written with `0600`; env-var override for CI
78
96
  - 🔍 **Dry-run mode** — preview every mutating request before it hits the API
79
- - 🧪 **Fully tested** — 692 Vitest tests, mocked axios, zero network in CI
97
+ - 🧪 **Fully tested** — 1856 Vitest tests, mocked axios, zero network in CI
80
98
  - ⚡ **Shell completion** — Bash / Zsh / Fish / PowerShell
81
99
 
82
100
  ## Requirements
@@ -113,6 +131,16 @@ switchbot --help
113
131
 
114
132
  ## Quick start
115
133
 
134
+ The fast path (credentials + policy + skill link, with rollback on failure):
135
+
136
+ ```bash
137
+ switchbot install --agent claude-code --skill-path ../switchbot-skill
138
+ # or preview first
139
+ switchbot install --dry-run
140
+ ```
141
+
142
+ Prefer the manual 4-step walk-through? Here it is:
143
+
116
144
  ```bash
117
145
  # 1. Save your credentials (one-time)
118
146
  switchbot config set-token <token> <secret>
@@ -120,16 +148,44 @@ switchbot config set-token <token> <secret>
120
148
  # 2. List every device on your account
121
149
  switchbot devices list
122
150
 
123
- # 3. Control a device
124
- switchbot devices command <deviceId> turnOn
151
+ # 3. Control a device, writing a structured entry to the audit log
152
+ switchbot devices command <deviceId> turnOn --audit-log
153
+
154
+ # 4. Confirm everything is healthy — network, catalog, credentials, cache.
155
+ # Any non-"ok" check prints with a hint; fix those first.
156
+ switchbot doctor --json | jq '.checks[] | select(.status!="ok")'
157
+ ```
158
+
159
+ Adding an AI agent or declarative automation? A few more one-liners
160
+ round out the first-day path:
161
+
162
+ ```bash
163
+ # 5. Cold-start snapshot an LLM can read before its first tool call.
164
+ switchbot agent-bootstrap --compact | jq '.identity, .devices.total'
165
+
166
+ # 6. Scaffold a policy.yaml (aliases, quiet hours, confirmations) and
167
+ # validate it. Safe to run — defaults apply if you never edit it.
168
+ switchbot policy new
169
+ switchbot policy validate
170
+
171
+ # 7. Stream real-time device events over MQTT (events land as JSONL).
172
+ switchbot events mqtt-tail --max 3 --json
173
+
174
+ # 8. Run the OpenClaw status bridge in the background.
175
+ switchbot status-sync start --openclaw-model home-agent
125
176
  ```
126
177
 
178
+ See [Policy](#policy) for the authoring flow, [Rules engine](#rules-engine)
179
+ for automations, and [`docs/agent-guide.md`](./docs/agent-guide.md)
180
+ for the agent surface.
181
+
127
182
  ## Credentials
128
183
 
129
184
  The CLI reads credentials in this order (first match wins):
130
185
 
131
186
  1. **Environment variables** — `SWITCHBOT_TOKEN` and `SWITCHBOT_SECRET`
132
- 2. **Config file** — `~/.switchbot/config.json` (written by `config set-token`, mode `0600`)
187
+ 2. **OS keychain** — native keychain (macOS Keychain / Windows Credential Manager / libsecret on Linux) when populated via `switchbot auth keychain set`
188
+ 3. **Config file** — `~/.switchbot/config.json` (written by `config set-token`, mode `0600`)
133
189
 
134
190
  Obtain the token and secret from the SwitchBot mobile app:
135
191
  **Profile → Preferences → Developer Options → Get Token**.
@@ -146,30 +202,150 @@ export SWITCHBOT_SECRET=...
146
202
  switchbot config show
147
203
  ```
148
204
 
205
+ ### OS keychain
206
+
207
+ Prefer native OS storage over the `0600` JSON on disk:
208
+
209
+ ```bash
210
+ # See which backend is active on this machine
211
+ switchbot auth keychain describe
212
+
213
+ # Move existing ~/.switchbot/config.json into the keychain.
214
+ # With --delete-file, the CLI deletes the source only when it contains
215
+ # nothing except token/secret; otherwise it scrubs those fields and keeps
216
+ # profile metadata such as labels and limits.
217
+ switchbot auth keychain migrate
218
+
219
+ # Or write credentials directly (TTY prompt or --stdin-file <path>)
220
+ switchbot auth keychain set
221
+
222
+ # Verify a profile has credentials without leaking the material
223
+ switchbot auth keychain get
224
+ ```
225
+
226
+ Backends: `security(1)` on macOS, `libsecret` / `secret-tool` on Linux,
227
+ Credential Manager (via PowerShell + Win32 `CredReadW`/`CredWriteW`) on
228
+ Windows. If no native backend is available, the file backend takes
229
+ over transparently so the CLI keeps working. `switchbot doctor`
230
+ surfaces which backend is active and warns when file-stored credentials
231
+ could be moved into a writable keychain.
232
+
233
+ ## Policy
234
+
235
+ `policy.yaml` is an optional per-user file that declares preferences
236
+ the CLI (and any connected AI agent) should honour: device aliases,
237
+ quiet-hours, confirmation overrides, audit-log location, and CLI
238
+ profile. The file lives at:
239
+
240
+ - Linux / macOS: default policy path resolved by the CLI
241
+ - Windows: default policy path resolved by the CLI
242
+
243
+ Everything in it is optional — if the file is missing, safe defaults
244
+ apply. Scaffold, edit, and validate:
245
+
246
+ ```bash
247
+ switchbot policy new # write a commented starter template
248
+ $EDITOR <policy-path>
249
+ switchbot policy validate # exit 0 if OK, otherwise line-accurate error
250
+ ```
251
+
252
+ Why most users want a policy file: it makes name resolution
253
+ deterministic. Without it, "turn on the bedroom light" falls through
254
+ the CLI's prefix/substring/fuzzy match strategies and can pick the
255
+ wrong device when two names collide. A one-line `aliases` entry
256
+ removes the ambiguity.
257
+
258
+ **Schema version.** The CLI requires **policy v0.2**. If you have an existing
259
+ v0.1 file from an earlier release, migrate it first:
260
+
261
+ ```bash
262
+ switchbot policy migrate # in-place upgrade, preserves comments
263
+ ```
264
+
265
+ The v0.2 schema adds a typed `automation.rules[]` block (triggers, conditions,
266
+ throttles, dry-run) used by the rules engine (see
267
+ [Rules engine](#rules-engine)). Full field-by-field reference, validation flow,
268
+ and error catalogue: [`docs/policy-reference.md`](./docs/policy-reference.md).
269
+ Five annotated starter files covering common setups live in
270
+ [`examples/policies/`](./examples/policies/).
271
+
272
+ ### Rules engine
273
+
274
+ With a policy.yaml (v0.2) you can declare automations that the CLI
275
+ executes for you. Supported triggers: **MQTT** (device events),
276
+ **cron** (schedule-driven), and **webhook** (local HTTP POST).
277
+ Supported conditions: `time_between` (quiet hours) and `device_state`
278
+ (live API check with per-tick dedup). Every fire is recorded in
279
+ `~/.switchbot/audit.log`. `rules run` is long-running; use
280
+ `daemon start` / `daemon reload` for the managed background mode.
281
+
282
+ ```bash
283
+ # 1. Author rules under `automation.rules`. See examples/policies/automation.yaml
284
+ # for a walkthrough covering the three trigger sources.
285
+
286
+ # 2. Static-check before running.
287
+ switchbot rules lint # exit 0 valid, 1 error
288
+ switchbot rules list --json | jq . # structured summary
289
+
290
+ # 3. Inspect a single rule in full detail (trigger, conditions, actions,
291
+ # cooldown, hysteresis, maxFiringsPerHour, suppressIfAlreadyDesired, last fired).
292
+ switchbot rules explain "motion on"
293
+ switchbot rules explain "motion on" --json
294
+
295
+ # 4. Run the engine. --dry-run overrides every rule into audit-only mode;
296
+ # --max-firings bounds a demo session.
297
+ switchbot rules run --dry-run --max-firings 5
298
+
299
+ # 5. Edit policy.yaml in another shell, then hot-reload without restart.
300
+ switchbot daemon reload # managed daemon reload
301
+
302
+ # 6. Review recorded fires.
303
+ switchbot rules tail --follow # stream rule-* audit lines
304
+ switchbot rules replay --since 1h --json # per-rule fires/dries/throttled/errors
305
+ switchbot rules summary # aggregate fires/errors per rule (24h window)
306
+ switchbot rules last-fired -n 20 # 20 most recent fire entries
307
+
308
+ # 7. Conflict and health analysis.
309
+ switchbot rules conflicts # opposing actions, high-frequency MQTT,
310
+ # destructive commands, quiet-hours gaps
311
+ switchbot rules doctor --json # lint + conflicts combined; exit 0 when clean
312
+ ```
313
+
314
+ When `quiet_hours` is configured in `policy.yaml`, `rules conflicts` additionally flags event-driven (MQTT / webhook) rules that lack a `time_between` condition — they would fire uninhibited during the quiet window. The hint in each finding includes a ready-to-paste `time_between` condition to add.
315
+
316
+ Webhook trigger token management:
317
+
318
+ ```bash
319
+ switchbot rules webhook-rotate-token # rotate the bearer token for webhook triggers
320
+ switchbot rules webhook-show-token # print current token (creates one if absent)
321
+ ```
322
+
323
+ See [`docs/design/phase4-rules.md`](./docs/design/phase4-rules.md) for
324
+ the engine's pipeline (subscribe → classify → match → conditions →
325
+ throttle → action → audit).
326
+
149
327
  ## Global options
150
328
 
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 |
329
+ - `--json`: Print the raw JSON response instead of a formatted table.
330
+ - `--format <fmt>`: Output format: `tsv`, `yaml`, `jsonl`, `json`, `id`.
331
+ - `--fields <cols>`: Comma-separated column names to include (for example `deviceId,type`).
332
+ - `-v`, `--verbose`: Log HTTP request/response details to stderr.
333
+ - `--dry-run`: Print mutating requests (POST/PUT/DELETE) without sending them.
334
+ - `--timeout <ms>`: HTTP request timeout in milliseconds (default `30000`).
335
+ - `--config <path>`: Override credential file location (default `~/.switchbot/config.json`).
336
+ - `--profile <name>`: Use a named credential profile (`~/.switchbot/profiles/<name>.json`).
337
+ - `--cache <dur>`: Set list and status cache TTL, for example `5m`, `1h`, `off`, `auto` (default).
338
+ - `--cache-list <dur>`: Set list-cache TTL independently (overrides `--cache`).
339
+ - `--cache-status <dur>`: Set status-cache TTL independently (default off; overrides `--cache`).
340
+ - `--no-cache`: Disable all cache reads for this invocation.
341
+ - `--retry-on-429 <n>`: Max 429 retry attempts (default `3`).
342
+ - `--no-retry`: Disable automatic 429 retries.
343
+ - `--backoff <strategy>`: Retry backoff: `exponential` (default) or `linear`.
344
+ - `--no-quota`: Disable local request-quota tracking.
345
+ - `--audit-log`: Append mutating commands to a JSONL audit log (default path `~/.switchbot/audit.log`).
346
+ - `--audit-log-path <path>`: Custom audit log path; use together with `--audit-log`.
347
+ - `-V`, `--version`: Print the CLI version.
348
+ - `-h`, `--help`: Show help for any command or subcommand.
173
349
 
174
350
  Every subcommand supports `--help`, and most include a parameter-format reference and examples.
175
351
 
@@ -203,6 +379,12 @@ switchbot devices command ABC123 turnOn --dry-run
203
379
  switchbot config set-token <token> <secret> # Save to ~/.switchbot/config.json
204
380
  switchbot config show # Print current source + masked secret
205
381
  switchbot config list-profiles # List saved profiles
382
+
383
+ # Print (or write) the recommended AI-agent profile template
384
+ switchbot config agent-profile # print to stdout
385
+ switchbot config agent-profile --write # write to ~/.switchbot/profiles/agent.json (mode 0600)
386
+ switchbot config agent-profile --write --force # overwrite if it already exists
387
+ switchbot config agent-profile --json # structured JSON envelope
206
388
  ```
207
389
 
208
390
  ### `devices` — list, status, control
@@ -231,7 +413,7 @@ switchbot devices list --filter 'name~living'
231
413
  switchbot devices list --filter 'type=/Hub.*/'
232
414
  switchbot devices list --filter 'name~office,type=/Bulb|Strip/'
233
415
 
234
- # Filter by family / room (family & room info requires the 'src: OpenClaw'
416
+ # Filter by family / room (family & room info requires the platform source
235
417
  # header, which this CLI sends on every request)
236
418
  switchbot devices list --json | jq '.deviceList[] | select(.familyName == "Home")'
237
419
  switchbot devices list --json | jq '[.deviceList[], .infraredRemoteList[]] | group_by(.familyName)'
@@ -270,11 +452,16 @@ switchbot devices commands curtain # Case-insensitive, substring match
270
452
  Three commands accept `--filter`. They share one four-operator grammar,
271
453
  but each exposes its own key set:
272
454
 
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` |
455
+ - `devices list`
456
+ Operators: `=` (substring; **exact** for `category`), `!=` (negated),
457
+ `~` (substring), `=/regex/` (case-insensitive regex).
458
+ Keys: `type`, `name`, `category`, `room`.
459
+ - `devices batch`
460
+ Operators: same as `devices list`.
461
+ Keys: `type`, `family`, `room`, `category`.
462
+ - `events tail` / `events mqtt-tail`
463
+ Operators: same (tail only; mqtt-tail uses `--topic` instead).
464
+ Keys: `deviceId`, `type`.
278
465
 
279
466
  Clauses are comma-separated and AND-ed. No OR across clauses — use regex
280
467
  alternation (`=/A|B/`) for that. `category` is the one key that stays exact
@@ -393,6 +580,10 @@ skipped devices appear under `summary.skipped` with `skippedReason:'offline'`.
393
580
  ```bash
394
581
  switchbot scenes list # Columns: sceneId, sceneName
395
582
  switchbot scenes execute <sceneId>
583
+
584
+ # One-shot summary: risk profile, execution hint, estimated commands
585
+ switchbot scenes explain <sceneId>
586
+ switchbot scenes explain <sceneId> --json
396
587
  ```
397
588
 
398
589
  ### `webhook` — receive device events over HTTP
@@ -441,7 +632,8 @@ switchbot events tail --port 8080 --path /hook --json
441
632
  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
633
 
443
634
  Output (one JSON line per matched event):
444
- ```
635
+
636
+ ```json
445
637
  { "t": "2024-01-01T12:00:00.000Z", "remote": "1.2.3.4:54321", "path": "/", "body": {...}, "matched": true }
446
638
  ```
447
639
 
@@ -466,7 +658,8 @@ switchbot events mqtt-tail --for 30s --json
466
658
  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
659
 
468
660
  Output (one JSON line per message):
469
- ```
661
+
662
+ ```json
470
663
  { "t": "2024-01-01T12:00:00.000Z", "topic": "switchbot/abc123/status", "payload": {...} }
471
664
  ```
472
665
 
@@ -482,31 +675,65 @@ nohup switchbot events mqtt-tail --json >> ~/switchbot-events.log 2>&1 &
482
675
 
483
676
  Run `switchbot doctor` to verify MQTT credentials are configured correctly before connecting.
484
677
 
678
+ ### `status-sync` — MQTT/OpenClaw bridge
679
+
680
+ Use this command family when you want the CLI itself to own the lifecycle of a
681
+ long-running bridge that forwards SwitchBot MQTT shadow events into an OpenClaw
682
+ gateway. Internally it reuses `events mqtt-tail --sink openclaw`, but adds a
683
+ stable command surface for foreground execution, background startup, status
684
+ inspection, and shutdown.
685
+
686
+ ```bash
687
+ # Foreground mode for supervisors / containers
688
+ switchbot status-sync run --openclaw-model home-agent
689
+
690
+ # Background mode for a normal shell session
691
+ switchbot status-sync start --openclaw-model home-agent
692
+
693
+ # Inspect the current bridge
694
+ switchbot status-sync status --json
695
+
696
+ # Stop the running bridge
697
+ switchbot status-sync stop
698
+ ```
699
+
700
+ Required input:
701
+
702
+ - `OPENCLAW_MODEL` or `--openclaw-model <id>`
703
+ - `OPENCLAW_TOKEN` or `--openclaw-token <token>`
704
+
705
+ Optional input:
706
+
707
+ - `OPENCLAW_URL` or `--openclaw-url <url>`
708
+ - `--topic <pattern>` to narrow the MQTT subscription
709
+ - `SWITCHBOT_STATUS_SYNC_HOME` or `--state-dir <path>` for custom runtime state
710
+
711
+ Background mode writes these files under the state directory:
712
+
713
+ - `state.json` — current pid, start time, effective command
714
+ - `stdout.log` — child stdout
715
+ - `stderr.log` — child stderr
716
+
717
+ Foreground vs background:
718
+
719
+ - `status-sync run` keeps the bridge attached to the current terminal
720
+ - `status-sync start` detaches the bridge and returns immediately
721
+ - `status-sync status` reports whether the bridge is alive plus paths/logs
722
+ - `status-sync stop` terminates the managed bridge process tree
723
+
485
724
  #### `mqtt-tail` sinks — route events to external services
486
725
 
487
726
  By default `mqtt-tail` prints JSONL to stdout. Use `--sink` (repeatable) to route events to one or more destinations instead:
488
727
 
489
728
  | Sink | Required flags |
490
- |---|---|
729
+ | --- | --- |
491
730
  | `stdout` | (default when no `--sink` given) |
492
731
  | `file` | `--sink-file <path>` — append JSONL |
493
732
  | `webhook` | `--webhook-url <url>` — HTTP POST each event |
494
- | `openclaw` | `--openclaw-url`, `--openclaw-token` (or `$OPENCLAW_TOKEN`), `--openclaw-model` |
495
733
  | `telegram` | `--telegram-token` (or `$TELEGRAM_TOKEN`), `--telegram-chat <chatId>` |
496
734
  | `homeassistant` | `--ha-url <url>` + `--ha-webhook-id` (no auth) or `--ha-token` (REST event API) |
497
735
 
498
736
  ```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
737
  # Generic webhook (n8n, Make, etc.)
511
738
  switchbot events mqtt-tail --sink webhook --webhook-url https://n8n.local/hook/abc
512
739
 
@@ -540,18 +767,27 @@ Supported shells: `bash`, `zsh`, `fish`, `powershell` (`pwsh` is accepted as an
540
767
  # Print the plan JSON Schema (give to your agent framework)
541
768
  switchbot plan schema
542
769
 
770
+ # Draft a candidate plan from natural language intent
771
+ switchbot plan suggest --intent "turn off all lights" --device <id1> --device <id2>
772
+
543
773
  # Validate a plan file without running it
544
774
  switchbot plan validate plan.json
545
775
 
546
776
  # Preview — mutations skipped, GETs still execute
547
777
  switchbot --dry-run plan run plan.json
548
778
 
549
- # Run pass --yes to allow destructive steps
550
- switchbot plan run plan.json --yes
779
+ # Save / review / approve / execute for destructive plans
780
+ switchbot plan save plan.json
781
+ switchbot plan review <planId>
782
+ switchbot plan approve <planId>
783
+ switchbot plan execute <planId>
551
784
  switchbot plan run plan.json --continue-on-error
785
+
786
+ # Run with per-step TTY confirmation for destructive steps (human-in-the-loop)
787
+ switchbot plan run plan.json --require-approval
552
788
  ```
553
789
 
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.
790
+ 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. `plan run` is the preview/direct path, but destructive steps are blocked by default and should go through `plan save` → `plan review` → `plan approve` → `plan execute`. See [`docs/agent-guide.md`](./docs/agent-guide.md) for the full schema and agent integration patterns.
555
791
 
556
792
  ### `devices watch` — poll status
557
793
 
@@ -575,7 +811,12 @@ Output is a JSONL stream of status-change events (with `--json`) or a refreshed
575
811
  switchbot mcp serve
576
812
  ```
577
813
 
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.
814
+ Exposes MCP tools (`list_devices`, `describe_device`, `get_device_status`,
815
+ `send_command`, `list_scenes`, `run_scene`, `search_catalog`,
816
+ `account_overview`, `plan_suggest`, `plan_run`, `audit_query`,
817
+ `audit_stats`, `policy_diff`, `policy_validate`, `policy_new`,
818
+ `policy_migrate`) plus a `switchbot://events` resource for real-time
819
+ shadow updates.
579
820
  See [`docs/agent-guide.md`](./docs/agent-guide.md) for the full tool reference and safety rules (destructive-command guard).
580
821
 
581
822
  ### `doctor` — self-check
@@ -585,7 +826,57 @@ switchbot doctor
585
826
  switchbot doctor --json
586
827
  ```
587
828
 
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.
829
+ 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.
830
+
831
+ `--json` output includes `maturityScore` (0–100) and `maturityLabel` (`production-ready` / `mostly-ready` / `needs-work` / `not-ready`) to give an at-a-glance readiness rating:
832
+
833
+ ```bash
834
+ switchbot doctor --json | jq '{score: .data.maturityScore, label: .data.maturityLabel}'
835
+ ```
836
+
837
+ Pass `--fix --yes` to auto-apply safe fixes (e.g. clear stale cache entries) without a prompt.
838
+
839
+ ### `health` — runtime health report
840
+
841
+ ```bash
842
+ # One-shot report: quota, audit error rate, circuit-breaker state
843
+ switchbot health check
844
+ switchbot health check --prometheus # Prometheus text format
845
+ switchbot health check --json
846
+
847
+ # Start a long-running HTTP server with /healthz and /metrics
848
+ switchbot health serve # default port 3100, bind 127.0.0.1
849
+ switchbot health serve --port 8080
850
+ switchbot health serve --json # print {"status":"listening",...} on start
851
+ ```
852
+
853
+ `/healthz` returns a JSON health report (HTTP 200 when `ok`/`degraded`, 503 when circuit is open).
854
+ `/metrics` returns Prometheus text metrics (`switchbot_quota_used_total`, `switchbot_circuit_open`, …).
855
+ Port conflicts are reported immediately with a clear hint to choose a different port via `--port`.
856
+
857
+ ### `upgrade-check` — version check
858
+
859
+ ```bash
860
+ switchbot upgrade-check # human output; exits 1 when update available
861
+ switchbot upgrade-check --json # structured JSON output
862
+ switchbot upgrade-check --timeout 5000 # custom registry timeout (ms)
863
+ ```
864
+
865
+ Queries the npm registry for the latest published version and compares it against the running version.
866
+ `--json` output:
867
+
868
+ ```json
869
+ {
870
+ "current": "3.2.1",
871
+ "latest": "4.0.0",
872
+ "upToDate": false,
873
+ "updateAvailable": true,
874
+ "breakingChange": true,
875
+ "installCommand": "npm install -g @switchbot/openapi-cli@4.0.0"
876
+ }
877
+ ```
878
+
879
+ `breakingChange` is `true` when the latest major version is higher than the current — useful for agents or CI that need to distinguish breaking upgrades from patch releases.
589
880
 
590
881
  ### `quota` — API request counter
591
882
 
@@ -654,7 +945,50 @@ switchbot cache clear --key list
654
945
  switchbot cache clear --key status
655
946
  ```
656
947
 
948
+ ### `policy` — validate, scaffold, and migrate policy.yaml
949
+
950
+ 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.
951
+
952
+ ```bash
953
+ # Write a starter policy at the default location
954
+ switchbot policy new # writes to the resolved default policy path
955
+ switchbot policy new ./custom/policy.yaml --force
956
+
957
+ # Validate (compiler-style errors with line:col + caret + hints)
958
+ switchbot policy validate
959
+ switchbot policy validate ./custom/policy.yaml
960
+ switchbot policy validate --json | jq '.data.errors'
961
+ switchbot policy validate --no-snippet # plain error list, no source preview
962
+
963
+ # Report the schema version the file declares
964
+ switchbot policy migrate
965
+
966
+ # Snapshot and restore the active policy
967
+ switchbot policy backup # write timestamped backup alongside policy file
968
+ switchbot policy backup --out ./backups/ # custom destination directory
969
+ switchbot policy restore <backup-file> # overwrite active policy from backup (auto-backups first)
970
+ ```
971
+
972
+ Path resolution order: positional `[path]` > `SWITCHBOT_POLICY_PATH` env var > default policy path.
657
973
 
974
+ **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`).
975
+
976
+ Example — editing an alias without quoting the deviceId:
977
+
978
+ ```console
979
+ $ switchbot policy validate
980
+ <policy-path>:14:11
981
+ 14 | bedroom light: 01-abc-12345
982
+ ^^^^^^^^^^^^^
983
+ error: /aliases/bedroom light does not match pattern ^[A-Z0-9]{2,}-[A-Z0-9-]+$
984
+ hint: paste the deviceId from `switchbot devices list --format=tsv`, e.g. 01-202407090924-26354212
985
+
986
+ ✗ 1 error in <policy-path> (schema v0.1)
987
+ ```
988
+
989
+ 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.
990
+
991
+ ## Output modes
658
992
 
659
993
  - **Default** — ANSI-colored tables for `list`/`status`, key-value tables for details.
660
994
  - **`--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 +1009,10 @@ switchbot devices status <id> --format yaml
675
1009
 
676
1010
  The CLI maintains two local disk caches under `~/.switchbot/`:
677
1011
 
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) |
1012
+ - `devices.json`: Device metadata (id, name, type, category, hub, room…).
1013
+ Default TTL: 1 hour.
1014
+ - `status.json`: Per-device status bodies.
1015
+ Default TTL: off (0).
682
1016
 
683
1017
  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
1018
 
@@ -718,32 +1052,28 @@ switchbot cache clear --key status
718
1052
 
719
1053
  ## Exit codes & error codes
720
1054
 
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) |
1055
+ - `0`: Success (including `--dry-run` intercept when validation passes).
1056
+ - `1`: Runtime error — API error, network failure, missing credentials.
1057
+ - `2`: Usage error bad flag, missing/invalid argument, unknown subcommand,
1058
+ unknown device type, invalid URL, conflicting flags.
1059
+
1060
+ Typical errors bubble up in the form `Error: <message>` on stderr. The
1061
+ SwitchBot-specific error codes mapped to readable messages:
1062
+
1063
+ - `151`: Device type error.
1064
+ - `152`: Device not found.
1065
+ - `160`: Command not supported by this device.
1066
+ - `161`: Device offline (BLE devices need a Hub).
1067
+ - `171`: Hub offline.
1068
+ - `190`: Device internal error / server busy.
1069
+ - `401`: Authentication failed (check token/secret).
1070
+ - `429`: Request rate too high (10,000 req/day cap).
739
1071
 
740
1072
  ## Environment variables
741
1073
 
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) |
1074
+ - `SWITCHBOT_TOKEN`: API token — takes priority over the config file.
1075
+ - `SWITCHBOT_SECRET`: API secret — takes priority over the config file.
1076
+ - `NO_COLOR`: Disable ANSI colors in all output (automatically respected).
747
1077
 
748
1078
  ## Scripting examples
749
1079
 
@@ -766,37 +1096,74 @@ npm install
766
1096
 
767
1097
  npm run dev -- <args> # Run from TypeScript sources via tsx
768
1098
  npm run build # Compile to dist/
769
- npm test # Run the Vitest suite (692 tests)
1099
+ npm test # Run the Vitest suite (1856 tests)
770
1100
  npm run test:watch # Watch mode
771
1101
  npm run test:coverage # Coverage report (v8, HTML + text)
772
1102
  ```
773
1103
 
774
1104
  ### Project layout
775
1105
 
776
- ```
1106
+ ```text
777
1107
  src/
778
1108
  ├── index.ts # Commander entry; mounts all subcommands; global flags
779
1109
  ├── auth.ts # HMAC-SHA256 signature (token + t + nonce → sign)
780
- ├── config.ts # Credential load/save; env > file priority; --config override
1110
+ ├── config.ts # Credential load/save; env > keychain > file priority
781
1111
  ├── api/client.ts # axios instance + request/response interceptors;
782
1112
  │ # --verbose / --dry-run / --timeout wiring
1113
+ ├── credentials/
1114
+ │ ├── keychain.ts # Credential store interface + OS backend selection
1115
+ │ └── backends/ # macos.ts / linux.ts / windows.ts / file.ts
783
1116
  ├── devices/
784
1117
  │ ├── catalog.ts # Static device catalog (commands, params, status fields)
785
1118
  │ └── cache.ts # Disk + in-memory cache for device list and status
1119
+ ├── install/
1120
+ │ ├── steps.ts # Generic step runner with rollback support
1121
+ │ ├── preflight.ts # Pre-flight checks (Node, npm, network, agent)
1122
+ │ └── default-steps.ts # Concrete steps: credentials, keychain, policy, skill, doctor
1123
+ ├── policy/
1124
+ │ ├── validate.ts # Schema version dispatch + JSON Schema validation
1125
+ │ ├── migrate.ts # v0.1 → v0.2 migration
1126
+ │ ├── load.ts # YAML file loading + error handling
1127
+ │ ├── add-rule.ts # Rule injection into automation.rules[]
1128
+ │ ├── diff.ts # Structural + line diff
1129
+ │ └── schema/v0.2.json # Authoritative v0.2 JSON Schema
1130
+ ├── rules/
1131
+ │ ├── engine.ts # Main orchestrator (MQTT + cron + webhook)
1132
+ │ ├── matcher.ts # Trigger + condition matchers
1133
+ │ ├── action.ts # Command renderer + executor
1134
+ │ ├── throttle.ts # Per-rule throttle gate
1135
+ │ ├── cron-scheduler.ts # 5-field cron + days filter
1136
+ │ ├── webhook-listener.ts # HTTP listener (bearer token, localhost-only)
1137
+ │ ├── pid-file.ts # Hot-reload via SIGHUP or sentinel file
1138
+ │ ├── audit-query.ts # Audit log filtering + aggregation
1139
+ │ ├── conflict-analyzer.ts # Static conflict detection (opposing actions,
1140
+ │ │ # high-freq MQTT, destructive cmds, quiet-hours gaps)
1141
+ │ ├── suggest.ts # Heuristic-based rule YAML generation
1142
+ │ └── types.ts # Shared rule/trigger/condition/action types
1143
+ ├── status-sync/
1144
+ │ └── manager.ts # Spawn/stop logic, state file, OpenClaw bridge
786
1145
  ├── lib/
787
1146
  │ └── devices.ts # Shared logic: listDevices, describeDevice, isDestructiveCommand
788
1147
  ├── commands/
1148
+ │ ├── auth.ts # `auth keychain` subcommand group
789
1149
  │ ├── config.ts
790
1150
  │ ├── devices.ts
791
1151
  │ ├── expand.ts # `devices expand` — semantic flag builder
792
1152
  │ ├── explain.ts # `devices explain` — one-shot device summary
793
1153
  │ ├── device-meta.ts # `devices meta` — local aliases / hide flags
1154
+ │ ├── install.ts # `switchbot install` / `uninstall`
1155
+ │ ├── policy.ts # `policy validate/new/migrate/diff/add-rule`
1156
+ │ ├── rules.ts # `rules suggest/lint/list/explain/run/reload/tail/replay/
1157
+ │ │ # conflicts/doctor/summary/last-fired/webhook-*`
794
1158
  │ ├── scenes.ts
1159
+ │ ├── health.ts # `health check/serve` — report + HTTP endpoints
1160
+ │ ├── upgrade-check.ts # `upgrade-check` — npm registry version check
1161
+ │ ├── status-sync.ts # `status-sync run/start/stop/status`
795
1162
  │ ├── webhook.ts
796
1163
  │ ├── watch.ts # `devices watch <deviceId>`
797
1164
  │ ├── events.ts # `events tail` / `events mqtt-tail`
798
1165
  │ ├── mcp.ts # `mcp serve` (MCP stdio/HTTP server)
799
- │ ├── plan.ts # `plan run/validate`
1166
+ │ ├── plan.ts # `plan run/validate/suggest`
800
1167
  │ ├── cache.ts # `cache show/clear`
801
1168
  │ ├── history.ts # `history show/replay`
802
1169
  │ ├── quota.ts # `quota status/reset`
@@ -807,11 +1174,11 @@ src/
807
1174
  │ └── completion.ts # `completion bash|zsh|fish|powershell`
808
1175
  └── utils/
809
1176
  ├── flags.ts # Global flag readers (isVerbose / isDryRun / getCacheMode / …)
810
- ├── output.ts # printTable / printKeyValue / printJson / handleError / buildErrorPayload
1177
+ ├── output.ts # printTable / printKeyValue / printJson / handleError
811
1178
  ├── format.ts # renderRows / filterFields / output-format dispatch
812
1179
  ├── audit.ts # JSONL audit log writer
813
1180
  └── quota.ts # Local daily-quota counter
814
- tests/ # Vitest suite (592 tests, mocked axios, no network)
1181
+ tests/ # Vitest suite (1856 tests, mocked axios, no network)
815
1182
  ```
816
1183
 
817
1184
  ### Release flow
@@ -836,18 +1203,29 @@ Bug reports, feature requests, and PRs are welcome.
836
1203
 
837
1204
  ## Roadmap
838
1205
 
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.
1206
+ Phase 1 through Phase 4 are shipped. The authoritative phase/track table
1207
+ (including skill-side `autonomyLevel` L1/L2/L3 mapping) lives in
1208
+ [`docs/design/roadmap.md`](./docs/design/roadmap.md).
1209
+
1210
+ Shipped tracks summary:
1211
+
1212
+ - **Track β**: one-command install/uninstall surface (`switchbot install` / `switchbot uninstall`).
1213
+ - **Track γ**: rules v0.2 runtime increment (`days` + `all`/`any`/`not`).
1214
+ - **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`).
1215
+ - **Track ζ (L3)**: autonomous rule authoring (`rules suggest`, `policy add-rule`) with MCP parity (`rules_suggest`, `policy_add_rule`).
1216
+ - **Track ε**: cross-OS keychain CI matrix (macOS + Linux libsecret + Windows Credential Manager).
1217
+
1218
+ Backlog tracks still open:
1219
+
1220
+ 1. **Daemon mode** — long-running local process with Unix/named-pipe
1221
+ transport so repeated MCP or plan invocations avoid fresh-process
1222
+ startup cost.
1223
+ 2. **`npx @switchbot/mcp-server`** — split the MCP server into a tiny
1224
+ package so non-CLI users can run it directly with `npx`.
1225
+ 3. **`switchbot self-test`** — scripted end-to-end go/no-go checks for
1226
+ token/secret validity plus representative device control.
1227
+ 4. **Record / replay** — capture request/response fixtures and replay
1228
+ offline for deterministic integration tests and CI.
851
1229
 
852
1230
  ## License
853
1231