Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
765ec3f
feat: P0-4/P0-5/P0-1+P0-8 — stable error JSON, path diagnostics in do…
Apr 24, 2026
572e764
feat(iter2): P0-2 risk metadata + P0-3 plan-run audit traceability
Apr 24, 2026
310d1c7
feat(iter3): P0-6 rules safety schema + P0-7 conflict analyzer
Apr 24, 2026
dbcb3c0
feat: iteration 4 — circuit breaker, health report, --explain, keycha…
Apr 24, 2026
be78619
feat: iteration 5 — daemon MVP, upgrade-check, scenes validate/simula…
Apr 24, 2026
f95f6ba
docs: update CHANGELOG for v3.2.0 — iterations 1-5 release notes
Apr 24, 2026
cd3060f
feat: v3.2.1 — plan resource model, MCP risk profiles, rules safety p…
Apr 25, 2026
dc2e918
fix: plan execute status machine, hysteresis reset, idempotencyHint, …
Apr 25, 2026
d455472
fix(rules): correct plan execute status machine, hysteresis reset, su…
Apr 25, 2026
9bf96c6
feat(scenes): add scenes explain subcommand with risk profile and exe…
Apr 25, 2026
04ad9ec
feat(health): add health serve HTTP endpoint with /healthz and /metrics
Apr 25, 2026
1e8b723
feat(policy): add backup and restore subcommands
Apr 25, 2026
2a212ba
feat(doctor,config): add maturity score to doctor --json and agent-pr…
Apr 25, 2026
d31063f
Tighten destructive execution and daemon safety flows
Apr 25, 2026
9f059e9
fix(devices): restore --yes bypass for destructive guard, align error…
Apr 25, 2026
51332ef
fix(plan): update plan-execute test — approved plans force yes:true, …
Apr 25, 2026
f18f1d5
fix(plan-store): validate planId is UUID v4 to prevent path traversal
Apr 25, 2026
6a8fd69
fix(policy): remove dead --force option from restore command
Apr 25, 2026
8662f8b
fix(health): handle EADDRINUSE port conflict with structured error
Apr 25, 2026
ef8b7ee
fix(upgrade-check): strip prerelease suffix before semver comparison
Apr 25, 2026
51f497a
feat(rules,upgrade-check): add rules explain, breakingChange field, a…
Apr 25, 2026
6c679be
docs(readme): sync with iteration-1 additions — health, upgrade-check…
Apr 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,84 @@ All notable changes to `@switchbot/openapi-cli` are documented in this file.
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.2.1] - 2026-04-25

### Added — plan resource model, MCP risk profiles, rules safety primitives

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

### Fixed

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

## [3.2.0] - 2026-04-25

### Added — daemon, upgrade-check, scenes validate/simulate, rules summary

- `switchbot daemon start/stop/status` — runs `rules run` as a cross-platform detached
background process; PID written to `~/.switchbot/daemon.pid`; log at `daemon.log`.
- `switchbot upgrade-check` — fetches latest version from npm registry, compares semver,
and prints an upgrade command; exits 1 when a newer version is available.
- `switchbot scenes validate [sceneId...]` — confirms scene IDs exist in your account;
exits 1 if any are missing.
- `switchbot scenes simulate <sceneId>` — shows exactly what `scenes execute` would POST
without sending the request.
- `switchbot rules summary` — aggregates `rule-fire` audit entries per rule over a
configurable time window, printing a table of fires / throttled / errors.
- `switchbot rules last-fired` — shows the N most recent `rule-fire` / `rule-fire-dry`
audit entries, newest first; `--rule` filter supported.

### Added — circuit breaker, health report, `--explain`, keychain hints

- `CircuitBreaker` class in `utils/retry.ts`; module-level `apiCircuitBreaker` singleton
wired in `api/client.ts`. Opens after 5 consecutive 5xx/network failures; auto-probes
after 60 s. 4xx responses excluded from failure counting.
- `switchbot health` — reports quota usage, audit error rate, circuit-breaker state, and
process info in JSON or Prometheus text (`--prometheus`) format.
- `devices command --explain` — prints risk level, device type, and safety reason before
execution without sending the request.
- `config set-token` now prints a tip to run `switchbot auth keychain store` when
file-backend credentials are saved on a platform with a native keychain.
- `doctor` now includes a `keychain` check that warns when file-backend credentials are
in use and a native keychain is available.

### Added — rules safety schema + conflict analyzer

- Policy schema v0.2 extended with optional fields: `throttle.dedupe_window`,
`cooldown`, `requires_stable_for`. All validated by `rules lint`.
- `ThrottleGate.check()` now accepts `dedupeWindowMs` and returns `dedupedBy` field.
- `rules/conflict-analyzer.ts` — static analysis detecting opposing-action pairs,
high-frequency catch-all triggers without throttle, and destructive rule actions.
- `switchbot rules conflicts [path]` and `switchbot rules doctor [path]` subcommands.

### Added — risk metadata, plan traceability

- `agentSafetyTier` in capabilities output maps to `riskLevel` (`high`/`medium`/`low`),
`idempotencyHint`, and `recommendedMode`.
- `plan run` now generates a UUID `planId` and stamps it on every audit entry produced
during that run, enabling plan-to-audit traceability.

### Added — stable error JSON, path diagnostics, doctor enhancements

- `resolutionHint` and `candidateMatches` fields added to structured error output.
- `name-resolver.ts` now rejects ambiguous device names when multiple candidates match.
- `doctor` reports PATH discoverability and npm global bin reachability.

## [3.0.0] - 2026-04-24

Major release — breaking changes, full feature parity across all branches.
Expand Down
120 changes: 108 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Under the hood every surface shares the same catalog, cache, and HMAC client —
- [`plan`](#plan--declarative-batch-operations)
- [`mcp`](#mcp--model-context-protocol-server)
- [`doctor`](#doctor--self-check)
- [`health`](#health--runtime-health-report)
- [`upgrade-check`](#upgrade-check--version-check)
- [`quota`](#quota--api-request-counter)
- [`history`](#history--audit-log)
- [`catalog`](#catalog--device-type-catalog)
Expand Down Expand Up @@ -92,7 +94,7 @@ Under the hood every surface shares the same catalog, cache, and HMAC client —
- 🎨 **Dual output modes** — colorized tables by default; `--json` passthrough for `jq` and scripting
- 🔐 **Secure credentials** — HMAC-SHA256 signed requests; config file written with `0600`; env-var override for CI
- 🔍 **Dry-run mode** — preview every mutating request before it hits the API
- 🧪 **Fully tested** — 1765 Vitest tests, mocked axios, zero network in CI
- 🧪 **Fully tested** — 1856 Vitest tests, mocked axios, zero network in CI
- ⚡ **Shell completion** — Bash / Zsh / Fish / PowerShell

## Requirements
Expand Down Expand Up @@ -275,7 +277,7 @@ executes for you. Supported triggers: **MQTT** (device events),
Supported conditions: `time_between` (quiet hours) and `device_state`
(live API check with per-tick dedup). Every fire is recorded in
`~/.switchbot/audit.log`. `rules run` is long-running; use
`rules reload` to hot-reload policy without dropping listeners.
`daemon start` / `daemon reload` for the managed background mode.

```bash
# 1. Author rules under `automation.rules`. See examples/policies/automation.yaml
Expand All @@ -285,16 +287,37 @@ Supported conditions: `time_between` (quiet hours) and `device_state`
switchbot rules lint # exit 0 valid, 1 error
switchbot rules list --json | jq . # structured summary

# 3. Run the engine. --dry-run overrides every rule into audit-only mode;
# 3. Inspect a single rule in full detail (trigger, conditions, actions,
# cooldown, hysteresis, maxFiringsPerHour, suppressIfAlreadyDesired, last fired).
switchbot rules explain "motion on"
switchbot rules explain "motion on" --json

# 4. Run the engine. --dry-run overrides every rule into audit-only mode;
# --max-firings bounds a demo session.
switchbot rules run --dry-run --max-firings 5

# 4. Edit policy.yaml in another shell, then hot-reload without restart.
switchbot rules reload # SIGHUP on Unix, sentinel file on Windows
# 5. Edit policy.yaml in another shell, then hot-reload without restart.
switchbot daemon reload # managed daemon reload

# 5. Review recorded fires.
# 6. Review recorded fires.
switchbot rules tail --follow # stream rule-* audit lines
switchbot rules replay --since 1h --json # per-rule fires/dries/throttled/errors
switchbot rules summary # aggregate fires/errors per rule (24h window)
switchbot rules last-fired -n 20 # 20 most recent fire entries

# 7. Conflict and health analysis.
switchbot rules conflicts # opposing actions, high-frequency MQTT,
# destructive commands, quiet-hours gaps
switchbot rules doctor --json # lint + conflicts combined; exit 0 when clean
```

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.

Webhook trigger token management:

```bash
switchbot rules webhook-rotate-token # rotate the bearer token for webhook triggers
switchbot rules webhook-show-token # print current token (creates one if absent)
```

See [`docs/design/phase4-rules.md`](./docs/design/phase4-rules.md) for
Expand Down Expand Up @@ -356,6 +379,12 @@ switchbot devices command ABC123 turnOn --dry-run
switchbot config set-token <token> <secret> # Save to ~/.switchbot/config.json
switchbot config show # Print current source + masked secret
switchbot config list-profiles # List saved profiles

# Print (or write) the recommended AI-agent profile template
switchbot config agent-profile # print to stdout
switchbot config agent-profile --write # write to ~/.switchbot/profiles/agent.json (mode 0600)
switchbot config agent-profile --write --force # overwrite if it already exists
switchbot config agent-profile --json # structured JSON envelope
```

### `devices` — list, status, control
Expand Down Expand Up @@ -551,6 +580,10 @@ skipped devices appear under `summary.skipped` with `skippedReason:'offline'`.
```bash
switchbot scenes list # Columns: sceneId, sceneName
switchbot scenes execute <sceneId>

# One-shot summary: risk profile, execution hint, estimated commands
switchbot scenes explain <sceneId>
switchbot scenes explain <sceneId> --json
```

### `webhook` — receive device events over HTTP
Expand Down Expand Up @@ -743,15 +776,18 @@ switchbot plan validate plan.json
# Preview — mutations skipped, GETs still execute
switchbot --dry-run plan run plan.json

# Run — pass --yes to allow destructive steps
switchbot plan run plan.json --yes
# Save / review / approve / execute for destructive plans
switchbot plan save plan.json
switchbot plan review <planId>
switchbot plan approve <planId>
switchbot plan execute <planId>
switchbot plan run plan.json --continue-on-error

# Run with per-step TTY confirmation for destructive steps (human-in-the-loop)
switchbot plan run plan.json --require-approval
```

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.
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.

### `devices watch` — poll status

Expand Down Expand Up @@ -792,6 +828,56 @@ switchbot doctor --json

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.

`--json` output includes `maturityScore` (0–100) and `maturityLabel` (`production-ready` / `mostly-ready` / `needs-work` / `not-ready`) to give an at-a-glance readiness rating:

```bash
switchbot doctor --json | jq '{score: .data.maturityScore, label: .data.maturityLabel}'
```

Pass `--fix --yes` to auto-apply safe fixes (e.g. clear stale cache entries) without a prompt.

### `health` — runtime health report

```bash
# One-shot report: quota, audit error rate, circuit-breaker state
switchbot health check
switchbot health check --prometheus # Prometheus text format
switchbot health check --json

# Start a long-running HTTP server with /healthz and /metrics
switchbot health serve # default port 3100, bind 127.0.0.1
switchbot health serve --port 8080
switchbot health serve --json # print {"status":"listening",...} on start
```

`/healthz` returns a JSON health report (HTTP 200 when `ok`/`degraded`, 503 when circuit is open).
`/metrics` returns Prometheus text metrics (`switchbot_quota_used_total`, `switchbot_circuit_open`, …).
Port conflicts are reported immediately with a clear hint to choose a different port via `--port`.

### `upgrade-check` — version check

```bash
switchbot upgrade-check # human output; exits 1 when update available
switchbot upgrade-check --json # structured JSON output
switchbot upgrade-check --timeout 5000 # custom registry timeout (ms)
```

Queries the npm registry for the latest published version and compares it against the running version.
`--json` output:

```json
{
"current": "3.2.1",
"latest": "4.0.0",
"upToDate": false,
"updateAvailable": true,
"breakingChange": true,
"installCommand": "npm install -g @switchbot/openapi-cli@4.0.0"
}
```

`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.

### `quota` — API request counter

```bash
Expand Down Expand Up @@ -876,6 +962,11 @@ switchbot policy validate --no-snippet # plain error list, no source

# Report the schema version the file declares
switchbot policy migrate

# Snapshot and restore the active policy
switchbot policy backup # write timestamped backup alongside policy file
switchbot policy backup --out ./backups/ # custom destination directory
switchbot policy restore <backup-file> # overwrite active policy from backup (auto-backups first)
```

Path resolution order: positional `[path]` > `SWITCHBOT_POLICY_PATH` env var > default policy path.
Expand Down Expand Up @@ -1005,7 +1096,7 @@ npm install

npm run dev -- <args> # Run from TypeScript sources via tsx
npm run build # Compile to dist/
npm test # Run the Vitest suite (1765 tests)
npm test # Run the Vitest suite (1856 tests)
npm run test:watch # Watch mode
npm run test:coverage # Coverage report (v8, HTML + text)
```
Expand Down Expand Up @@ -1045,6 +1136,8 @@ src/
│ ├── webhook-listener.ts # HTTP listener (bearer token, localhost-only)
│ ├── pid-file.ts # Hot-reload via SIGHUP or sentinel file
│ ├── audit-query.ts # Audit log filtering + aggregation
│ ├── conflict-analyzer.ts # Static conflict detection (opposing actions,
│ │ # high-freq MQTT, destructive cmds, quiet-hours gaps)
│ ├── suggest.ts # Heuristic-based rule YAML generation
│ └── types.ts # Shared rule/trigger/condition/action types
├── status-sync/
Expand All @@ -1060,8 +1153,11 @@ src/
│ ├── device-meta.ts # `devices meta` — local aliases / hide flags
│ ├── install.ts # `switchbot install` / `uninstall`
│ ├── policy.ts # `policy validate/new/migrate/diff/add-rule`
│ ├── rules.ts # `rules suggest/lint/list/run/reload/tail/replay`
│ ├── rules.ts # `rules suggest/lint/list/explain/run/reload/tail/replay/
│ │ # conflicts/doctor/summary/last-fired/webhook-*`
│ ├── scenes.ts
│ ├── health.ts # `health check/serve` — report + HTTP endpoints
│ ├── upgrade-check.ts # `upgrade-check` — npm registry version check
│ ├── status-sync.ts # `status-sync run/start/stop/status`
│ ├── webhook.ts
│ ├── watch.ts # `devices watch <deviceId>`
Expand All @@ -1082,7 +1178,7 @@ src/
├── format.ts # renderRows / filterFields / output-format dispatch
├── audit.ts # JSONL audit log writer
└── quota.ts # Local daily-quota counter
tests/ # Vitest suite (1765 tests, mocked axios, no network)
tests/ # Vitest suite (1856 tests, mocked axios, no network)
```

### Release flow
Expand Down
Loading
Loading