@switchbot/openapi-cli 3.2.0 → 3.2.1

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 (331) hide show
  1. package/dist/api/client.d.ts +31 -0
  2. package/dist/api/client.js +236 -0
  3. package/dist/api/client.js.map +1 -0
  4. package/dist/auth.d.ts +1 -0
  5. package/dist/auth.js +21 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/commands/agent-bootstrap.d.ts +10 -0
  8. package/dist/commands/agent-bootstrap.js +200 -0
  9. package/dist/commands/agent-bootstrap.js.map +1 -0
  10. package/dist/commands/auth.d.ts +18 -0
  11. package/dist/commands/auth.js +355 -0
  12. package/dist/commands/auth.js.map +1 -0
  13. package/dist/commands/batch.d.ts +2 -0
  14. package/dist/commands/batch.js +414 -0
  15. package/dist/commands/batch.js.map +1 -0
  16. package/dist/commands/cache.d.ts +2 -0
  17. package/dist/commands/cache.js +127 -0
  18. package/dist/commands/cache.js.map +1 -0
  19. package/dist/commands/capabilities.d.ts +31 -0
  20. package/dist/commands/capabilities.js +383 -0
  21. package/dist/commands/capabilities.js.map +1 -0
  22. package/dist/commands/catalog.d.ts +2 -0
  23. package/dist/commands/catalog.js +360 -0
  24. package/dist/commands/catalog.js.map +1 -0
  25. package/dist/commands/completion.d.ts +2 -0
  26. package/dist/commands/completion.js +386 -0
  27. package/dist/commands/completion.js.map +1 -0
  28. package/dist/commands/config.d.ts +21 -0
  29. package/dist/commands/config.js +377 -0
  30. package/dist/commands/config.js.map +1 -0
  31. package/dist/commands/daemon.d.ts +2 -0
  32. package/dist/commands/daemon.js +411 -0
  33. package/dist/commands/daemon.js.map +1 -0
  34. package/dist/commands/device-meta.d.ts +2 -0
  35. package/dist/commands/device-meta.js +160 -0
  36. package/dist/commands/device-meta.js.map +1 -0
  37. package/dist/commands/devices.d.ts +2 -0
  38. package/dist/commands/devices.js +949 -0
  39. package/dist/commands/devices.js.map +1 -0
  40. package/dist/commands/doctor.d.ts +3 -0
  41. package/dist/commands/doctor.js +1016 -0
  42. package/dist/commands/doctor.js.map +1 -0
  43. package/dist/commands/events.d.ts +31 -0
  44. package/dist/commands/events.js +564 -0
  45. package/dist/commands/events.js.map +1 -0
  46. package/dist/commands/expand.d.ts +2 -0
  47. package/dist/commands/expand.js +131 -0
  48. package/dist/commands/expand.js.map +1 -0
  49. package/dist/commands/explain.d.ts +2 -0
  50. package/dist/commands/explain.js +140 -0
  51. package/dist/commands/explain.js.map +1 -0
  52. package/dist/commands/health.d.ts +8 -0
  53. package/dist/commands/health.js +114 -0
  54. package/dist/commands/health.js.map +1 -0
  55. package/dist/commands/history.d.ts +2 -0
  56. package/dist/commands/history.js +321 -0
  57. package/dist/commands/history.js.map +1 -0
  58. package/dist/commands/identity.d.ts +45 -0
  59. package/dist/commands/identity.js +60 -0
  60. package/dist/commands/identity.js.map +1 -0
  61. package/dist/commands/install.d.ts +20 -0
  62. package/dist/commands/install.js +247 -0
  63. package/dist/commands/install.js.map +1 -0
  64. package/dist/commands/mcp.d.ts +14 -0
  65. package/dist/commands/mcp.js +2018 -0
  66. package/dist/commands/mcp.js.map +1 -0
  67. package/dist/commands/plan.d.ts +51 -0
  68. package/dist/commands/plan.js +654 -0
  69. package/dist/commands/plan.js.map +1 -0
  70. package/dist/commands/policy.d.ts +24 -0
  71. package/dist/commands/policy.js +587 -0
  72. package/dist/commands/policy.js.map +1 -0
  73. package/dist/commands/quota.d.ts +2 -0
  74. package/dist/commands/quota.js +79 -0
  75. package/dist/commands/quota.js.map +1 -0
  76. package/dist/commands/rules.d.ts +2 -0
  77. package/dist/commands/rules.js +876 -0
  78. package/dist/commands/rules.js.map +1 -0
  79. package/dist/commands/scenes.d.ts +2 -0
  80. package/dist/commands/scenes.js +265 -0
  81. package/dist/commands/scenes.js.map +1 -0
  82. package/dist/commands/schema.d.ts +2 -0
  83. package/dist/commands/schema.js +185 -0
  84. package/dist/commands/schema.js.map +1 -0
  85. package/dist/commands/status-sync.d.ts +2 -0
  86. package/dist/commands/status-sync.js +132 -0
  87. package/dist/commands/status-sync.js.map +1 -0
  88. package/dist/commands/uninstall.d.ts +20 -0
  89. package/dist/commands/uninstall.js +238 -0
  90. package/dist/commands/uninstall.js.map +1 -0
  91. package/dist/commands/upgrade-check.d.ts +2 -0
  92. package/dist/commands/upgrade-check.js +107 -0
  93. package/dist/commands/upgrade-check.js.map +1 -0
  94. package/dist/commands/watch.d.ts +2 -0
  95. package/dist/commands/watch.js +195 -0
  96. package/dist/commands/watch.js.map +1 -0
  97. package/dist/commands/webhook.d.ts +2 -0
  98. package/dist/commands/webhook.js +183 -0
  99. package/dist/commands/webhook.js.map +1 -0
  100. package/dist/config.d.ts +57 -0
  101. package/dist/config.js +259 -0
  102. package/dist/config.js.map +1 -0
  103. package/dist/credentials/backends/file.d.ts +18 -0
  104. package/dist/credentials/backends/file.js +102 -0
  105. package/dist/credentials/backends/file.js.map +1 -0
  106. package/dist/credentials/backends/linux.d.ts +16 -0
  107. package/dist/credentials/backends/linux.js +130 -0
  108. package/dist/credentials/backends/linux.js.map +1 -0
  109. package/dist/credentials/backends/macos.d.ts +18 -0
  110. package/dist/credentials/backends/macos.js +130 -0
  111. package/dist/credentials/backends/macos.js.map +1 -0
  112. package/dist/credentials/backends/windows.d.ts +23 -0
  113. package/dist/credentials/backends/windows.js +216 -0
  114. package/dist/credentials/backends/windows.js.map +1 -0
  115. package/dist/credentials/keychain.d.ts +83 -0
  116. package/dist/credentials/keychain.js +89 -0
  117. package/dist/credentials/keychain.js.map +1 -0
  118. package/dist/credentials/prime.d.ts +32 -0
  119. package/dist/credentials/prime.js +53 -0
  120. package/dist/credentials/prime.js.map +1 -0
  121. package/dist/devices/cache.d.ts +79 -0
  122. package/dist/devices/cache.js +294 -0
  123. package/dist/devices/cache.js.map +1 -0
  124. package/dist/devices/catalog.d.ts +138 -0
  125. package/dist/devices/catalog.js +768 -0
  126. package/dist/devices/catalog.js.map +1 -0
  127. package/dist/devices/device-meta.d.ts +15 -0
  128. package/dist/devices/device-meta.js +57 -0
  129. package/dist/devices/device-meta.js.map +1 -0
  130. package/dist/devices/history-agg.d.ts +37 -0
  131. package/dist/devices/history-agg.js +139 -0
  132. package/dist/devices/history-agg.js.map +1 -0
  133. package/dist/devices/history-query.d.ts +45 -0
  134. package/dist/devices/history-query.js +182 -0
  135. package/dist/devices/history-query.js.map +1 -0
  136. package/dist/devices/param-validator.d.ts +40 -0
  137. package/dist/devices/param-validator.js +434 -0
  138. package/dist/devices/param-validator.js.map +1 -0
  139. package/dist/devices/resources.d.ts +74 -0
  140. package/dist/devices/resources.js +271 -0
  141. package/dist/devices/resources.js.map +1 -0
  142. package/dist/index.d.ts +1 -0
  143. package/dist/index.js +170 -56946
  144. package/dist/index.js.map +1 -0
  145. package/dist/install/default-steps.d.ts +66 -0
  146. package/dist/install/default-steps.js +258 -0
  147. package/dist/install/default-steps.js.map +1 -0
  148. package/dist/install/preflight.d.ts +60 -0
  149. package/dist/install/preflight.js +213 -0
  150. package/dist/install/preflight.js.map +1 -0
  151. package/dist/install/steps.d.ts +61 -0
  152. package/dist/install/steps.js +68 -0
  153. package/dist/install/steps.js.map +1 -0
  154. package/dist/lib/command-keywords.d.ts +5 -0
  155. package/dist/lib/command-keywords.js +18 -0
  156. package/dist/lib/command-keywords.js.map +1 -0
  157. package/dist/lib/daemon-state.d.ts +24 -0
  158. package/dist/lib/daemon-state.js +47 -0
  159. package/dist/lib/daemon-state.js.map +1 -0
  160. package/dist/lib/destructive-mode.d.ts +2 -0
  161. package/dist/lib/destructive-mode.js +13 -0
  162. package/dist/lib/destructive-mode.js.map +1 -0
  163. package/dist/lib/devices.d.ts +151 -0
  164. package/dist/lib/devices.js +383 -0
  165. package/dist/lib/devices.js.map +1 -0
  166. package/dist/lib/idempotency.d.ts +46 -0
  167. package/dist/lib/idempotency.js +107 -0
  168. package/dist/lib/idempotency.js.map +1 -0
  169. package/dist/lib/plan-store.d.ts +19 -0
  170. package/dist/lib/plan-store.js +69 -0
  171. package/dist/lib/plan-store.js.map +1 -0
  172. package/dist/lib/request-context.d.ts +7 -0
  173. package/dist/lib/request-context.js +13 -0
  174. package/dist/lib/request-context.js.map +1 -0
  175. package/dist/lib/scenes.d.ts +7 -0
  176. package/dist/lib/scenes.js +11 -0
  177. package/dist/lib/scenes.js.map +1 -0
  178. package/dist/logger.d.ts +4 -0
  179. package/dist/logger.js +17 -0
  180. package/dist/logger.js.map +1 -0
  181. package/dist/mcp/device-history.d.ts +36 -0
  182. package/dist/mcp/device-history.js +146 -0
  183. package/dist/mcp/device-history.js.map +1 -0
  184. package/dist/mcp/events-subscription.d.ts +45 -0
  185. package/dist/mcp/events-subscription.js +214 -0
  186. package/dist/mcp/events-subscription.js.map +1 -0
  187. package/dist/mqtt/client.d.ts +25 -0
  188. package/dist/mqtt/client.js +181 -0
  189. package/dist/mqtt/client.js.map +1 -0
  190. package/dist/mqtt/credential.d.ts +16 -0
  191. package/dist/mqtt/credential.js +31 -0
  192. package/dist/mqtt/credential.js.map +1 -0
  193. package/dist/policy/add-rule.d.ts +21 -0
  194. package/dist/policy/add-rule.js +125 -0
  195. package/dist/policy/add-rule.js.map +1 -0
  196. package/dist/policy/diff.d.ts +21 -0
  197. package/dist/policy/diff.js +92 -0
  198. package/dist/policy/diff.js.map +1 -0
  199. package/dist/policy/format.d.ts +6 -0
  200. package/dist/policy/format.js +58 -0
  201. package/dist/policy/format.js.map +1 -0
  202. package/dist/policy/load.d.ts +32 -0
  203. package/dist/policy/load.js +62 -0
  204. package/dist/policy/load.js.map +1 -0
  205. package/dist/policy/migrate.d.ts +21 -0
  206. package/dist/policy/migrate.js +68 -0
  207. package/dist/policy/migrate.js.map +1 -0
  208. package/dist/policy/schema.d.ts +5 -0
  209. package/dist/policy/schema.js +19 -0
  210. package/dist/policy/schema.js.map +1 -0
  211. package/dist/policy/validate.d.ts +19 -0
  212. package/dist/policy/validate.js +263 -0
  213. package/dist/policy/validate.js.map +1 -0
  214. package/dist/rules/action.d.ts +65 -0
  215. package/dist/rules/action.js +217 -0
  216. package/dist/rules/action.js.map +1 -0
  217. package/dist/rules/audit-query.d.ts +51 -0
  218. package/dist/rules/audit-query.js +90 -0
  219. package/dist/rules/audit-query.js.map +1 -0
  220. package/dist/rules/conflict-analyzer.d.ts +57 -0
  221. package/dist/rules/conflict-analyzer.js +215 -0
  222. package/dist/rules/conflict-analyzer.js.map +1 -0
  223. package/dist/rules/cron-scheduler.d.ts +62 -0
  224. package/dist/rules/cron-scheduler.js +187 -0
  225. package/dist/rules/cron-scheduler.js.map +1 -0
  226. package/dist/rules/destructive.d.ts +20 -0
  227. package/dist/rules/destructive.js +53 -0
  228. package/dist/rules/destructive.js.map +1 -0
  229. package/dist/rules/engine.d.ts +193 -0
  230. package/dist/rules/engine.js +758 -0
  231. package/dist/rules/engine.js.map +1 -0
  232. package/dist/rules/matcher.d.ts +56 -0
  233. package/dist/rules/matcher.js +231 -0
  234. package/dist/rules/matcher.js.map +1 -0
  235. package/dist/rules/pid-file.d.ts +43 -0
  236. package/dist/rules/pid-file.js +96 -0
  237. package/dist/rules/pid-file.js.map +1 -0
  238. package/dist/rules/quiet-hours.d.ts +26 -0
  239. package/dist/rules/quiet-hours.js +46 -0
  240. package/dist/rules/quiet-hours.js.map +1 -0
  241. package/dist/rules/suggest.d.ts +20 -0
  242. package/dist/rules/suggest.js +96 -0
  243. package/dist/rules/suggest.js.map +1 -0
  244. package/dist/rules/throttle.d.ts +61 -0
  245. package/dist/rules/throttle.js +117 -0
  246. package/dist/rules/throttle.js.map +1 -0
  247. package/dist/rules/types.d.ts +117 -0
  248. package/dist/rules/types.js +35 -0
  249. package/dist/rules/types.js.map +1 -0
  250. package/dist/rules/webhook-listener.d.ts +63 -0
  251. package/dist/rules/webhook-listener.js +224 -0
  252. package/dist/rules/webhook-listener.js.map +1 -0
  253. package/dist/rules/webhook-token.d.ts +50 -0
  254. package/dist/rules/webhook-token.js +91 -0
  255. package/dist/rules/webhook-token.js.map +1 -0
  256. package/dist/schema/field-aliases.d.ts +34 -0
  257. package/dist/schema/field-aliases.js +132 -0
  258. package/dist/schema/field-aliases.js.map +1 -0
  259. package/dist/sinks/dispatcher.d.ts +7 -0
  260. package/dist/sinks/dispatcher.js +13 -0
  261. package/dist/sinks/dispatcher.js.map +1 -0
  262. package/dist/sinks/file.d.ts +6 -0
  263. package/dist/sinks/file.js +20 -0
  264. package/dist/sinks/file.js.map +1 -0
  265. package/dist/sinks/format.d.ts +20 -0
  266. package/dist/sinks/format.js +57 -0
  267. package/dist/sinks/format.js.map +1 -0
  268. package/dist/sinks/homeassistant.d.ts +18 -0
  269. package/dist/sinks/homeassistant.js +45 -0
  270. package/dist/sinks/homeassistant.js.map +1 -0
  271. package/dist/sinks/openclaw.d.ts +13 -0
  272. package/dist/sinks/openclaw.js +34 -0
  273. package/dist/sinks/openclaw.js.map +1 -0
  274. package/dist/sinks/stdout.d.ts +4 -0
  275. package/dist/sinks/stdout.js +6 -0
  276. package/dist/sinks/stdout.js.map +1 -0
  277. package/dist/sinks/telegram.d.ts +11 -0
  278. package/dist/sinks/telegram.js +29 -0
  279. package/dist/sinks/telegram.js.map +1 -0
  280. package/dist/sinks/types.d.ts +13 -0
  281. package/dist/sinks/types.js +2 -0
  282. package/dist/sinks/types.js.map +1 -0
  283. package/dist/sinks/webhook.d.ts +6 -0
  284. package/dist/sinks/webhook.js +23 -0
  285. package/dist/sinks/webhook.js.map +1 -0
  286. package/dist/status-sync/manager.d.ts +48 -0
  287. package/dist/status-sync/manager.js +269 -0
  288. package/dist/status-sync/manager.js.map +1 -0
  289. package/dist/utils/arg-parsers.d.ts +16 -0
  290. package/dist/utils/arg-parsers.js +67 -0
  291. package/dist/utils/arg-parsers.js.map +1 -0
  292. package/dist/utils/audit.d.ts +69 -0
  293. package/dist/utils/audit.js +122 -0
  294. package/dist/utils/audit.js.map +1 -0
  295. package/dist/utils/filter.d.ts +81 -0
  296. package/dist/utils/filter.js +190 -0
  297. package/dist/utils/filter.js.map +1 -0
  298. package/dist/utils/flags.d.ts +72 -0
  299. package/dist/utils/flags.js +187 -0
  300. package/dist/utils/flags.js.map +1 -0
  301. package/dist/utils/format.d.ts +9 -0
  302. package/dist/utils/format.js +118 -0
  303. package/dist/utils/format.js.map +1 -0
  304. package/dist/utils/health.d.ts +48 -0
  305. package/dist/utils/health.js +102 -0
  306. package/dist/utils/health.js.map +1 -0
  307. package/dist/utils/help-json.d.ts +39 -0
  308. package/dist/utils/help-json.js +55 -0
  309. package/dist/utils/help-json.js.map +1 -0
  310. package/dist/utils/name-resolver.d.ts +26 -0
  311. package/dist/utils/name-resolver.js +138 -0
  312. package/dist/utils/name-resolver.js.map +1 -0
  313. package/dist/utils/output.d.ts +73 -0
  314. package/dist/utils/output.js +405 -0
  315. package/dist/utils/output.js.map +1 -0
  316. package/dist/utils/quota.d.ts +61 -0
  317. package/dist/utils/quota.js +228 -0
  318. package/dist/utils/quota.js.map +1 -0
  319. package/dist/utils/redact.d.ts +23 -0
  320. package/dist/utils/redact.js +69 -0
  321. package/dist/utils/redact.js.map +1 -0
  322. package/dist/utils/retry.d.ts +72 -0
  323. package/dist/utils/retry.js +141 -0
  324. package/dist/utils/retry.js.map +1 -0
  325. package/dist/utils/string.d.ts +2 -0
  326. package/dist/utils/string.js +23 -0
  327. package/dist/utils/string.js.map +1 -0
  328. package/dist/version.d.ts +2 -0
  329. package/dist/version.js +5 -0
  330. package/dist/version.js.map +1 -0
  331. package/package.json +2 -2
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Rule action executor — the only place that calls into `executeCommand`
3
+ * from the rules pipeline.
4
+ *
5
+ * Responsibilities:
6
+ * 1. Parse the `command` string into a `{ deviceId, verb, parameter }`
7
+ * tuple, rejecting shapes the PoC doesn't understand.
8
+ * 2. Enforce the destructive-command blocklist as a second line of
9
+ * defence (the validator should have caught it at load time — this
10
+ * protects against hand-crafted engine inputs).
11
+ * 3. Resolve `action.device` (alias or deviceId) into the `<id>`
12
+ * slot.
13
+ * 4. Branch on `dry_run`: dry-run writes audit with kind
14
+ * `rule-fire-dry` and returns without touching the API.
15
+ * 5. Live run delegates to `executeCommand`, then re-writes audit
16
+ * with the rule-scoped kind + fireId so `rules tail` / `replay`
17
+ * can correlate multi-action fires.
18
+ */
19
+ import { executeCommand } from '../lib/devices.js';
20
+ import { writeAudit } from '../utils/audit.js';
21
+ import { isDestructiveCommand } from './destructive.js';
22
+ const DEVICES_COMMAND_RE = /^devices\s+command\s+(\S+)\s+(\S+)(?:\s+(.*))?$/;
23
+ export function parseRuleCommand(cmd) {
24
+ const m = DEVICES_COMMAND_RE.exec(cmd.trim());
25
+ if (!m)
26
+ return null;
27
+ const deviceIdSlot = m[1];
28
+ const verb = m[2];
29
+ const rest = (m[3] ?? '').trim();
30
+ return {
31
+ deviceIdSlot,
32
+ verb,
33
+ parameterTokens: rest.length === 0 ? [] : rest.split(/\s+/),
34
+ };
35
+ }
36
+ /** Alias-first resolver — falls back to the raw value (assumed deviceId). */
37
+ export function resolveActionDevice(explicit, slot, aliases) {
38
+ // Explicit device field on the action wins.
39
+ const candidate = explicit ?? (slot && slot !== '<id>' ? slot : null);
40
+ if (!candidate)
41
+ return null;
42
+ if (aliases[candidate])
43
+ return aliases[candidate];
44
+ return candidate;
45
+ }
46
+ /**
47
+ * Render a parameter for SwitchBot's command API. For the PoC we pass
48
+ * the raw token string for single-token args, join with `:` for
49
+ * multi-token args (matches the CLI's `devices command` convention),
50
+ * and `undefined` when no tokens were supplied (the SDK substitutes
51
+ * `'default'`).
52
+ */
53
+ function renderParameter(tokens) {
54
+ if (tokens.length === 0)
55
+ return undefined;
56
+ if (tokens.length === 1)
57
+ return tokens[0];
58
+ return tokens.join(':');
59
+ }
60
+ export async function executeRuleAction(action, ctx) {
61
+ const parsed = parseRuleCommand(action.command);
62
+ if (!parsed) {
63
+ writeAudit({
64
+ t: new Date().toISOString(),
65
+ kind: 'rule-fire',
66
+ deviceId: 'unknown',
67
+ command: action.command,
68
+ parameter: null,
69
+ commandType: 'command',
70
+ dryRun: true,
71
+ result: 'error',
72
+ error: 'unparseable-command',
73
+ rule: {
74
+ name: ctx.rule.name,
75
+ triggerSource: ctx.rule.when.source,
76
+ fireId: ctx.fireId,
77
+ reason: 'unparseable-command',
78
+ },
79
+ });
80
+ return { ok: false, error: 'unparseable-command', blocked: true };
81
+ }
82
+ if (isDestructiveCommand(action.command)) {
83
+ writeAudit({
84
+ t: new Date().toISOString(),
85
+ kind: 'rule-fire',
86
+ deviceId: resolveActionDevice(action.device, parsed.deviceIdSlot, ctx.aliases) ?? 'unknown',
87
+ command: action.command,
88
+ parameter: null,
89
+ commandType: 'command',
90
+ dryRun: true,
91
+ result: 'error',
92
+ error: `destructive-verb:${parsed.verb}`,
93
+ rule: {
94
+ name: ctx.rule.name,
95
+ triggerSource: ctx.rule.when.source,
96
+ fireId: ctx.fireId,
97
+ reason: `destructive verb "${parsed.verb}" refused at runtime`,
98
+ },
99
+ });
100
+ return { ok: false, error: `destructive-verb:${parsed.verb}`, blocked: true, verb: parsed.verb };
101
+ }
102
+ const deviceId = resolveActionDevice(action.device, parsed.deviceIdSlot, ctx.aliases);
103
+ if (!deviceId || deviceId === '<id>') {
104
+ writeAudit({
105
+ t: new Date().toISOString(),
106
+ kind: 'rule-fire',
107
+ deviceId: 'unknown',
108
+ command: action.command,
109
+ parameter: null,
110
+ commandType: 'command',
111
+ dryRun: true,
112
+ result: 'error',
113
+ error: 'missing-device',
114
+ rule: {
115
+ name: ctx.rule.name,
116
+ triggerSource: ctx.rule.when.source,
117
+ fireId: ctx.fireId,
118
+ reason: 'action omitted `device` and command used `<id>` placeholder',
119
+ },
120
+ });
121
+ return { ok: false, error: 'missing-device', verb: parsed.verb };
122
+ }
123
+ const dryRun = ctx.globalDryRun === true || ctx.rule.dry_run === true;
124
+ const parameter = renderParameter(parsed.parameterTokens);
125
+ if (dryRun) {
126
+ writeAudit({
127
+ t: new Date().toISOString(),
128
+ kind: 'rule-fire-dry',
129
+ deviceId,
130
+ command: parsed.verb,
131
+ parameter: parameter ?? 'default',
132
+ commandType: 'command',
133
+ dryRun: true,
134
+ result: 'ok',
135
+ rule: {
136
+ name: ctx.rule.name,
137
+ triggerSource: ctx.rule.when.source,
138
+ matchedDevice: deviceId,
139
+ fireId: ctx.fireId,
140
+ },
141
+ });
142
+ return { ok: true, dryRun: true, deviceId, verb: parsed.verb };
143
+ }
144
+ if (ctx.skipApiCall) {
145
+ writeAudit({
146
+ t: new Date().toISOString(),
147
+ kind: 'rule-fire',
148
+ deviceId,
149
+ command: parsed.verb,
150
+ parameter: parameter ?? 'default',
151
+ commandType: 'command',
152
+ dryRun: false,
153
+ result: 'ok',
154
+ rule: {
155
+ name: ctx.rule.name,
156
+ triggerSource: ctx.rule.when.source,
157
+ matchedDevice: deviceId,
158
+ fireId: ctx.fireId,
159
+ reason: 'api-skipped',
160
+ },
161
+ });
162
+ return { ok: true, deviceId, verb: parsed.verb };
163
+ }
164
+ try {
165
+ await executeCommand(deviceId, parsed.verb, parameter, 'command', ctx.httpClient);
166
+ writeAudit({
167
+ t: new Date().toISOString(),
168
+ kind: 'rule-fire',
169
+ deviceId,
170
+ command: parsed.verb,
171
+ parameter: parameter ?? 'default',
172
+ commandType: 'command',
173
+ dryRun: false,
174
+ result: 'ok',
175
+ rule: {
176
+ name: ctx.rule.name,
177
+ triggerSource: ctx.rule.when.source,
178
+ matchedDevice: deviceId,
179
+ fireId: ctx.fireId,
180
+ },
181
+ });
182
+ return { ok: true, deviceId, verb: parsed.verb };
183
+ }
184
+ catch (err) {
185
+ const msg = err instanceof Error ? err.message : String(err);
186
+ writeAudit({
187
+ t: new Date().toISOString(),
188
+ kind: 'rule-fire',
189
+ deviceId,
190
+ command: parsed.verb,
191
+ parameter: parameter ?? 'default',
192
+ commandType: 'command',
193
+ dryRun: false,
194
+ result: 'error',
195
+ error: msg,
196
+ rule: {
197
+ name: ctx.rule.name,
198
+ triggerSource: ctx.rule.when.source,
199
+ matchedDevice: deviceId,
200
+ fireId: ctx.fireId,
201
+ },
202
+ });
203
+ return { ok: false, error: msg, deviceId, verb: parsed.verb };
204
+ }
205
+ }
206
+ /**
207
+ * Extract the raw deviceId from an action object without alias resolution.
208
+ * Prefers `action.device` over the deviceId embedded in the command string.
209
+ * Use resolveActionDevice() when alias resolution is needed.
210
+ */
211
+ export function extractDeviceIdFromAction(action) {
212
+ if (action.device)
213
+ return action.device;
214
+ const m = /\bdevices\s+command\s+(\S+)/.exec(action.command ?? '');
215
+ return m ? m[1] : null;
216
+ }
217
+ //# sourceMappingURL=action.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action.js","sourceRoot":"","sources":["../../src/rules/action.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared filters + aggregations over the audit log for
3
+ * `switchbot rules tail` and `switchbot rules replay`.
4
+ *
5
+ * All functions are pure — no I/O, no clock reads — so they can be
6
+ * unit-tested with fixture arrays. The CLI entry points handle file
7
+ * reading, `--follow` tailing, and human vs JSON rendering.
8
+ */
9
+ import type { AuditEntry, AuditEntryKind } from '../utils/audit.js';
10
+ /** The subset of audit kinds the rules engine emits. */
11
+ export declare const RULE_AUDIT_KINDS: readonly AuditEntryKind[];
12
+ export interface RuleAuditFilter {
13
+ /** Filter entries with `t >= sinceMs`. Unbounded when undefined. */
14
+ sinceMs?: number;
15
+ /** Filter to a single rule name (matched against entry.rule?.name). */
16
+ ruleName?: string;
17
+ /** Only these kinds are returned. Defaults to RULE_AUDIT_KINDS. */
18
+ kinds?: readonly AuditEntryKind[];
19
+ }
20
+ /** Keep entries that are rule-engine emitted and match the filter. */
21
+ export declare function filterRuleAudits(entries: readonly AuditEntry[], filter?: RuleAuditFilter): AuditEntry[];
22
+ export interface RuleSummary {
23
+ /** Rule name as recorded in the audit entry. */
24
+ rule: string;
25
+ /** Number of real (non-dry) fires. */
26
+ fires: number;
27
+ /** Number of dry fires. */
28
+ driesFires: number;
29
+ /** Number of throttled skips. */
30
+ throttled: number;
31
+ /** Number of entries whose `result === 'error'`. */
32
+ errors: number;
33
+ /** fires where result === 'ok' divided by fires + driesFires + errors fired. */
34
+ errorRate: number;
35
+ /** Earliest timestamp observed for this rule (ISO). */
36
+ firstAt: string | null;
37
+ /** Latest timestamp observed for this rule (ISO). */
38
+ lastAt: string | null;
39
+ /** Trigger source observed — 'mixed' if the same rule name spans sources. */
40
+ triggerSource: 'mqtt' | 'cron' | 'webhook' | 'mixed' | null;
41
+ }
42
+ export interface ReplayReport {
43
+ /** Total entries (after filter) considered. */
44
+ total: number;
45
+ /** Per-rule summaries, sorted by `fires + driesFires` descending. */
46
+ summaries: RuleSummary[];
47
+ /** Count of rule-webhook-rejected entries with no rule name. */
48
+ webhookRejectedCount: number;
49
+ }
50
+ /** Aggregate a filtered stream into per-rule counters. */
51
+ export declare function aggregateRuleAudits(entries: readonly AuditEntry[]): ReplayReport;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Shared filters + aggregations over the audit log for
3
+ * `switchbot rules tail` and `switchbot rules replay`.
4
+ *
5
+ * All functions are pure — no I/O, no clock reads — so they can be
6
+ * unit-tested with fixture arrays. The CLI entry points handle file
7
+ * reading, `--follow` tailing, and human vs JSON rendering.
8
+ */
9
+ /** The subset of audit kinds the rules engine emits. */
10
+ export const RULE_AUDIT_KINDS = [
11
+ 'rule-fire',
12
+ 'rule-fire-dry',
13
+ 'rule-throttled',
14
+ 'rule-webhook-rejected',
15
+ ];
16
+ /** Keep entries that are rule-engine emitted and match the filter. */
17
+ export function filterRuleAudits(entries, filter = {}) {
18
+ const kinds = new Set(filter.kinds ?? RULE_AUDIT_KINDS);
19
+ const out = [];
20
+ for (const e of entries) {
21
+ if (!kinds.has(e.kind))
22
+ continue;
23
+ if (filter.sinceMs !== undefined) {
24
+ const ms = Date.parse(e.t);
25
+ if (!Number.isFinite(ms) || ms < filter.sinceMs)
26
+ continue;
27
+ }
28
+ if (filter.ruleName !== undefined) {
29
+ if (e.rule?.name !== filter.ruleName)
30
+ continue;
31
+ }
32
+ out.push(e);
33
+ }
34
+ return out;
35
+ }
36
+ /** Aggregate a filtered stream into per-rule counters. */
37
+ export function aggregateRuleAudits(entries) {
38
+ const byRule = new Map();
39
+ let webhookRejectedCount = 0;
40
+ for (const e of entries) {
41
+ if (e.kind === 'rule-webhook-rejected' && !e.rule) {
42
+ webhookRejectedCount++;
43
+ continue;
44
+ }
45
+ const name = e.rule?.name;
46
+ if (!name)
47
+ continue;
48
+ let s = byRule.get(name);
49
+ if (!s) {
50
+ s = {
51
+ rule: name,
52
+ fires: 0,
53
+ driesFires: 0,
54
+ throttled: 0,
55
+ errors: 0,
56
+ errorRate: 0,
57
+ firstAt: null,
58
+ lastAt: null,
59
+ triggerSource: null,
60
+ };
61
+ byRule.set(name, s);
62
+ }
63
+ if (e.kind === 'rule-fire')
64
+ s.fires++;
65
+ else if (e.kind === 'rule-fire-dry')
66
+ s.driesFires++;
67
+ else if (e.kind === 'rule-throttled')
68
+ s.throttled++;
69
+ if (e.result === 'error')
70
+ s.errors++;
71
+ if (!s.firstAt || e.t < s.firstAt)
72
+ s.firstAt = e.t;
73
+ if (!s.lastAt || e.t > s.lastAt)
74
+ s.lastAt = e.t;
75
+ const source = e.rule?.triggerSource;
76
+ if (source) {
77
+ if (s.triggerSource === null)
78
+ s.triggerSource = source;
79
+ else if (s.triggerSource !== source)
80
+ s.triggerSource = 'mixed';
81
+ }
82
+ }
83
+ for (const s of byRule.values()) {
84
+ const denom = s.fires + s.driesFires;
85
+ s.errorRate = denom === 0 ? 0 : s.errors / denom;
86
+ }
87
+ const summaries = [...byRule.values()].sort((a, b) => b.fires + b.driesFires - (a.fires + a.driesFires));
88
+ return { total: entries.length, summaries, webhookRejectedCount };
89
+ }
90
+ //# sourceMappingURL=audit-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-query.js","sourceRoot":"","sources":["../../src/rules/audit-query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Static conflict analysis for automation rules.
3
+ *
4
+ * Detects patterns that are technically valid but likely to cause
5
+ * operational problems:
6
+ *
7
+ * 1. Opposing-action pairs — same device, opposite commands (e.g.
8
+ * turnOn / turnOff), triggered by the same source within a short
9
+ * window and with no throttle on either rule.
10
+ *
11
+ * 2. High-frequency MQTT rules without throttle — rules that listen
12
+ * on `device.shadow` (catch-all) with no throttle can fire on
13
+ * every shadow push (up to once per second) and exhaust the daily
14
+ * API quota quickly.
15
+ *
16
+ * 3. Potentially-destructive action without quiet-hours protection —
17
+ * a rule that targets a destructive verb is technically blocked by
18
+ * the engine, but we can still flag it early so users don't get a
19
+ * surprise at runtime.
20
+ *
21
+ * Results are designed to be consumed by `rules doctor --json` and
22
+ * by CI pipelines. Each finding carries a `severity` so callers can
23
+ * decide how to gate on them.
24
+ */
25
+ import type { Rule } from './types.js';
26
+ export type ConflictSeverity = 'error' | 'warning' | 'info';
27
+ export interface ConflictFinding {
28
+ severity: ConflictSeverity;
29
+ code: string;
30
+ message: string;
31
+ /** Rule names involved in this finding. */
32
+ rules: string[];
33
+ hint?: string;
34
+ }
35
+ export interface ConflictReport {
36
+ findings: ConflictFinding[];
37
+ /** Count per severity level. */
38
+ counts: Record<ConflictSeverity, number>;
39
+ /** True when there are no error-severity findings. */
40
+ clean: boolean;
41
+ }
42
+ /**
43
+ * MQTT events that fire on every device state push and can rapidly exhaust
44
+ * the daily API quota when a rule has no throttle.
45
+ *
46
+ * Conditional events like `motion.detected` are intentionally excluded —
47
+ * they fire discretely, not at continuous high frequency. Extend this set
48
+ * when new catch-all or near-continuous event types are added to the classifier.
49
+ */
50
+ export declare const HIGH_FREQ_EVENTS: readonly string[];
51
+ /** Returns true when an MQTT event is known to fire at high frequency. */
52
+ export declare function isHighFreqEvent(event: string): boolean;
53
+ export interface QuietHours {
54
+ start: string;
55
+ end: string;
56
+ }
57
+ export declare function analyzeConflicts(rules: Rule[], quietHours?: QuietHours | null): ConflictReport;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Static conflict analysis for automation rules.
3
+ *
4
+ * Detects patterns that are technically valid but likely to cause
5
+ * operational problems:
6
+ *
7
+ * 1. Opposing-action pairs — same device, opposite commands (e.g.
8
+ * turnOn / turnOff), triggered by the same source within a short
9
+ * window and with no throttle on either rule.
10
+ *
11
+ * 2. High-frequency MQTT rules without throttle — rules that listen
12
+ * on `device.shadow` (catch-all) with no throttle can fire on
13
+ * every shadow push (up to once per second) and exhaust the daily
14
+ * API quota quickly.
15
+ *
16
+ * 3. Potentially-destructive action without quiet-hours protection —
17
+ * a rule that targets a destructive verb is technically blocked by
18
+ * the engine, but we can still flag it early so users don't get a
19
+ * surprise at runtime.
20
+ *
21
+ * Results are designed to be consumed by `rules doctor --json` and
22
+ * by CI pipelines. Each finding carries a `severity` so callers can
23
+ * decide how to gate on them.
24
+ */
25
+ import { isTimeBetween, isAllCondition, isAnyCondition, isNotCondition } from './types.js';
26
+ import { parseMaxPerMs } from './throttle.js';
27
+ import { isDestructiveCommand } from './destructive.js';
28
+ import { extractDeviceIdFromAction } from './action.js';
29
+ /** Known opposing command pairs (order-independent). */
30
+ const OPPOSING_PAIRS = [
31
+ ['turnOn', 'turnOff'],
32
+ ['lock', 'unlock'],
33
+ ['open', 'close'],
34
+ ['openDoor', 'closeDoor'],
35
+ ['openCurtain', 'closeCurtain'],
36
+ ['turnOn', 'standby'],
37
+ ['brightnessUp', 'brightnessDown'],
38
+ ['volumeUp', 'volumeDown'],
39
+ ['fanSpeedUp', 'fanSpeedDown'],
40
+ ];
41
+ /**
42
+ * MQTT events that fire on every device state push and can rapidly exhaust
43
+ * the daily API quota when a rule has no throttle.
44
+ *
45
+ * Conditional events like `motion.detected` are intentionally excluded —
46
+ * they fire discretely, not at continuous high frequency. Extend this set
47
+ * when new catch-all or near-continuous event types are added to the classifier.
48
+ */
49
+ export const HIGH_FREQ_EVENTS = ['device.shadow', '*'];
50
+ /** Returns true when an MQTT event is known to fire at high frequency. */
51
+ export function isHighFreqEvent(event) {
52
+ return HIGH_FREQ_EVENTS.includes(event);
53
+ }
54
+ function commandsAreOpposing(a, b) {
55
+ for (const [x, y] of OPPOSING_PAIRS) {
56
+ if ((a === x && b === y) || (a === y && b === x))
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ function extractCommandVerb(command) {
62
+ // command strings are like "devices command <id> turnOn" — extract last token
63
+ const parts = command.trim().split(/\s+/);
64
+ return parts[parts.length - 1] ?? command;
65
+ }
66
+ function effectiveCooldownMs(rule) {
67
+ if (rule.cooldown) {
68
+ try {
69
+ return parseMaxPerMs(rule.cooldown);
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ if (rule.throttle?.max_per) {
76
+ try {
77
+ return parseMaxPerMs(rule.throttle.max_per);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function hasTimeBetweenGuard(conditions) {
86
+ if (!conditions)
87
+ return false;
88
+ for (const c of conditions) {
89
+ if (isTimeBetween(c))
90
+ return true;
91
+ if (isAllCondition(c) && hasTimeBetweenGuard(c.all))
92
+ return true;
93
+ if (isAnyCondition(c) && hasTimeBetweenGuard(c.any))
94
+ return true;
95
+ if (isNotCondition(c) && hasTimeBetweenGuard([c.not]))
96
+ return true;
97
+ }
98
+ return false;
99
+ }
100
+ export function analyzeConflicts(rules, quietHours) {
101
+ const findings = [];
102
+ const active = rules.filter((r) => r.enabled !== false);
103
+ // 1. Opposing-action pairs on the same device
104
+ for (let i = 0; i < active.length; i++) {
105
+ for (let j = i + 1; j < active.length; j++) {
106
+ const a = active[i];
107
+ const b = active[j];
108
+ // Only flag when they share the same trigger source (otherwise they
109
+ // can't race each other in normal operation).
110
+ if (a.when.source !== b.when.source)
111
+ continue;
112
+ const cooldownA = effectiveCooldownMs(a);
113
+ const cooldownB = effectiveCooldownMs(b);
114
+ // If both rules have meaningful cooldowns (≥ 5 minutes), the risk is
115
+ // low — skip.
116
+ const bothThrottled = cooldownA !== null && cooldownA >= 5 * 60_000 &&
117
+ cooldownB !== null && cooldownB >= 5 * 60_000;
118
+ if (bothThrottled)
119
+ continue;
120
+ for (const actionA of a.then) {
121
+ for (const actionB of b.then) {
122
+ const deviceA = extractDeviceIdFromAction(actionA);
123
+ const deviceB = extractDeviceIdFromAction(actionB);
124
+ // Skip if devices can't be compared.
125
+ if (!deviceA || !deviceB || deviceA !== deviceB)
126
+ continue;
127
+ const verbA = extractCommandVerb(actionA.command);
128
+ const verbB = extractCommandVerb(actionB.command);
129
+ if (commandsAreOpposing(verbA, verbB)) {
130
+ const noThrottle = cooldownA === null || cooldownB === null;
131
+ findings.push({
132
+ severity: noThrottle ? 'warning' : 'info',
133
+ code: 'opposing-actions',
134
+ message: `Rules "${a.name}" and "${b.name}" issue opposing commands (${verbA} / ${verbB}) on device "${deviceA}" via the same trigger source.`,
135
+ rules: [a.name, b.name],
136
+ hint: noThrottle
137
+ ? 'Add a "cooldown" or "throttle.max_per" to both rules to prevent rapid state oscillation.'
138
+ : undefined,
139
+ });
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ // 2. High-frequency MQTT catch-all rules without throttle
146
+ for (const rule of active) {
147
+ if (rule.when.source !== 'mqtt')
148
+ continue;
149
+ const event = rule.when.event;
150
+ const isHighFreq = isHighFreqEvent(event);
151
+ if (!isHighFreq)
152
+ continue;
153
+ const cooldown = effectiveCooldownMs(rule);
154
+ if (cooldown === null) {
155
+ findings.push({
156
+ severity: 'warning',
157
+ code: 'high-frequency-no-throttle',
158
+ message: `Rule "${rule.name}" listens on "${event}" (high-frequency catch-all) with no throttle/cooldown. This can rapidly exhaust the daily API quota.`,
159
+ rules: [rule.name],
160
+ hint: 'Add "cooldown: 1m" or "throttle: { max_per: 1m }" to rate-limit this rule.',
161
+ });
162
+ }
163
+ else if (cooldown < 30_000) {
164
+ findings.push({
165
+ severity: 'info',
166
+ code: 'high-frequency-low-throttle',
167
+ message: `Rule "${rule.name}" listens on "${event}" with a throttle under 30 s. Consider increasing to at least 1 m to protect API quota.`,
168
+ rules: [rule.name],
169
+ });
170
+ }
171
+ }
172
+ // 3. Destructive actions in rules (engine blocks these at runtime, but
173
+ // surface early with clear guidance).
174
+ for (const rule of active) {
175
+ for (let i = 0; i < rule.then.length; i++) {
176
+ const verb = extractCommandVerb(rule.then[i].command);
177
+ if (isDestructiveCommand(verb)) {
178
+ findings.push({
179
+ severity: 'error',
180
+ code: 'destructive-action-in-rule',
181
+ message: `Rule "${rule.name}" then[${i}] contains destructive command "${verb}". The engine blocks this at runtime.`,
182
+ rules: [rule.name],
183
+ hint: 'Remove the destructive command or replace it with a non-destructive alternative.',
184
+ });
185
+ }
186
+ }
187
+ }
188
+ // 4. Event-driven rules with no time_between guard when quiet_hours is defined.
189
+ // Cron rules fire on an explicit schedule so their overlap with quiet hours
190
+ // requires schedule analysis — flag those separately when needed.
191
+ if (quietHours?.start && quietHours?.end) {
192
+ for (const rule of active) {
193
+ if (rule.when.source === 'cron')
194
+ continue;
195
+ if (!hasTimeBetweenGuard(rule.conditions)) {
196
+ findings.push({
197
+ severity: 'warning',
198
+ code: 'no-quiet-hours-guard',
199
+ message: `Rule "${rule.name}" (${rule.when.source} trigger) has no time_between condition and may fire during quiet hours (${quietHours.start}–${quietHours.end}).`,
200
+ rules: [rule.name],
201
+ hint: `Add a conditions entry "{ time_between: ['${quietHours.end}', '${quietHours.start}'] }" to block this rule during quiet hours.`,
202
+ });
203
+ }
204
+ }
205
+ }
206
+ const counts = { error: 0, warning: 0, info: 0 };
207
+ for (const f of findings)
208
+ counts[f.severity]++;
209
+ return {
210
+ findings,
211
+ counts,
212
+ clean: counts.error === 0,
213
+ };
214
+ }
215
+ //# sourceMappingURL=conflict-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-analyzer.js","sourceRoot":"","sources":["../../src/rules/conflict-analyzer.ts"],"names":[],"mappings":"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"}