Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- **Daemon start failed in bundled builds** (BUG-001): CLI entry path resolution navigated above the dist/ directory when running from the single-file bundle. Now correctly detects the bundled scenario.
- **`rules run` exited 0 when `automation.enabled` was false** (BUG-002): daemon interpreted this as success. Now exits 1 with a clear message.
- **Unknown subcommands exited 0** (BUG-005/BUG-008): `cache list`, `history list`, and other invalid subcommand inputs triggered Commander help display and exited 0. Now exits 2 (usage error).
- **`mcp tools --json` omitted description and inputSchema** (BUG-007): tool directory only listed names. Now includes full tool metadata.
- **Pino logger wrote to stdout** (BUG-009): redirected to stderr so it doesn't corrupt JSON/MCP output.

### Changed (Breaking)

- **`schemaVersion` bumped from `1.1` to `1.2`**: all `--json` responses now carry `schemaVersion: "1.2"`. Consumers that pin on the exact string must update their check. Parsers that only read `data`/`error` are unaffected.
- **`catalog show <Type> --json`**: `data` is now always an array (single-entry array when filtering by type). Previously was a bare object for single-type queries.
- **`devices commands <Type> --json`**: same change — `data` is always an array.
- **`_fetchedAt` renamed to `fetchedAt`**: removed underscore prefix from the CLI-added timestamp field in `devices status` JSON output.
- **`rules run --json` when `automation.enabled` is false**: previously emitted `{data: {kind:"control", controlKind:"disabled"}}` (success envelope) with exit 1. Now emits `{error: {code:1, kind:"runtime", message:"..."}}` (error envelope) — consistent with the JSON protocol.

### Added

- **`devices expand` supports lighting commands**: `setBrightness` (`--brightness`), `setColor` (`--color`), and `setColorTemperature` (`--color-temp`) flags now expand for Color Bulb, Strip Light, Ceiling Light, and similar devices.

## [3.4.0] - 2026-05-07

### Added
Expand Down
712 changes: 110 additions & 602 deletions README.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion docs/agent-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
}
```

### Available tools (21)
### Available tools (24)

| Tool | Purpose | Safety tier |
| --- | --- | --- |
Expand All @@ -100,6 +100,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
| `audit_query` | Filter audit log entries | read |
| `audit_stats` | Aggregate audit stats by kind/result/device/rule | read |
| `rules_suggest` | Draft automation rule YAML from intent | read |
| `rule_notifications` | Query rule notification delivery history | read |
| `rules_explain` | Show why a rule evaluation fired or was blocked | read |
| `rules_simulate` | Simulate a rule against historical events | read |
| `policy_add_rule` | Inject rule YAML into `automation.rules[]` with diff | action |

The MCP server refuses destructive commands (Smart Lock `unlock`, Garage Door `open`, etc.) unless the tool call includes `confirm: true`, and the default safety profile still blocks direct destructive execution in favor of the reviewed CLI flow (`plan save` → `plan review` → `plan approve` → `plan execute`). The allowed list is the `destructive: true` commands in the catalog — `switchbot schema export | jq '[.data.types[].commands[] | select(.destructive)]'` shows every one.
Expand Down
6 changes: 3 additions & 3 deletions docs/audit-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Every record is a JSON object with at least the following fields:

| Field | Type | Notes |
|----------------|----------------------|----------------------------------------------------------------------------------------|
| `auditVersion` | number | Schema version. Current: `1`. Missing on records written before audit versioning. |
| `auditVersion` | number | Schema version. Current: `2`. Missing on records written before audit versioning. |
| `t` | string (ISO-8601) | Timestamp when the record was written. |
| `kind` | `"command"` | Record discriminator. Currently the only kind is `command`. |
| `kind` | string | Record discriminator. Values: `command`, `rule-fire`, `rule-fire-dry`, `rule-throttled`, `rule-webhook-rejected`, `rule-notify`, `rule-evaluate`, `llm-suggest`, `llm-condition`, `llm-budget-exceeded`. |
| `deviceId` | string | Target device ID. |
| `command` | string | SwitchBot command name (e.g. `turnOn`, `setColor`). |
| `parameter` | string \| object | Command parameter as sent — `"default"` when unused. |
Expand All @@ -30,7 +30,7 @@ Every record is a JSON object with at least the following fields:
### Example

```json
{"auditVersion":1,"t":"2026-04-20T01:23:45.123Z","kind":"command","deviceId":"ABC123","command":"turnOn","parameter":"default","commandType":"command","dryRun":false,"result":"ok"}
{"auditVersion":2,"t":"2026-04-20T01:23:45.123Z","kind":"command","deviceId":"ABC123","command":"turnOn","parameter":"default","commandType":"command","dryRun":false,"result":"ok"}
```

## Crash safety
Expand Down
15 changes: 14 additions & 1 deletion docs/design/phase4-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,25 @@ were folded into the composite nodes above).

## Actions

Each `then[]` entry renders to:
Each `then[]` entry is one of two types:

**`type: command`** (default) — renders to:

```
switchbot <command with <id> substituted> <args rendered as --key value> --audit-log
```

**`type: notify`** — delivers a payload to an external channel:

```yaml
- type: notify
channel: webhook # webhook | file | openclaw
to: https://your.host/hook
template: '{"rule":"{{ rule.name }}","fired":"{{ rule.fired_at }}"}'
```

Channels: `webhook` (HTTP POST), `file` (append JSONL), `openclaw` (HTTP POST). Template supports `{{ rule.name }}`, `{{ event.* }}`, `{{ device.id }}` placeholders. Audit gains `rule-notify` kind for every notify dispatch.

Rules:

1. **Safety tier gates still apply.** If the rendered command is
Expand Down
16 changes: 6 additions & 10 deletions docs/design/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ points back to.
| Capability | This repo (`switchbot-openapi-cli`) | Cross-repo (`+ companion skill repo`) | Notes |
| --- | --- | --- | --- |
| Phase 1 (manual orchestration) | Shipped | Shipped | Stable in v2.7.x |
| Phase 2 (policy tooling) | Shipped | Shipped | v0.1 + v0.2 policy schema support |
| Phase 2 (policy tooling) | Shipped | Shipped | v0.2 policy schema (v0.1 removed in v3.0) |
| Phase 3A (keychain + install CLI) | Shipped | Shipped | `switchbot install` / `switchbot uninstall` |
| Phase 3B (skill packaging + external registry) | External tracking only | In progress outside this repo | Owned by companion skill repo |
| Phase 4 (rules engine, v0.2 model) | Shipped | Shipped | MQTT/cron/webhook + `days` + `all`/`any`/`not` |
Expand Down Expand Up @@ -81,7 +81,7 @@ reads it, the MCP server reads it, and `doctor` reports on it.

Surfaces:

- `policy new | validate | migrate | diff` (v0.1 and v0.2 schemas)
- `policy new | validate | migrate | diff` (v0.2 schema; v0.1 removed in v3.0)
- Default `policy.yaml` discovery rules
- Aliases (human-readable device names)
- Quiet hours (local-time windows, midnight-crossing supported)
Expand Down Expand Up @@ -199,23 +199,19 @@ the skill's `manifest.json` `roadmap` block, which points back here.

## Next execution queue (ordered)

1. **v0.1 policy deprecation window (post-default-flip hardening).**
Keep validating v0.1, but emit explicit migration guidance in UX/docs.
Exit when: policy docs and CLI examples consistently steer new users to
v0.2, and migration guidance is visible in `policy migrate` help.
2. **Daemon mode for repeated agent invocations.**
1. **Daemon mode for repeated agent invocations.**
Add a local long-lived process with Unix socket / named pipe transport.
Exit when: repeated MCP + plan runs no longer pay fresh-process startup,
and `doctor` can verify daemon health.
3. **Standalone MCP package (`npx @switchbot/mcp-server`).**
2. **Standalone MCP package (`npx @switchbot/mcp-server`).**
Split MCP serve entrypoint into a tiny publishable package while
preserving tool contract parity with the main CLI.
Exit when: `npx @switchbot/mcp-server` boots and passes the same MCP
contract tests as `switchbot mcp serve`.
4. **`switchbot self-test` command.**
3. **`switchbot self-test` command.**
Add scripted go/no-go checks for credentials + one representative device.
Exit when: CI can run a deterministic self-test job with pass/fail JSON.
5. **Record/replay fixtures for deterministic integration tests.**
4. **Record/replay fixtures for deterministic integration tests.**
Capture request/response transcripts and replay offline in CI.
Exit when: at least one full scenario (list → status → command guard)
is replayable without live API calls.
Expand Down
15 changes: 9 additions & 6 deletions docs/json-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ stdout.

```json
{
"schemaVersion": "1.1",
"schemaVersion": "1.2",
"data": <command-specific payload>
}
```
Expand All @@ -34,7 +34,7 @@ stdout.

```json
{
"schemaVersion": "1.1",
"schemaVersion": "1.2",
"error": {
"code": 2,
"kind": "usage" | "guard" | "api" | "runtime",
Expand Down Expand Up @@ -82,12 +82,14 @@ envelope:
### Stream header (always the first line under `--json`)

```json
{ "schemaVersion": "1", "stream": true, "eventKind": "tick" | "event", "cadence": "poll" | "push" }
{ "schemaVersion": "1.2", "stream": true, "eventKind": "tick" | "event", "cadence": "poll" | "push" }
```

- **Must always be the first line** on stdout under `--json`. Consumers
should read one line, parse, and key on `{ "stream": true }` to confirm
they are reading from a streaming command.
- `schemaVersion` is `"1.2"` for `devices watch` and `"1"` for
`events tail` / `events mqtt-tail`.
- `eventKind` picks the downstream parser. `tick` → `devices watch` shape
with `{ t, tick, deviceId, changed, ... }`. `event` → unified event
envelope (see below).
Expand Down Expand Up @@ -125,7 +127,7 @@ envelope:

```json
{
"schemaVersion": "1.1",
"schemaVersion": "1.2",
"data": {
"t": "2026-04-21T14:23:45.012Z",
"tick": 1,
Expand Down Expand Up @@ -184,9 +186,10 @@ switchbot devices status BOT1 --json | jq -e '.error' && exit 1

## 4. Versioning

- The non-streaming envelope is versioned as `schemaVersion: "1.1"`.
- The non-streaming envelope is versioned as `schemaVersion: "1.2"`.
- The streaming header and event envelope are versioned as
`schemaVersion: "1"`.
`schemaVersion: "1"` for `events tail` / `events mqtt-tail`, and
`"1.2"` for `devices watch`.
- The two axes are deliberately separate: adding a field inside `data`
does **not** bump the envelope, but renaming / removing `data` would.
- Breaking changes land on a major release. Additive fields land on a
Expand Down
Loading
Loading