You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(M1): add NotifyAction union type, schema oneOf, rule-notify audit kind
* feat(M2): add notify action executor with file/webhook channels and template rendering
* feat(rules): dispatch notify actions in engine + lintRules validation
* feat(mcp): add rule_notifications tool for notify audit history
* feat(doctor): add notify-connectivity check for webhook URLs in policy
* feat(llm): add intent complexity scorer
* feat(llm): add provider interface, OpenAI and Anthropic providers, factory
* feat(rules): add LLM-backed rule suggestion with auto-routing
- Add suggestRule llm param (openai | anthropic | local | auto)
- Auto mode scores intent complexity and routes to LLM when score >= 4
- LLM path calls provider.generateYaml, lints result, falls back to
heuristic with warning on provider failure
- Add buildRuleSuggestSystemPrompt embedding v0.2 schema snippet
- Wire --llm flag into CLI rules suggest command
- Add llm param to rules_suggest MCP tool
- Add llm-suggest kind to audit log with backend/model/latency fields
- Fix tests/rules/suggest.test.ts: add async/await (suggestRule is now async)
- Fix tests/llm/suggest.test.ts: set mock implementations in beforeEach
to survive vitest restoreMocks:true between tests
* docs: update roadmap and README for tracks θ (notify) and η (LLM suggest)
* fix: wire dry-run through notify, expose llm in CLI/MCP, fix rule_notifications total
- engine.ts: pass globalDryRun to executeNotifyAction so notify actions
respect both rule-level dry_run and the --dry-run flag
- notify.ts: skip webhook/file side effects and write audit with dryRun:true
when dry-run is active; add dryRun field to NotifyResult and NotifyContext
- rules.ts: add --llm <backend> option to `rules suggest` and pass it
through to suggestRule
- mcp.ts: add llm field to rules_suggest inputSchema and handler;
fix rule_notifications to report total as entries.length (pre-limit count)
* fix: forward explicit overrides to LLM, guard YAML structure, reject non-http(s) URLs
- suggest.ts: append trigger/event/schedule/days/webhookPath as explicit
constraints in the LLM user message so models cannot silently override them
- suggest.ts: validate parsed YAML is a non-null object with when/then fields
before passing to lintRules, preventing internal crashes on malformed output
- notify.ts: reject non-http/https protocols in sendWebhook with a clear error
instead of silently treating them as plain HTTP
* fix(rules): enforce dry_run, override constraints, and reject non-http URLs at lint time
- LLM-generated rules are now always coerced to dry_run:true with a warning
when the model returned dry_run:false or omitted the field.
- Caller-provided --trigger now hard-fails when the LLM returns a different
trigger source. Within the same trigger, --event/--schedule/--days/
--webhook-path mismatches override the LLM value and emit a warning.
- Notify webhook URLs with non-http(s) protocols (e.g. ftp://, file://) are
now rejected at lint time with code notify-unsupported-protocol, matching
the runtime guard.
* fix(rules): validate --llm enum at CLI; warn when auto falls back to heuristic
- `rules suggest --llm <backend>` now rejects unknown values with a
UsageError (exit 2). Previously any string fell through and was passed
along, producing confusing downstream errors. MCP rule_suggest already
enforced this via Zod enum.
- When `--llm auto` falls back to the heuristic because intent complexity
is below the LLM threshold, the result now carries a warning telling
the caller they can pass `--llm openai|anthropic` to force LLM.
* fix(rules): record llm audit after final outcome; validate llm output against policy schema
- writeAudit for kind=llm-suggest now runs after the parse/validate/lint
pipeline, with result='ok' or 'error' set from the final outcome and
the error message captured in the entry. Previously every LLM call
recorded result='ok' even when the suggestion was rejected downstream.
- Generated rules are now wrapped into a synthetic v0.2 policy and run
through validateLoadedPolicy before lint, so structural defects (wrong
types, missing fields, unknown enums) are caught with a clear schema
error instead of crashing further down the pipeline. Test fixtures
updated to use plausible SwitchBot device IDs.
* docs: document llm constraint enforcement and notify url protocol rules
- README rules-suggest section now spells out the four guardrails users
should expect: forced dry_run, hard alignment with explicit overrides,
CLI --llm enum validation, and the http(s)-only notify URL rule.
- Notify channel descriptions explicitly call out that lint rejects
schemes other than http:// / https://.
* fix(rules): expand nested notify vars, reject relative file paths, complete llm prompt schema
PR review P2 fixes:
- notify template now flattens nested event payloads into dotted keys
(event.context.deviceMac, event.list.0). Renderer used to spread only
top-level payload keys, so documented nested placeholders rendered
literally. Depth-capped at 6; Date instances become ISO strings.
- channel: file rejects non-absolute paths at lint (notify-relative-path)
and at runtime. Schema already documented absolute-path requirement;
enforcement was missing, so logs/notify.jsonl resolved against process
cwd, which differs across rules run / daemon / systemd contexts. Tilde
is not expanded.
- llm rule-suggest prompt embeds trigger and triggerWebhook defs in
addition to triggerMqtt/triggerCron, eliminating the dangling \$ref on
rule.when. Adds a Rules-section bullet describing the webhook trigger
shape so --trigger webhook flows reach schema validation cleanly.
* chore(rules): polish from code review
- runtime appendToFile error mirrors the lint hint (absolute path
example, tilde-not-expanded note) so users skipping lint get the
same guidance
- comment near rule-notify writeAudit explains keeping action.to
unsanitised is intentional forensic behaviour, not an oversight
- comment near the rule-suggest prompt template warns that
tests/llm/rule-prompt.test.ts string-slices on the
"JSON Schema:\n" / "\n\nRules:" delimiters
---------
Co-authored-by: chenliuyun <chenliuyun@onero.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,14 @@ All notable changes to `@switchbot/openapi-cli` are documented in this file.
7
7
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
8
8
This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
10
+
## [Unreleased]
11
+
12
+
### Fixed
13
+
14
+
- notify templates now expand nested event fields — dotted paths like `{{ event.context.deviceMac }}` and array indexes like `{{ event.list.0 }}` resolve instead of rendering literally.
15
+
-`channel: file` notify actions reject relative paths (lint code `notify-relative-path`, also enforced at runtime); `~` is not expanded.
16
+
- LLM rule-suggest prompt now embeds the `trigger` and `triggerWebhook` schema defs so `--trigger webhook` flows no longer produce dangling-`$ref` outputs.
`~/.switchbot/audit.log`. `rules run` is long-running; use
279
279
`daemon start` / `daemon reload` for the managed background mode.
280
280
281
+
**Actions** — each rule's `then` array accepts two action types:
282
+
283
+
-`type: command` (default, no `type` field required) — sends a device command, e.g. `devices command <id> turnOn`
284
+
-`type: notify` — delivers a payload to an external channel after the rule fires:
285
+
-`channel: webhook` — HTTP POST to a URL (only `http://` and `https://` schemes are accepted; `rules lint` rejects others)
286
+
-`channel: file` — appends a JSONL line to a local file. `to` must be an absolute path; relative or `~`-prefixed paths are rejected by `rules lint` (code `notify-relative-path`) and at runtime
287
+
-`channel: openclaw` — HTTP POST to an OpenClaw endpoint (same protocol restriction)
288
+
- Optional `template` field supports `{{ rule.name }}`, `{{ event.* }}`, `{{ device.id }}` placeholders. Nested fields use dot paths, e.g. `{{ event.context.deviceMac }}`; arrays index numerically, e.g. `{{ event.list.0 }}`
# Set OPENAI_API_KEY or ANTHROPIC_API_KEY; auto mode falls back to heuristic on failure
311
336
```
312
337
338
+
`rules suggest` enforces several guardrails on LLM output so a model can't quietly arm
339
+
something unsafe:
340
+
341
+
-**`dry_run` is forced to `true`** on every LLM-generated rule. Review the output and
342
+
flip it yourself before running the engine without `--dry-run`.
343
+
-**Explicit overrides always win.** If you pass `--trigger`, the LLM's answer must match;
344
+
a mismatch fails fast. Within the same trigger, mismatched `--event` / `--schedule` /
345
+
`--days` / `--webhook-path` are rewritten to your value with a warning.
346
+
-**`--llm` is enum-validated at the CLI** (`auto | openai | anthropic`) — junk values
347
+
exit non-zero instead of falling through.
348
+
-**Notify URLs must be `http://` or `https://`.**`rules lint` and the runtime both
349
+
reject `file://`, `ftp://`, etc., so a generated webhook can't smuggle in a non-HTTP
350
+
scheme.
351
+
313
352
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.
to generate YAML for complex intents via an LLM backend.
888
+
`rule_notifications` returns `rule-notify` audit entries, filterable by rule
889
+
name, time range, channel, and result.
847
890
See [`docs/agent-guide.md`](./docs/agent-guide.md) for the full tool reference and safety rules (destructive-command guard).
848
891
849
892
### `doctor` — self-check
@@ -853,7 +896,7 @@ switchbot doctor
853
896
switchbot doctor --json
854
897
```
855
898
856
-
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.
899
+
Runs local checks (Node version, credentials, profiles, catalog, cache, quota, clock, MQTT, policy, MCP, notify-connectivity) 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). The `notify-connectivity` check probes webhook URLs declared in `type: notify` actions. Use this to diagnose connectivity or config issues before running automation.
857
900
858
901
`--json` output includes `maturityScore` (0–100) and `maturityLabel` (`production-ready` / `mostly-ready` / `needs-work` / `not-ready`) to give an at-a-glance readiness rating:
detail: {webhookCount: webhookUrls.length,message: `${webhookUrls.length} webhook URL(s) configured (live probe not run — use --probe to test connectivity)`},
0 commit comments