Skip to content

feat: catalog sync — Weather Station, Lock Vision, Lock Vision Pro, Smart Lock Pro Wifi, AI Art Frame uploadImage#47

Merged
chenliuyun merged 7 commits into
mainfrom
feat/catalog-sync-llm-followups
May 15, 2026
Merged

feat: catalog sync — Weather Station, Lock Vision, Lock Vision Pro, Smart Lock Pro Wifi, AI Art Frame uploadImage#47
chenliuyun merged 7 commits into
mainfrom
feat/catalog-sync-llm-followups

Conversation

@chenliuyun
Copy link
Copy Markdown
Collaborator

@chenliuyun chenliuyun commented May 15, 2026

Summary

Catch up to upstream SwitchBot OpenAPI device additions:

  • Weather Station — read-only sensor (temperature / humidity / atmosphericPressure / battery)
  • Lock Vision — video lock with lock / unlock (destructive, safetyReason set)
  • Lock Vision Pro — adds deadbolt
  • Smart Lock Pro Wifi — Matter-enabled Lock Pro, registered as a Smart Lock alias
  • AI Art Frame — new uploadImage <imageUrl> command (https-only)

Catalog data only — no CATALOG_SCHEMA_VERSION bump.

Branch also includes follow-up engine work landed as separate commits (USD/token budget for llm conditions, event_count condition + recent_events hook, local / non-tool-use LLM provider, daemon JSON-RPC IPC). See individual commits for details.

Test plan

  • npm test — 2465 passed
  • npm run build — green
  • node dist/cli.js schema export --type "Weather Station" round-trips
  • node dist/cli.js devices commands "AI Art Frame" shows uploadImage
  • Lock Vision / Lock Vision Pro unlock still requires --yes / confirm:true

chenliuyun added 7 commits May 15, 2026 22:38
- Weather Station (deviceType WeatherStation) — outdoor sensor with
  atmosphericPressure, temperature, humidity, battery, version
- Lock Vision and Lock Vision Pro — video smart locks with the same
  destructive unlock guard as Smart Lock Pro; Pro variant adds deadbolt
- Smart Lock Pro Wifi alias on the existing Smart Lock entry — Matter-
  enabled Lock Pro reports a different deviceType but identical control
  surface, so an alias avoids a parallel duplicate entry
- AI Art Frame uploadImage <imageUrl> command — single https URL
  parameter, idempotent; documented assumption noted in CHANGELOG
LLM conditions now enforce per-rule and global budgets across three
dimensions: hourly call count (existing), hourly token count (new),
and daily USD cost (new). Costs are computed from a per-model pricing
table; calls to models not in the table silently skip the cost
dimension while still enforcing calls and tokens.

- DecideResult.usage carries provider-reported tokensIn / tokensOut
  and a calculated costUsd. OpenAI and Anthropic providers populate
  this from their respective response shapes.
- Schema v0.2 accepts max_tokens_per_hour and max_cost_per_day_usd
  on both per-rule budget and global automation.llm_budget.
- Audit kind llm-condition now records llmUsage; llm-budget-exceeded
  records the dimension that tripped (calls | tokens | cost).
- Two new lints: condition-llm-tokens-budget-zero (token cap of 0
  always trips on_error) and condition-llm-cost-without-known-model
  (USD cap with provider:auto silently skips models without pricing).
- The evaluator keeps backward compatibility: callers passing a
  single number still mean "global max_calls_per_hour".
Cross-event aggregation backed by the per-device history JSONL ring at
~/.switchbot/device-history/<deviceId>.jsonl. New helper queryEventWindow()
in src/devices/history-window.ts walks rotated files newest-first, applies
optional eventFilter, and bails when timestamps fall below sinceMs.

Two consumers share the same EventWindowFetcher abstraction in matcher.ts:

1. event_count condition (new oneOf branch in v0.2 schema):
     conditions:
       - event_count:
           device: front-door
           event: motion.detected
           window: "5m"
           min: 3
           max: 10
   Resolves alias to deviceId, parses duration, counts records inside
   [now - window, now], fails when count is outside [min, max].

2. LLM recent_events hook (declared in v0.2 schema since Track kappa but
   not wired): now populates context.recent_events with up to N latest
   matching events on the trigger device.

LlmConditionEvaluator is finally wired into RulesEngine (previously only
simulate had it).

New lints: condition-event-count-bad-window, condition-event-count-max-below-min.
…allback

Many users want to keep LLM conditions on-device (privacy, latency, no
per-token cost, air-gapped). This adds first-class support for any
OpenAI-compatible /v1/chat/completions endpoint — Ollama, llama.cpp
server, vLLM, LM Studio.

Default behavior: assume the endpoint does NOT support OpenAI-style
tool use (the common reality for local models). decide() routes through
a structured-output prompt that asks the model for a JSON object
{"pass": bool, "reason": str} and parses it with a lenient extractor
that handles fenced code blocks and prose-wrapped JSON. If the first
response is not parseable, one repair retry is performed before failing.

Operators on tool-use-capable local endpoints can opt in via YAML
tool_use: true or SWITCHBOT_LOCAL_LLM_TOOL_USE=1.

Surface area:
- New LLMProvider.capabilities.toolUse flag (true for openai/anthropic,
  configurable for local).
- New SWITCHBOT_LOCAL_LLM_URL / SWITCHBOT_LOCAL_LLM_MODEL /
  SWITCHBOT_LOCAL_LLM_API_KEY env vars (defaults: ollama at
  localhost:11434, llama3.2, no auth).
- New doctor check `local-llm-reachable` — only fires when policy uses
  provider:local; probes the endpoint with a 3s GET and reports latency.

createLLMProvider('local', ...) now constructs LocalProvider rather
than reusing OpenAIProvider — this fixes the (already documented as a
gap) case where a local endpoint doesn't speak tool use.
… pipe

Closes the last Track kappa follow-up: long-running clients (notably
`mcp serve`) currently pay a Node.js cold-start cost on every invocation
because the rules daemon has no way to accept commands in-process. This
adds a minimal newline-delimited JSON-RPC 2.0 endpoint:

- POSIX: ~/.switchbot/daemon.sock, mode 0600
- Windows: \.\pipe\switchbot-daemon-<user> (per-user DACL)

The path helper, server, and client all live under src/daemon/. The
server binds when `switchbot rules run` finishes engine.start() and
unbinds on stop(). v1 methods exposed:

  - daemon.status   — pid, startedAt, rulesActive, globalDryRun
  - daemon.ping     — { ok: true, t }
  - daemon.reload   — drives the same path SIGHUP / sentinel use today

Wire protocol is one JSON message per line — trivial to speak from any
language without a Content-Length parser. The single-shot client opens
a connection per call, which keeps both sides stateless; pooling can be
added later for hot paths.

New doctor check `daemon-ipc` probes the socket when the daemon is
running and reports round-trip latency. When the daemon is stopped the
check is silently skipped (status: ok, applicable: false), so users
without a long-running daemon see no warnings.

mcp.<toolName> proxying via the daemon (for `mcp serve --via-daemon`)
is intentionally deferred — the transport itself is the load-bearing
piece, and routing MCP traffic through it is a separable layer that
can land in a follow-up without changing this surface.
…licy reference

Updates docs to match the five MVPs already landed on this branch:

- roadmap.md: refresh status header to 2026-05-15, add Track κ
  (already shipped on main) under "Completed tracks", and add the
  five in-flight MVPs (Track μ catalog sync + Track λ.1-λ.4
  budget/aggregation/local-provider/IPC) under a new "In-flight"
  section. Replace the now-completed "Daemon mode" item in the
  next-execution queue with the follow-up "mcp serve --via-daemon".

- policy-reference.md: add `event_count` row to the conditions
  table and a fields block describing window/min/max + the two
  lints that guard it. Expand the LLM condition block with
  `provider: local`, the new budget dimensions
  (max_tokens_per_hour, max_cost_per_day_usd), guidance for the
  SWITCHBOT_LOCAL_LLM_* env vars, the structured-output fallback
  for non-tool-use models, and the corresponding lints + audit
  records. Add the matching global llm_budget fields.

agent-guide.md unchanged — these MVPs add no new MCP tools or
plan/CLI surface; catalog additions are auto-discovered through
schema export, and the daemon JSON-RPC surface is internal until
the upcoming `mcp serve --via-daemon` proxy lands.
Brings README in line with the five MVPs already landed on this
branch and documented in CHANGELOG / docs/policy-reference.md:

- Conditions list adds event_count alongside time_between /
  device_state / llm.
- LLM condition block now shows provider: local, the new
  max_tokens_per_hour and max_cost_per_day_usd budget dimensions,
  and the SWITCHBOT_LOCAL_LLM_URL / _MODEL / _TOOL_USE env vars
  (with structured-output fallback note for non-tool-use models).
- Doctor check list adds daemon-ipc and local-llm-reachable, with
  a one-liner about when each fires.
- Test count refreshed to 2465 in both the Features bullet and the
  Development section.
@chenliuyun chenliuyun merged commit 5817667 into main May 15, 2026
11 checks passed
@chenliuyun chenliuyun deleted the feat/catalog-sync-llm-followups branch May 16, 2026 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant