@switchbot/openapi-cli 3.2.1 → 3.2.2

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 (332) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +57349 -170
  3. package/package.json +9 -5
  4. package/dist/api/client.d.ts +0 -31
  5. package/dist/api/client.js +0 -236
  6. package/dist/api/client.js.map +0 -1
  7. package/dist/auth.d.ts +0 -1
  8. package/dist/auth.js +0 -21
  9. package/dist/auth.js.map +0 -1
  10. package/dist/commands/agent-bootstrap.d.ts +0 -10
  11. package/dist/commands/agent-bootstrap.js +0 -200
  12. package/dist/commands/agent-bootstrap.js.map +0 -1
  13. package/dist/commands/auth.d.ts +0 -18
  14. package/dist/commands/auth.js +0 -355
  15. package/dist/commands/auth.js.map +0 -1
  16. package/dist/commands/batch.d.ts +0 -2
  17. package/dist/commands/batch.js +0 -414
  18. package/dist/commands/batch.js.map +0 -1
  19. package/dist/commands/cache.d.ts +0 -2
  20. package/dist/commands/cache.js +0 -127
  21. package/dist/commands/cache.js.map +0 -1
  22. package/dist/commands/capabilities.d.ts +0 -31
  23. package/dist/commands/capabilities.js +0 -383
  24. package/dist/commands/capabilities.js.map +0 -1
  25. package/dist/commands/catalog.d.ts +0 -2
  26. package/dist/commands/catalog.js +0 -360
  27. package/dist/commands/catalog.js.map +0 -1
  28. package/dist/commands/completion.d.ts +0 -2
  29. package/dist/commands/completion.js +0 -386
  30. package/dist/commands/completion.js.map +0 -1
  31. package/dist/commands/config.d.ts +0 -21
  32. package/dist/commands/config.js +0 -377
  33. package/dist/commands/config.js.map +0 -1
  34. package/dist/commands/daemon.d.ts +0 -2
  35. package/dist/commands/daemon.js +0 -411
  36. package/dist/commands/daemon.js.map +0 -1
  37. package/dist/commands/device-meta.d.ts +0 -2
  38. package/dist/commands/device-meta.js +0 -160
  39. package/dist/commands/device-meta.js.map +0 -1
  40. package/dist/commands/devices.d.ts +0 -2
  41. package/dist/commands/devices.js +0 -949
  42. package/dist/commands/devices.js.map +0 -1
  43. package/dist/commands/doctor.d.ts +0 -3
  44. package/dist/commands/doctor.js +0 -1016
  45. package/dist/commands/doctor.js.map +0 -1
  46. package/dist/commands/events.d.ts +0 -31
  47. package/dist/commands/events.js +0 -564
  48. package/dist/commands/events.js.map +0 -1
  49. package/dist/commands/expand.d.ts +0 -2
  50. package/dist/commands/expand.js +0 -131
  51. package/dist/commands/expand.js.map +0 -1
  52. package/dist/commands/explain.d.ts +0 -2
  53. package/dist/commands/explain.js +0 -140
  54. package/dist/commands/explain.js.map +0 -1
  55. package/dist/commands/health.d.ts +0 -8
  56. package/dist/commands/health.js +0 -114
  57. package/dist/commands/health.js.map +0 -1
  58. package/dist/commands/history.d.ts +0 -2
  59. package/dist/commands/history.js +0 -321
  60. package/dist/commands/history.js.map +0 -1
  61. package/dist/commands/identity.d.ts +0 -45
  62. package/dist/commands/identity.js +0 -60
  63. package/dist/commands/identity.js.map +0 -1
  64. package/dist/commands/install.d.ts +0 -20
  65. package/dist/commands/install.js +0 -247
  66. package/dist/commands/install.js.map +0 -1
  67. package/dist/commands/mcp.d.ts +0 -14
  68. package/dist/commands/mcp.js +0 -2018
  69. package/dist/commands/mcp.js.map +0 -1
  70. package/dist/commands/plan.d.ts +0 -51
  71. package/dist/commands/plan.js +0 -654
  72. package/dist/commands/plan.js.map +0 -1
  73. package/dist/commands/policy.d.ts +0 -24
  74. package/dist/commands/policy.js +0 -587
  75. package/dist/commands/policy.js.map +0 -1
  76. package/dist/commands/quota.d.ts +0 -2
  77. package/dist/commands/quota.js +0 -79
  78. package/dist/commands/quota.js.map +0 -1
  79. package/dist/commands/rules.d.ts +0 -2
  80. package/dist/commands/rules.js +0 -876
  81. package/dist/commands/rules.js.map +0 -1
  82. package/dist/commands/scenes.d.ts +0 -2
  83. package/dist/commands/scenes.js +0 -265
  84. package/dist/commands/scenes.js.map +0 -1
  85. package/dist/commands/schema.d.ts +0 -2
  86. package/dist/commands/schema.js +0 -185
  87. package/dist/commands/schema.js.map +0 -1
  88. package/dist/commands/status-sync.d.ts +0 -2
  89. package/dist/commands/status-sync.js +0 -132
  90. package/dist/commands/status-sync.js.map +0 -1
  91. package/dist/commands/uninstall.d.ts +0 -20
  92. package/dist/commands/uninstall.js +0 -238
  93. package/dist/commands/uninstall.js.map +0 -1
  94. package/dist/commands/upgrade-check.d.ts +0 -2
  95. package/dist/commands/upgrade-check.js +0 -107
  96. package/dist/commands/upgrade-check.js.map +0 -1
  97. package/dist/commands/watch.d.ts +0 -2
  98. package/dist/commands/watch.js +0 -195
  99. package/dist/commands/watch.js.map +0 -1
  100. package/dist/commands/webhook.d.ts +0 -2
  101. package/dist/commands/webhook.js +0 -183
  102. package/dist/commands/webhook.js.map +0 -1
  103. package/dist/config.d.ts +0 -57
  104. package/dist/config.js +0 -259
  105. package/dist/config.js.map +0 -1
  106. package/dist/credentials/backends/file.d.ts +0 -18
  107. package/dist/credentials/backends/file.js +0 -102
  108. package/dist/credentials/backends/file.js.map +0 -1
  109. package/dist/credentials/backends/linux.d.ts +0 -16
  110. package/dist/credentials/backends/linux.js +0 -130
  111. package/dist/credentials/backends/linux.js.map +0 -1
  112. package/dist/credentials/backends/macos.d.ts +0 -18
  113. package/dist/credentials/backends/macos.js +0 -130
  114. package/dist/credentials/backends/macos.js.map +0 -1
  115. package/dist/credentials/backends/windows.d.ts +0 -23
  116. package/dist/credentials/backends/windows.js +0 -216
  117. package/dist/credentials/backends/windows.js.map +0 -1
  118. package/dist/credentials/keychain.d.ts +0 -83
  119. package/dist/credentials/keychain.js +0 -89
  120. package/dist/credentials/keychain.js.map +0 -1
  121. package/dist/credentials/prime.d.ts +0 -32
  122. package/dist/credentials/prime.js +0 -53
  123. package/dist/credentials/prime.js.map +0 -1
  124. package/dist/devices/cache.d.ts +0 -79
  125. package/dist/devices/cache.js +0 -294
  126. package/dist/devices/cache.js.map +0 -1
  127. package/dist/devices/catalog.d.ts +0 -138
  128. package/dist/devices/catalog.js +0 -768
  129. package/dist/devices/catalog.js.map +0 -1
  130. package/dist/devices/device-meta.d.ts +0 -15
  131. package/dist/devices/device-meta.js +0 -57
  132. package/dist/devices/device-meta.js.map +0 -1
  133. package/dist/devices/history-agg.d.ts +0 -37
  134. package/dist/devices/history-agg.js +0 -139
  135. package/dist/devices/history-agg.js.map +0 -1
  136. package/dist/devices/history-query.d.ts +0 -45
  137. package/dist/devices/history-query.js +0 -182
  138. package/dist/devices/history-query.js.map +0 -1
  139. package/dist/devices/param-validator.d.ts +0 -40
  140. package/dist/devices/param-validator.js +0 -434
  141. package/dist/devices/param-validator.js.map +0 -1
  142. package/dist/devices/resources.d.ts +0 -74
  143. package/dist/devices/resources.js +0 -271
  144. package/dist/devices/resources.js.map +0 -1
  145. package/dist/index.d.ts +0 -1
  146. package/dist/index.js.map +0 -1
  147. package/dist/install/default-steps.d.ts +0 -66
  148. package/dist/install/default-steps.js +0 -258
  149. package/dist/install/default-steps.js.map +0 -1
  150. package/dist/install/preflight.d.ts +0 -60
  151. package/dist/install/preflight.js +0 -213
  152. package/dist/install/preflight.js.map +0 -1
  153. package/dist/install/steps.d.ts +0 -61
  154. package/dist/install/steps.js +0 -68
  155. package/dist/install/steps.js.map +0 -1
  156. package/dist/lib/command-keywords.d.ts +0 -5
  157. package/dist/lib/command-keywords.js +0 -18
  158. package/dist/lib/command-keywords.js.map +0 -1
  159. package/dist/lib/daemon-state.d.ts +0 -24
  160. package/dist/lib/daemon-state.js +0 -47
  161. package/dist/lib/daemon-state.js.map +0 -1
  162. package/dist/lib/destructive-mode.d.ts +0 -2
  163. package/dist/lib/destructive-mode.js +0 -13
  164. package/dist/lib/destructive-mode.js.map +0 -1
  165. package/dist/lib/devices.d.ts +0 -151
  166. package/dist/lib/devices.js +0 -383
  167. package/dist/lib/devices.js.map +0 -1
  168. package/dist/lib/idempotency.d.ts +0 -46
  169. package/dist/lib/idempotency.js +0 -107
  170. package/dist/lib/idempotency.js.map +0 -1
  171. package/dist/lib/plan-store.d.ts +0 -19
  172. package/dist/lib/plan-store.js +0 -69
  173. package/dist/lib/plan-store.js.map +0 -1
  174. package/dist/lib/request-context.d.ts +0 -7
  175. package/dist/lib/request-context.js +0 -13
  176. package/dist/lib/request-context.js.map +0 -1
  177. package/dist/lib/scenes.d.ts +0 -7
  178. package/dist/lib/scenes.js +0 -11
  179. package/dist/lib/scenes.js.map +0 -1
  180. package/dist/logger.d.ts +0 -4
  181. package/dist/logger.js +0 -17
  182. package/dist/logger.js.map +0 -1
  183. package/dist/mcp/device-history.d.ts +0 -36
  184. package/dist/mcp/device-history.js +0 -146
  185. package/dist/mcp/device-history.js.map +0 -1
  186. package/dist/mcp/events-subscription.d.ts +0 -45
  187. package/dist/mcp/events-subscription.js +0 -214
  188. package/dist/mcp/events-subscription.js.map +0 -1
  189. package/dist/mqtt/client.d.ts +0 -25
  190. package/dist/mqtt/client.js +0 -181
  191. package/dist/mqtt/client.js.map +0 -1
  192. package/dist/mqtt/credential.d.ts +0 -16
  193. package/dist/mqtt/credential.js +0 -31
  194. package/dist/mqtt/credential.js.map +0 -1
  195. package/dist/policy/add-rule.d.ts +0 -21
  196. package/dist/policy/add-rule.js +0 -125
  197. package/dist/policy/add-rule.js.map +0 -1
  198. package/dist/policy/diff.d.ts +0 -21
  199. package/dist/policy/diff.js +0 -92
  200. package/dist/policy/diff.js.map +0 -1
  201. package/dist/policy/format.d.ts +0 -6
  202. package/dist/policy/format.js +0 -58
  203. package/dist/policy/format.js.map +0 -1
  204. package/dist/policy/load.d.ts +0 -32
  205. package/dist/policy/load.js +0 -62
  206. package/dist/policy/load.js.map +0 -1
  207. package/dist/policy/migrate.d.ts +0 -21
  208. package/dist/policy/migrate.js +0 -68
  209. package/dist/policy/migrate.js.map +0 -1
  210. package/dist/policy/schema.d.ts +0 -5
  211. package/dist/policy/schema.js +0 -19
  212. package/dist/policy/schema.js.map +0 -1
  213. package/dist/policy/validate.d.ts +0 -19
  214. package/dist/policy/validate.js +0 -263
  215. package/dist/policy/validate.js.map +0 -1
  216. package/dist/rules/action.d.ts +0 -65
  217. package/dist/rules/action.js +0 -217
  218. package/dist/rules/action.js.map +0 -1
  219. package/dist/rules/audit-query.d.ts +0 -51
  220. package/dist/rules/audit-query.js +0 -90
  221. package/dist/rules/audit-query.js.map +0 -1
  222. package/dist/rules/conflict-analyzer.d.ts +0 -57
  223. package/dist/rules/conflict-analyzer.js +0 -215
  224. package/dist/rules/conflict-analyzer.js.map +0 -1
  225. package/dist/rules/cron-scheduler.d.ts +0 -62
  226. package/dist/rules/cron-scheduler.js +0 -187
  227. package/dist/rules/cron-scheduler.js.map +0 -1
  228. package/dist/rules/destructive.d.ts +0 -20
  229. package/dist/rules/destructive.js +0 -53
  230. package/dist/rules/destructive.js.map +0 -1
  231. package/dist/rules/engine.d.ts +0 -193
  232. package/dist/rules/engine.js +0 -758
  233. package/dist/rules/engine.js.map +0 -1
  234. package/dist/rules/matcher.d.ts +0 -56
  235. package/dist/rules/matcher.js +0 -231
  236. package/dist/rules/matcher.js.map +0 -1
  237. package/dist/rules/pid-file.d.ts +0 -43
  238. package/dist/rules/pid-file.js +0 -96
  239. package/dist/rules/pid-file.js.map +0 -1
  240. package/dist/rules/quiet-hours.d.ts +0 -26
  241. package/dist/rules/quiet-hours.js +0 -46
  242. package/dist/rules/quiet-hours.js.map +0 -1
  243. package/dist/rules/suggest.d.ts +0 -20
  244. package/dist/rules/suggest.js +0 -96
  245. package/dist/rules/suggest.js.map +0 -1
  246. package/dist/rules/throttle.d.ts +0 -61
  247. package/dist/rules/throttle.js +0 -117
  248. package/dist/rules/throttle.js.map +0 -1
  249. package/dist/rules/types.d.ts +0 -117
  250. package/dist/rules/types.js +0 -35
  251. package/dist/rules/types.js.map +0 -1
  252. package/dist/rules/webhook-listener.d.ts +0 -63
  253. package/dist/rules/webhook-listener.js +0 -224
  254. package/dist/rules/webhook-listener.js.map +0 -1
  255. package/dist/rules/webhook-token.d.ts +0 -50
  256. package/dist/rules/webhook-token.js +0 -91
  257. package/dist/rules/webhook-token.js.map +0 -1
  258. package/dist/schema/field-aliases.d.ts +0 -34
  259. package/dist/schema/field-aliases.js +0 -132
  260. package/dist/schema/field-aliases.js.map +0 -1
  261. package/dist/sinks/dispatcher.d.ts +0 -7
  262. package/dist/sinks/dispatcher.js +0 -13
  263. package/dist/sinks/dispatcher.js.map +0 -1
  264. package/dist/sinks/file.d.ts +0 -6
  265. package/dist/sinks/file.js +0 -20
  266. package/dist/sinks/file.js.map +0 -1
  267. package/dist/sinks/format.d.ts +0 -20
  268. package/dist/sinks/format.js +0 -57
  269. package/dist/sinks/format.js.map +0 -1
  270. package/dist/sinks/homeassistant.d.ts +0 -18
  271. package/dist/sinks/homeassistant.js +0 -45
  272. package/dist/sinks/homeassistant.js.map +0 -1
  273. package/dist/sinks/openclaw.d.ts +0 -13
  274. package/dist/sinks/openclaw.js +0 -34
  275. package/dist/sinks/openclaw.js.map +0 -1
  276. package/dist/sinks/stdout.d.ts +0 -4
  277. package/dist/sinks/stdout.js +0 -6
  278. package/dist/sinks/stdout.js.map +0 -1
  279. package/dist/sinks/telegram.d.ts +0 -11
  280. package/dist/sinks/telegram.js +0 -29
  281. package/dist/sinks/telegram.js.map +0 -1
  282. package/dist/sinks/types.d.ts +0 -13
  283. package/dist/sinks/types.js +0 -2
  284. package/dist/sinks/types.js.map +0 -1
  285. package/dist/sinks/webhook.d.ts +0 -6
  286. package/dist/sinks/webhook.js +0 -23
  287. package/dist/sinks/webhook.js.map +0 -1
  288. package/dist/status-sync/manager.d.ts +0 -48
  289. package/dist/status-sync/manager.js +0 -269
  290. package/dist/status-sync/manager.js.map +0 -1
  291. package/dist/utils/arg-parsers.d.ts +0 -16
  292. package/dist/utils/arg-parsers.js +0 -67
  293. package/dist/utils/arg-parsers.js.map +0 -1
  294. package/dist/utils/audit.d.ts +0 -69
  295. package/dist/utils/audit.js +0 -122
  296. package/dist/utils/audit.js.map +0 -1
  297. package/dist/utils/filter.d.ts +0 -81
  298. package/dist/utils/filter.js +0 -190
  299. package/dist/utils/filter.js.map +0 -1
  300. package/dist/utils/flags.d.ts +0 -72
  301. package/dist/utils/flags.js +0 -187
  302. package/dist/utils/flags.js.map +0 -1
  303. package/dist/utils/format.d.ts +0 -9
  304. package/dist/utils/format.js +0 -118
  305. package/dist/utils/format.js.map +0 -1
  306. package/dist/utils/health.d.ts +0 -48
  307. package/dist/utils/health.js +0 -102
  308. package/dist/utils/health.js.map +0 -1
  309. package/dist/utils/help-json.d.ts +0 -39
  310. package/dist/utils/help-json.js +0 -55
  311. package/dist/utils/help-json.js.map +0 -1
  312. package/dist/utils/name-resolver.d.ts +0 -26
  313. package/dist/utils/name-resolver.js +0 -138
  314. package/dist/utils/name-resolver.js.map +0 -1
  315. package/dist/utils/output.d.ts +0 -73
  316. package/dist/utils/output.js +0 -405
  317. package/dist/utils/output.js.map +0 -1
  318. package/dist/utils/quota.d.ts +0 -61
  319. package/dist/utils/quota.js +0 -228
  320. package/dist/utils/quota.js.map +0 -1
  321. package/dist/utils/redact.d.ts +0 -23
  322. package/dist/utils/redact.js +0 -69
  323. package/dist/utils/redact.js.map +0 -1
  324. package/dist/utils/retry.d.ts +0 -72
  325. package/dist/utils/retry.js +0 -141
  326. package/dist/utils/retry.js.map +0 -1
  327. package/dist/utils/string.d.ts +0 -2
  328. package/dist/utils/string.js +0 -23
  329. package/dist/utils/string.js.map +0 -1
  330. package/dist/version.d.ts +0 -2
  331. package/dist/version.js +0 -5
  332. package/dist/version.js.map +0 -1
@@ -1,768 +0,0 @@
1
- /**
2
- * Static catalog of SwitchBot device types, control commands and status fields.
3
- * Sourced from https://github.com/OpenWonderLabs/SwitchBotAPI — keep in sync
4
- * when the upstream API adds new device types.
5
- *
6
- * Field conventions:
7
- * - CommandSpec.idempotent: repeat-safe — calling it N times ends in the
8
- * same state as calling it once (turnOn, setBrightness 50). Agents can
9
- * retry these freely. Counter-examples: toggle, press, volumeAdd.
10
- * - CommandSpec.safetyTier: explicit action safety classification. See
11
- * SafetyTier for the 5-tier enum. Built-in entries set this on the
12
- * destructive tier; other tiers are derived (see deriveSafetyTier).
13
- * - DeviceCatalogEntry.role: functional grouping for filter/search
14
- * ("all lighting", "all security"). Does not affect API behavior.
15
- * - DeviceCatalogEntry.readOnly: the device has no control commands; it
16
- * can only be queried via 'devices status'.
17
- */
18
- /**
19
- * Catalog shape version. Bump when any of the exported interfaces
20
- * (CommandSpec / DeviceCatalogEntry / SafetyTier) gain/lose/rename a
21
- * load-bearing field. The agent-bootstrap payload's schemaVersion must
22
- * stay pinned to this value; `doctor` fails the `catalog-schema` check
23
- * when they drift.
24
- */
25
- export const CATALOG_SCHEMA_VERSION = '1.0';
26
- /**
27
- * Human-readable descriptions for common status fields. Populated from
28
- * the SwitchBot API v1.1 docs. Used by deriveStatusQueries() so every
29
- * query has a meaningful description even when the entry itself only
30
- * declares the field name.
31
- */
32
- const STATUS_FIELD_DESCRIPTIONS = {
33
- power: 'Power state (on/off)',
34
- battery: 'Battery percentage (0-100)',
35
- version: 'Firmware version string',
36
- temperature: 'Ambient temperature (°C)',
37
- humidity: 'Ambient humidity (% RH)',
38
- CO2: 'CO2 concentration (ppm)',
39
- brightness: 'Current brightness (0-100)',
40
- color: 'Current RGB color (r:g:b)',
41
- colorTemperature: 'Color temperature in Kelvin',
42
- mode: 'Operating mode',
43
- deviceMode: 'Hardware mode (Bot-specific)',
44
- lockState: 'Lock state (locked/unlocked)',
45
- doorState: 'Door contact state (open/closed)',
46
- calibrate: 'Calibration status',
47
- moving: 'Motion in progress (boolean)',
48
- slidePosition: 'Slide position (0-100)',
49
- group: 'Multi-device group membership',
50
- direction: 'Tilt direction',
51
- voltage: 'Line voltage',
52
- electricCurrent: 'Instantaneous current draw',
53
- electricityOfDay: 'kWh consumed today',
54
- usedElectricity: 'Cumulative kWh',
55
- useTime: 'Total runtime (seconds)',
56
- weight: 'Load / weight reading',
57
- switchStatus: 'Relay state (integer encoded)',
58
- switch1Status: 'Channel 1 relay state',
59
- switch2Status: 'Channel 2 relay state',
60
- workingStatus: 'Device working status (vacuum/purifier)',
61
- onlineStatus: 'Online / offline (string)',
62
- online: 'Online / offline (boolean or int)',
63
- taskType: 'Current task identifier',
64
- nightStatus: 'Night-mode status',
65
- oscillation: 'Horizontal oscillation on/off',
66
- verticalOscillation: 'Vertical oscillation on/off',
67
- chargingStatus: 'Charging (boolean)',
68
- fanSpeed: 'Current fan speed level',
69
- nebulizationEfficiency: 'Humidifier mist level',
70
- childLock: 'Child-lock engaged',
71
- sound: 'Beep / audio feedback enabled',
72
- lackWater: 'Water tank low (boolean)',
73
- filterElement: 'Filter life remaining',
74
- auto: 'Auto mode enabled',
75
- targetTemperature: 'Thermostat target temperature',
76
- moveDetected: 'Motion detected (boolean)',
77
- openState: 'Contact sensor open/closed',
78
- status: 'Device-specific status word',
79
- lightLevel: 'Ambient light level',
80
- };
81
- /**
82
- * P11: derive the read-only query list for an entry. If the entry has
83
- * explicit `statusQueries`, return them as-is; otherwise synthesize one
84
- * ReadOnlyQuerySpec per `statusFields` entry, all keyed to the `status`
85
- * endpoint. IR-category entries have no status channel so return [].
86
- */
87
- export function deriveStatusQueries(entry) {
88
- if (entry.statusQueries && entry.statusQueries.length > 0)
89
- return entry.statusQueries;
90
- if (entry.category === 'ir')
91
- return [];
92
- const fields = entry.statusFields ?? [];
93
- return fields.map((f) => ({
94
- field: f,
95
- description: STATUS_FIELD_DESCRIPTIONS[f] ?? `${f} (see API docs)`,
96
- endpoint: 'status',
97
- safetyTier: 'read',
98
- }));
99
- }
100
- // ---- Command fragments (reused across entries) -------------------------
101
- const onOff = [
102
- { command: 'turnOn', parameter: '—', description: 'Power on', idempotent: true },
103
- { command: 'turnOff', parameter: '—', description: 'Power off', idempotent: true },
104
- ];
105
- const onOffToggle = [
106
- ...onOff,
107
- { command: 'toggle', parameter: '—', description: 'Toggle power', idempotent: false },
108
- ];
109
- const lightControls = [
110
- { command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage', idempotent: true, exampleParams: ['50', '80'] },
111
- { command: 'setColor', parameter: 'R:G:B (0-255 each)', description: 'Set RGB color, e.g. "255:0:0"', idempotent: true, exampleParams: ['255:0:0', '255:255:255'] },
112
- { command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)', idempotent: true, exampleParams: ['2700', '4000', '6500'] },
113
- ];
114
- export const DEVICE_CATALOG = [
115
- // ---------- Physical devices ----------
116
- {
117
- type: 'Bot',
118
- category: 'physical',
119
- description: 'Mechanical arm robot that physically presses a button or toggles a switch on demand.',
120
- role: 'other',
121
- commands: [
122
- ...onOff,
123
- { command: 'press', parameter: '—', description: 'Press the button (momentary)', idempotent: false },
124
- ],
125
- statusFields: ['power', 'battery', 'deviceMode', 'version'],
126
- },
127
- {
128
- type: 'Curtain',
129
- category: 'physical',
130
- description: 'Motorized curtain track runner that opens/closes curtains by slide position (0=open, 100=closed).',
131
- role: 'curtain',
132
- aliases: ['Curtain3', 'Curtain 3'],
133
- commands: [
134
- ...onOff,
135
- { command: 'pause', parameter: '—', description: 'Stop movement', idempotent: true },
136
- { command: 'setPosition', parameter: '0-100 (0=open, 100=closed)', description: 'Move to a position', idempotent: true, exampleParams: ['0', '50', '100'] },
137
- { command: 'setPosition', parameter: 'index,mode,position (e.g. "0,ff,80")', description: 'Multi-arg form: mode=0 Performance | 1 Silent | ff default', idempotent: true, exampleParams: ['0,ff,50'] },
138
- ],
139
- statusFields: ['calibrate', 'group', 'moving', 'slidePosition', 'battery', 'version'],
140
- },
141
- {
142
- type: 'Smart Lock',
143
- category: 'physical',
144
- description: 'Bluetooth/Wi-Fi electronic deadbolt that locks and unlocks a door via cloud API.',
145
- role: 'security',
146
- aliases: ['Smart Lock Pro'],
147
- commands: [
148
- { command: 'lock', parameter: '—', description: 'Lock the door', idempotent: true },
149
- { command: 'unlock', parameter: '—', description: 'Unlock the door', idempotent: true, safetyTier: 'destructive', safetyReason: 'Physically unlocks the door — anyone nearby can open it.' },
150
- { command: 'deadbolt', parameter: '—', description: 'Pro only: engage deadbolt', idempotent: true },
151
- ],
152
- statusFields: ['battery', 'version', 'lockState', 'doorState', 'calibrate'],
153
- },
154
- {
155
- type: 'Smart Lock Lite',
156
- category: 'physical',
157
- description: 'Compact electronic deadbolt with lock and unlock control; no deadbolt mode.',
158
- role: 'security',
159
- commands: [
160
- { command: 'lock', parameter: '—', description: 'Lock the door', idempotent: true },
161
- { command: 'unlock', parameter: '—', description: 'Unlock the door', idempotent: true, safetyTier: 'destructive', safetyReason: 'Physically unlocks the door — anyone nearby can open it.' },
162
- ],
163
- statusFields: ['battery', 'version', 'lockState', 'doorState', 'calibrate'],
164
- },
165
- {
166
- type: 'Smart Lock Ultra',
167
- category: 'physical',
168
- description: 'Premium electronic deadbolt with full lock, unlock, and deadbolt control.',
169
- role: 'security',
170
- commands: [
171
- { command: 'lock', parameter: '—', description: 'Lock the door', idempotent: true },
172
- { command: 'unlock', parameter: '—', description: 'Unlock the door', idempotent: true, safetyTier: 'destructive', safetyReason: 'Physically unlocks the door — anyone nearby can open it.' },
173
- { command: 'deadbolt', parameter: '—', description: 'Engage deadbolt', idempotent: true },
174
- ],
175
- statusFields: ['battery', 'version', 'lockState', 'doorState', 'calibrate'],
176
- },
177
- {
178
- type: 'Plug',
179
- category: 'physical',
180
- description: 'Smart wall outlet plug with on/off/toggle control and basic power status.',
181
- role: 'power',
182
- commands: onOffToggle,
183
- statusFields: ['power', 'version'],
184
- },
185
- {
186
- type: 'Plug Mini (US)',
187
- category: 'physical',
188
- description: 'Compact smart plug with voltage, current, and daily energy consumption reporting.',
189
- role: 'power',
190
- aliases: ['Plug Mini (JP)'],
191
- commands: onOffToggle,
192
- statusFields: ['voltage', 'weight', 'electricityOfDay', 'electricCurrent', 'power', 'version'],
193
- },
194
- {
195
- type: 'Relay Switch 1',
196
- category: 'physical',
197
- description: 'In-wall relay switch with configurable modes (toggle/edge/detached/momentary) and power metering.',
198
- role: 'power',
199
- aliases: ['Relay Switch 1PM'],
200
- commands: [
201
- ...onOffToggle,
202
- { command: 'setMode', parameter: '0=toggle | 1=edge | 2=detached | 3=momentary', description: 'Switch operating mode', idempotent: true, exampleParams: ['0', '1', '2', '3'] },
203
- ],
204
- statusFields: ['switchStatus', 'voltage', 'version', 'useTime', 'electricCurrent', 'power', 'usedElectricity'],
205
- },
206
- {
207
- type: 'Relay Switch 2PM',
208
- category: 'physical',
209
- description: 'Dual-channel relay switch with per-channel on/off/toggle and optional roller-shade mode.',
210
- role: 'power',
211
- commands: [
212
- { command: 'turnOn', parameter: '1 | 2 (channel)', description: 'Turn on channel 1 or 2', idempotent: true, exampleParams: ['1', '2'] },
213
- { command: 'turnOff', parameter: '1 | 2 (channel)', description: 'Turn off channel 1 or 2', idempotent: true, exampleParams: ['1', '2'] },
214
- { command: 'toggle', parameter: '1 | 2 (channel)', description: 'Toggle channel 1 or 2', idempotent: false, exampleParams: ['1', '2'] },
215
- { command: 'setMode', parameter: '"<channel>;<mode>" e.g. "1;0"', description: 'Per-channel mode (see Relay Switch 1 modes)', idempotent: true, exampleParams: ['1;0', '2;3'] },
216
- { command: 'setPosition', parameter: '0-100 (roller percentage)', description: 'Roller-shade-pair mode only', idempotent: true, exampleParams: ['0', '50', '100'] },
217
- ],
218
- statusFields: ['switch1Status', 'switch2Status', 'voltage', 'electricCurrent', 'power', 'usedElectricity'],
219
- },
220
- {
221
- type: 'Humidifier',
222
- category: 'physical',
223
- description: 'Ultrasonic humidifier with auto and preset humidity level control.',
224
- role: 'climate',
225
- commands: [
226
- ...onOff,
227
- { command: 'setMode', parameter: 'auto | 101 (34%) | 102 (67%) | 103 (100%) | 0-100', description: 'Set preset or target humidity', idempotent: true, exampleParams: ['auto', '101', '50'] },
228
- ],
229
- statusFields: ['power', 'humidity', 'temperature', 'nebulizationEfficiency', 'auto', 'childLock', 'sound', 'lackWater'],
230
- },
231
- {
232
- type: 'Humidifier2',
233
- category: 'physical',
234
- description: 'Evaporative humidifier with multiple speed/auto/sleep/humidity modes and child lock.',
235
- role: 'climate',
236
- aliases: ['Evaporative Humidifier'],
237
- commands: [
238
- ...onOff,
239
- { command: 'setMode', parameter: '\'{"mode":1-8,"targetHumidify":0-100}\'', description: 'mode: 1=lv4 2=lv3 3=lv2 4=lv1 5=humidity 6=sleep 7=auto 8=drying', idempotent: true, exampleParams: ['{"mode":7,"targetHumidify":50}'] },
240
- { command: 'setChildLock', parameter: 'true | false', description: 'Enable or disable child lock', idempotent: true, exampleParams: ['true', 'false'] },
241
- ],
242
- statusFields: ['power', 'humidity', 'temperature', 'mode', 'childLock', 'filterElement'],
243
- },
244
- {
245
- type: 'Air Purifier VOC',
246
- category: 'physical',
247
- description: 'HEPA air purifier with VOC or PM2.5 sensing, multiple fan modes, and child lock.',
248
- role: 'climate',
249
- aliases: ['Air Purifier PM2.5', 'Air Purifier Table VOC', 'Air Purifier Table PM2.5'],
250
- commands: [
251
- ...onOff,
252
- { command: 'setMode', parameter: '\'{"mode":1-4,"fanGear":1-3}\'', description: 'mode: 1=normal 2=auto 3=sleep 4=pet; fanGear only when mode=1', idempotent: true, exampleParams: ['{"mode":2}', '{"mode":1,"fanGear":2}'] },
253
- { command: 'setChildLock', parameter: '0 | 1', description: 'Disable / enable child lock', idempotent: true, exampleParams: ['0', '1'] },
254
- ],
255
- statusFields: ['power', 'mode', 'childLock', 'filterElement'],
256
- },
257
- {
258
- type: 'Color Bulb',
259
- category: 'physical',
260
- description: 'Wi-Fi smart bulb with tunable brightness, RGB color, and color temperature.',
261
- role: 'lighting',
262
- commands: [...onOffToggle, ...lightControls],
263
- statusFields: ['power', 'brightness', 'color', 'colorTemperature', 'version'],
264
- },
265
- {
266
- type: 'Strip Light',
267
- category: 'physical',
268
- description: 'Addressable LED strip with on/off, brightness, RGB color, and color temperature control.',
269
- role: 'lighting',
270
- aliases: ['Strip Light 3'],
271
- commands: [...onOffToggle, ...lightControls],
272
- statusFields: ['power', 'brightness', 'color', 'colorTemperature', 'version'],
273
- },
274
- {
275
- type: 'Ceiling Light',
276
- category: 'physical',
277
- description: 'Smart ceiling fixture with brightness and color-temperature adjustment (no RGB).',
278
- role: 'lighting',
279
- aliases: ['Ceiling Light Pro'],
280
- commands: [
281
- ...onOffToggle,
282
- { command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage', idempotent: true, exampleParams: ['50', '80'] },
283
- { command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)', idempotent: true, exampleParams: ['2700', '4000', '6500'] },
284
- ],
285
- statusFields: ['power', 'brightness', 'colorTemperature', 'version'],
286
- },
287
- {
288
- type: 'Smart Radiator Thermostat',
289
- category: 'physical',
290
- description: 'Motorized thermostatic valve for radiators with schedule, manual, eco, and comfort modes.',
291
- role: 'climate',
292
- commands: [
293
- ...onOff,
294
- { command: 'setMode', parameter: '0=schedule 1=manual 2=off 3=eco 4=comfort 5=quickHeat', description: 'Operating mode', idempotent: true, exampleParams: ['1', '3'] },
295
- { command: 'setManualModeTemperature', parameter: '5-30 (°C)', description: 'Target temperature in manual mode', idempotent: true, exampleParams: ['20', '22'] },
296
- ],
297
- statusFields: ['power', 'temperature', 'humidity', 'battery', 'version', 'mode', 'targetTemperature'],
298
- },
299
- {
300
- type: 'Robot Vacuum Cleaner S1',
301
- category: 'physical',
302
- description: 'Entry-level robot vacuum with start/stop/dock and four suction power levels.',
303
- role: 'cleaning',
304
- aliases: ['Robot Vacuum', 'Robot Vacuum Cleaner S1 Plus', 'K10+'],
305
- commands: [
306
- { command: 'start', parameter: '—', description: 'Start cleaning', idempotent: true },
307
- { command: 'stop', parameter: '—', description: 'Stop cleaning', idempotent: true },
308
- { command: 'dock', parameter: '—', description: 'Return to dock', idempotent: true },
309
- { command: 'PowLevel', parameter: '0-3', description: '0=Quiet 1=Standard 2=Strong 3=Max', idempotent: true, exampleParams: ['0', '1', '2', '3'] },
310
- ],
311
- statusFields: ['workingStatus', 'onlineStatus', 'battery', 'version'],
312
- },
313
- {
314
- type: 'K10+ Pro Combo',
315
- category: 'physical',
316
- description: 'Compact robot vacuum and mop combo with sweep/mop sessions, fan level, and water level.',
317
- role: 'cleaning',
318
- aliases: ['K20+ Pro'],
319
- commands: [
320
- { command: 'startClean', parameter: '\'{"action":"sweep"|"mop","param":{"fanLevel":1-4,"times":1-2639999}}\'', description: 'Begin a cleaning session', idempotent: false, exampleParams: ['{"action":"sweep","param":{"fanLevel":2,"times":1}}'] },
321
- { command: 'pause', parameter: '—', description: 'Pause cleaning', idempotent: true },
322
- { command: 'dock', parameter: '—', description: 'Return to dock', idempotent: true },
323
- { command: 'setVolume', parameter: '0-100', description: 'Set voice volume', idempotent: true, exampleParams: ['0', '50', '100'] },
324
- { command: 'changeParam', parameter: '\'{"fanLevel":1-4,"waterLevel":1-2,"times":1-2639999}\'', description: 'Change parameters mid-run', idempotent: true, exampleParams: ['{"fanLevel":3,"waterLevel":1,"times":1}'] },
325
- ],
326
- statusFields: ['workingStatus', 'onlineStatus', 'battery', 'taskType'],
327
- },
328
- {
329
- type: 'Floor Cleaning Robot S10',
330
- category: 'physical',
331
- description: 'Advanced floor cleaning robot with sweep/mop modes, self-wash dock, and humidifier refill.',
332
- role: 'cleaning',
333
- aliases: ['Robot Vacuum Cleaner S10', 'Robot Vacuum Cleaner S20'],
334
- commands: [
335
- { command: 'startClean', parameter: '\'{"action":"sweep"|"sweep_mop","param":{"fanLevel":1-4,"waterLevel":1-2,"times":1-2639999}}\'', description: 'Begin a cleaning session', idempotent: false, exampleParams: ['{"action":"sweep","param":{"fanLevel":2,"waterLevel":1,"times":1}}'] },
336
- { command: 'pause', parameter: '—', description: 'Pause cleaning', idempotent: true },
337
- { command: 'dock', parameter: '—', description: 'Return to dock', idempotent: true },
338
- { command: 'addWaterForHumi', parameter: '—', description: 'Refill the humidifier water tank', idempotent: false },
339
- { command: 'selfClean', parameter: '1 | 2 | 3', description: '1=wash mop | 2=dry | 3=terminate self-clean', idempotent: false, exampleParams: ['1', '2', '3'] },
340
- { command: 'setVolume', parameter: '0-100', description: 'Set voice volume', idempotent: true, exampleParams: ['0', '50', '100'] },
341
- { command: 'changeParam', parameter: '\'{"fanLevel":1-4,"waterLevel":1-2,"times":1-2639999}\'', description: 'Change parameters mid-run', idempotent: true, exampleParams: ['{"fanLevel":3,"waterLevel":1,"times":1}'] },
342
- ],
343
- statusFields: ['workingStatus', 'onlineStatus', 'battery', 'taskType'],
344
- },
345
- {
346
- type: 'Battery Circulator Fan',
347
- category: 'physical',
348
- description: 'Rechargeable table/floor fan with wind modes, speed control, night-light, and auto-off timer.',
349
- role: 'fan',
350
- aliases: ['Circulator Fan'],
351
- commands: [
352
- ...onOffToggle,
353
- { command: 'setNightLightMode', parameter: 'off | 1 | 2', description: 'Night-light mode', idempotent: true, exampleParams: ['off', '1', '2'] },
354
- { command: 'setWindMode', parameter: 'direct | natural | sleep | baby', description: 'Wind mode', idempotent: true, exampleParams: ['natural', 'sleep'] },
355
- { command: 'setWindSpeed', parameter: '1-100', description: 'Fan speed', idempotent: true, exampleParams: ['50', '100'] },
356
- { command: 'closeDelay', parameter: 'seconds', description: 'Auto-off timer in seconds', idempotent: true, exampleParams: ['1800', '3600'] },
357
- ],
358
- statusFields: ['mode', 'version', 'battery', 'power', 'nightStatus', 'oscillation', 'verticalOscillation', 'chargingStatus', 'fanSpeed'],
359
- },
360
- {
361
- type: 'Blind Tilt',
362
- category: 'physical',
363
- description: 'Motorized tilt rod for horizontal blinds; controls slat angle (0=closed, 100=open).',
364
- role: 'curtain',
365
- commands: [
366
- ...onOff,
367
- { command: 'setPosition', parameter: '"<direction>;<angle>" (up|down; 0,2,...,100)', description: 'Tilt direction + angle (0=closed, 100=open)', idempotent: true, exampleParams: ['up;50', 'down;80'] },
368
- { command: 'fullyOpen', parameter: '—', description: 'Open fully', idempotent: true },
369
- { command: 'closeUp', parameter: '—', description: 'Close up', idempotent: true },
370
- { command: 'closeDown', parameter: '—', description: 'Close down', idempotent: true },
371
- ],
372
- statusFields: ['version', 'calibrate', 'group', 'moving', 'direction', 'slidePosition', 'battery'],
373
- },
374
- {
375
- type: 'Roller Shade',
376
- category: 'physical',
377
- description: 'Motorized roller blind that moves to a set position (0=open, 100=closed).',
378
- role: 'curtain',
379
- commands: [
380
- ...onOff,
381
- { command: 'setPosition', parameter: '0-100 (0=open, 100=closed)', description: 'Move to a position', idempotent: true, exampleParams: ['0', '50', '100'] },
382
- ],
383
- statusFields: ['slidePosition', 'battery', 'version', 'moving'],
384
- },
385
- {
386
- type: 'Garage Door Opener',
387
- category: 'physical',
388
- description: 'Cloud-connected garage door controller; turnOn opens and turnOff closes the door.',
389
- role: 'security',
390
- commands: [
391
- { command: 'turnOn', parameter: '—', description: 'Open the garage door', idempotent: true, safetyTier: 'destructive', safetyReason: 'Opens the garage door — anyone nearby can enter the space.' },
392
- { command: 'turnOff', parameter: '—', description: 'Close the garage door', idempotent: true, safetyTier: 'destructive', safetyReason: 'Closes the garage door — verify no person or obstacle is in the way.' },
393
- ],
394
- statusFields: ['switchStatus', 'version', 'online'],
395
- },
396
- {
397
- type: 'Video Doorbell',
398
- category: 'physical',
399
- description: 'Wi-Fi video doorbell with motion detection enable/disable control.',
400
- role: 'security',
401
- commands: [
402
- { command: 'enableMotionDetection', parameter: '—', description: 'Enable motion detection', idempotent: true },
403
- { command: 'disableMotionDetection', parameter: '—', description: 'Disable motion detection', idempotent: true },
404
- ],
405
- statusFields: ['battery', 'version'],
406
- },
407
- {
408
- type: 'Keypad',
409
- category: 'physical',
410
- description: 'PIN-pad access controller that creates and deletes door passcodes for a Smart Lock.',
411
- role: 'security',
412
- aliases: ['Keypad Touch'],
413
- commands: [
414
- { command: 'createKey', parameter: '\'{"name":"...","type":"permanent|timeLimit|disposable|urgent","password":"6-12 digits","startTime":<s>,"endTime":<s>}\'', description: 'Create a passcode (async; result via webhook)', idempotent: false, safetyTier: 'destructive', safetyReason: 'Provisions a new access credential — anyone with this passcode can unlock the door.' },
415
- { command: 'deleteKey', parameter: '\'{"id":<passcode_id>}\'', description: 'Delete a passcode (async; result via webhook)', idempotent: true, safetyTier: 'destructive', safetyReason: 'Permanently removes a passcode — the holder immediately loses door access.' },
416
- ],
417
- statusFields: ['version'],
418
- },
419
- {
420
- type: 'Candle Warmer Lamp',
421
- category: 'physical',
422
- description: 'Decorative candle-warmer lamp with adjustable brightness and color temperature.',
423
- role: 'lighting',
424
- commands: [
425
- ...onOffToggle,
426
- { command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage', idempotent: true, exampleParams: ['50', '80'] },
427
- { command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)', idempotent: true, exampleParams: ['2700', '4000'] },
428
- ],
429
- statusFields: ['power', 'brightness', 'colorTemperature', 'version'],
430
- },
431
- // Status-only devices (no commands)
432
- {
433
- type: 'Meter',
434
- category: 'physical',
435
- description: 'Battery-powered temperature and humidity sensor; read-only, no control commands.',
436
- role: 'sensor',
437
- readOnly: true,
438
- aliases: ['Meter Plus', 'MeterPro', 'MeterPro(CO2)', 'WoIOSensor', 'Hub 2'],
439
- commands: [],
440
- statusFields: ['temperature', 'humidity', 'CO2', 'battery', 'version'],
441
- },
442
- {
443
- type: 'Motion Sensor',
444
- category: 'physical',
445
- description: 'PIR motion detector that reports movement and ambient brightness; read-only.',
446
- role: 'sensor',
447
- readOnly: true,
448
- commands: [],
449
- statusFields: ['battery', 'version', 'moveDetected', 'brightness', 'openState'],
450
- },
451
- {
452
- type: 'Contact Sensor',
453
- category: 'physical',
454
- description: 'Door or window open/close sensor that also reports movement and brightness; read-only.',
455
- role: 'sensor',
456
- readOnly: true,
457
- commands: [],
458
- statusFields: ['battery', 'version', 'moveDetected', 'openState', 'brightness'],
459
- },
460
- {
461
- type: 'Water Leak Detector',
462
- category: 'physical',
463
- description: 'Water sensor that reports leak status; read-only, no control commands.',
464
- role: 'sensor',
465
- readOnly: true,
466
- commands: [],
467
- statusFields: ['battery', 'version', 'status'],
468
- },
469
- // Status-only hub-class devices (no control commands)
470
- {
471
- type: 'Hub Mini',
472
- category: 'physical',
473
- description: 'IR hub that bridges BLE devices to the cloud and learns IR remotes; no direct control commands.',
474
- role: 'hub',
475
- readOnly: true,
476
- aliases: ['Hub Mini2'],
477
- commands: [],
478
- statusFields: ['version'],
479
- },
480
- {
481
- type: 'Hub 3',
482
- category: 'physical',
483
- description: 'Wi-Fi hub with built-in temperature, humidity, and light sensors; manages local BLE devices.',
484
- role: 'hub',
485
- readOnly: true,
486
- commands: [],
487
- statusFields: ['version', 'temperature', 'humidity', 'lightLevel'],
488
- },
489
- {
490
- type: 'AI Hub',
491
- category: 'physical',
492
- description: 'Advanced hub with AI-based automations; bridges BLE devices to the cloud; read-only status.',
493
- role: 'hub',
494
- readOnly: true,
495
- commands: [],
496
- statusFields: ['version'],
497
- },
498
- {
499
- type: 'Home Climate Panel',
500
- category: 'physical',
501
- description: 'Wall-mounted display showing temperature and humidity; sensor-only, no control.',
502
- role: 'climate',
503
- readOnly: true,
504
- commands: [],
505
- statusFields: ['temperature', 'humidity', 'version'],
506
- },
507
- {
508
- type: 'Wallet Finder Card',
509
- category: 'physical',
510
- description: 'Slim Bluetooth tracker card for locating wallets; reports battery status only.',
511
- role: 'sensor',
512
- readOnly: true,
513
- commands: [],
514
- statusFields: ['battery', 'version'],
515
- },
516
- {
517
- type: 'Outdoor Spotlight Cam',
518
- category: 'physical',
519
- description: 'Battery-powered outdoor security camera with spotlight; status-only via cloud API.',
520
- role: 'security',
521
- readOnly: true,
522
- commands: [],
523
- statusFields: ['battery', 'version'],
524
- },
525
- // ---------- Virtual IR remotes ----------
526
- {
527
- type: 'Air Conditioner',
528
- category: 'ir',
529
- description: 'IR-controlled air conditioner with on/off and full HVAC parameter control (mode, fan, temp).',
530
- role: 'climate',
531
- commands: [
532
- ...onOff,
533
- { command: 'setAll', parameter: '"<temp>,<mode>,<fan>,<on|off>"', description: 'mode: 1=auto 2=cool 3=dry 4=fan 5=heat; fan: 1=auto 2=low 3=mid 4=high', idempotent: true, exampleParams: ['26,2,3,on', '22,5,2,on'] },
534
- ],
535
- },
536
- {
537
- type: 'TV',
538
- category: 'ir',
539
- description: 'IR-controlled television or streaming device with channel, volume, and power commands.',
540
- role: 'media',
541
- aliases: ['IPTV', 'Streamer', 'Set Top Box'],
542
- commands: [
543
- ...onOff,
544
- { command: 'SetChannel', parameter: '1-999 (channel number)', description: 'Switch to a specific channel', idempotent: true, exampleParams: ['1', '15'] },
545
- { command: 'volumeAdd', parameter: '—', description: 'Volume up', idempotent: false },
546
- { command: 'volumeSub', parameter: '—', description: 'Volume down', idempotent: false },
547
- { command: 'channelAdd', parameter: '—', description: 'Channel up', idempotent: false },
548
- { command: 'channelSub', parameter: '—', description: 'Channel down', idempotent: false },
549
- ],
550
- },
551
- {
552
- type: 'DVD',
553
- category: 'ir',
554
- description: 'IR-controlled disc player or speaker with playback, track navigation, and volume commands.',
555
- role: 'media',
556
- aliases: ['Speaker'],
557
- commands: [
558
- ...onOff,
559
- { command: 'setMute', parameter: '—', description: 'Toggle mute', idempotent: false },
560
- { command: 'FastForward', parameter: '—', description: 'Fast forward', idempotent: false },
561
- { command: 'Rewind', parameter: '—', description: 'Rewind', idempotent: false },
562
- { command: 'Next', parameter: '—', description: 'Next track', idempotent: false },
563
- { command: 'Previous', parameter: '—', description: 'Previous track', idempotent: false },
564
- { command: 'Pause', parameter: '—', description: 'Pause', idempotent: true },
565
- { command: 'Play', parameter: '—', description: 'Play', idempotent: true },
566
- { command: 'Stop', parameter: '—', description: 'Stop', idempotent: true },
567
- { command: 'volumeAdd', parameter: '—', description: 'Volume up', idempotent: false },
568
- { command: 'volumeSub', parameter: '—', description: 'Volume down', idempotent: false },
569
- ],
570
- },
571
- {
572
- type: 'Fan',
573
- category: 'ir',
574
- description: 'IR-controlled fan with on/off, swing, timer, and speed preset commands.',
575
- role: 'fan',
576
- commands: [
577
- ...onOff,
578
- { command: 'swing', parameter: '—', description: 'Toggle swing', idempotent: false },
579
- { command: 'timer', parameter: '—', description: 'Toggle timer', idempotent: false },
580
- { command: 'lowSpeed', parameter: '—', description: 'Low speed', idempotent: true },
581
- { command: 'middleSpeed', parameter: '—', description: 'Medium speed', idempotent: true },
582
- { command: 'highSpeed', parameter: '—', description: 'High speed', idempotent: true },
583
- ],
584
- },
585
- {
586
- type: 'Light',
587
- category: 'ir',
588
- description: 'IR-controlled light fixture with on/off and relative brightness adjustment commands.',
589
- role: 'lighting',
590
- commands: [
591
- ...onOff,
592
- { command: 'brightnessUp', parameter: '—', description: 'Brightness up', idempotent: false },
593
- { command: 'brightnessDown', parameter: '—', description: 'Brightness down', idempotent: false },
594
- ],
595
- },
596
- {
597
- type: 'Others',
598
- category: 'ir',
599
- description: 'Catch-all for custom IR remotes with user-defined button names learned by a Hub.',
600
- role: 'other',
601
- commands: [
602
- { command: '<buttonName>', parameter: '—', description: 'User-defined custom IR button (requires --type customize)', commandType: 'customize' },
603
- ],
604
- },
605
- ];
606
- /** Find a catalog entry by exact name, alias, or case-insensitive substring. */
607
- export function findCatalogEntry(query) {
608
- const q = query.trim().toLowerCase();
609
- if (!q)
610
- return null;
611
- const names = (e) => [e.type, ...(e.aliases ?? [])];
612
- const catalog = getEffectiveCatalog();
613
- const exact = catalog.find((e) => names(e).some((n) => n.toLowerCase() === q));
614
- if (exact)
615
- return exact;
616
- const matches = catalog.filter((e) => names(e).some((n) => n.toLowerCase().includes(q)));
617
- if (matches.length === 0)
618
- return null;
619
- if (matches.length === 1)
620
- return matches[0];
621
- return matches;
622
- }
623
- /**
624
- * Derive the safety tier for a catalog command, honouring an explicit
625
- * `safetyTier` when present and falling back to heuristic inference.
626
- *
627
- * The inference order is:
628
- * 1. Explicit `spec.safetyTier`.
629
- * 2. IR context (customize command OR entry.category === 'ir')
630
- * → `'ir-fire-forget'`.
631
- * 3. Default → `'mutation'`.
632
- */
633
- export function deriveSafetyTier(spec, entry) {
634
- if (spec.safetyTier)
635
- return spec.safetyTier;
636
- if (spec.commandType === 'customize')
637
- return 'ir-fire-forget';
638
- if (entry?.category === 'ir')
639
- return 'ir-fire-forget';
640
- return 'mutation';
641
- }
642
- /** Read the safety reason for a command. */
643
- export function getCommandSafetyReason(spec) {
644
- return spec.safetyReason ?? null;
645
- }
646
- /**
647
- * Pick up to 3 non-destructive, idempotent commands an agent can safely invoke
648
- * to explore or exercise a device. Used by `devices describe --json` to hint
649
- * at concrete next steps.
650
- */
651
- export function suggestedActions(entry) {
652
- const safe = entry.commands.filter((c) => c.idempotent === true &&
653
- deriveSafetyTier(c, entry) !== 'destructive' &&
654
- c.commandType !== 'customize');
655
- const picks = [];
656
- const seen = new Set();
657
- for (const c of safe) {
658
- if (seen.has(c.command))
659
- continue;
660
- seen.add(c.command);
661
- picks.push(c);
662
- if (picks.length >= 3)
663
- break;
664
- }
665
- return picks.map((c) => ({
666
- command: c.command,
667
- parameter: c.exampleParams?.[0],
668
- description: c.description,
669
- }));
670
- }
671
- // ---- Overlay loader ----------------------------------------------------
672
- //
673
- // Users can drop a `~/.switchbot/catalog.json` file to override or extend
674
- // the built-in catalog without waiting on a CLI release. The overlay is a
675
- // list of DeviceCatalogEntry objects; each entry matches on `type`:
676
- // - Entry with `type` matching a built-in replaces that built-in entry.
677
- // - Entry with a new `type` is appended.
678
- // - Entry with `{ type: "X", remove: true }` deletes the built-in.
679
- //
680
- // The overlay is loaded once per process and cached. Malformed JSON or
681
- // files that don't match the expected shape are ignored (with a warning
682
- // to stderr in verbose mode).
683
- import fs from 'node:fs';
684
- import path from 'node:path';
685
- import os from 'node:os';
686
- function overlayFilePath() {
687
- return path.join(os.homedir(), '.switchbot', 'catalog.json');
688
- }
689
- export function getCatalogOverlayPath() {
690
- return overlayFilePath();
691
- }
692
- /** Read the overlay file. Never throws — returns `error` on bad files. */
693
- export function loadCatalogOverlay() {
694
- const file = overlayFilePath();
695
- if (!fs.existsSync(file)) {
696
- return { path: file, exists: false, entries: [] };
697
- }
698
- try {
699
- const raw = fs.readFileSync(file, 'utf-8');
700
- const parsed = JSON.parse(raw);
701
- if (!Array.isArray(parsed)) {
702
- return {
703
- path: file,
704
- exists: true,
705
- entries: [],
706
- error: 'overlay must be a JSON array of device catalog entries',
707
- };
708
- }
709
- const entries = [];
710
- for (const item of parsed) {
711
- if (!item || typeof item !== 'object' || typeof item.type !== 'string') {
712
- return {
713
- path: file,
714
- exists: true,
715
- entries: [],
716
- error: 'every overlay entry must be an object with a string `type`',
717
- };
718
- }
719
- entries.push(item);
720
- }
721
- return { path: file, exists: true, entries };
722
- }
723
- catch (err) {
724
- return {
725
- path: file,
726
- exists: true,
727
- entries: [],
728
- error: err instanceof Error ? err.message : String(err),
729
- };
730
- }
731
- }
732
- let overlayCache = null;
733
- function overlayOnce() {
734
- if (overlayCache === null)
735
- overlayCache = loadCatalogOverlay();
736
- return overlayCache;
737
- }
738
- /** Clear the overlay cache (test helper; also useful for `catalog refresh`). */
739
- export function resetCatalogOverlayCache() {
740
- overlayCache = null;
741
- }
742
- /** Merge built-in catalog with the on-disk overlay. */
743
- export function getEffectiveCatalog() {
744
- const overlay = overlayOnce();
745
- if (!overlay.entries.length)
746
- return DEVICE_CATALOG;
747
- const byType = new Map();
748
- for (const e of DEVICE_CATALOG)
749
- byType.set(e.type, e);
750
- for (const entry of overlay.entries) {
751
- if (entry.remove) {
752
- byType.delete(entry.type);
753
- continue;
754
- }
755
- const existing = byType.get(entry.type);
756
- if (existing) {
757
- byType.set(entry.type, { ...existing, ...entry });
758
- }
759
- else if (entry.category && entry.commands) {
760
- // New entry — require the fields the renderer needs. Missing fields
761
- // would make the new entry crash later, so skip silently rather than
762
- // ship half-valid data to the user.
763
- byType.set(entry.type, entry);
764
- }
765
- }
766
- return Array.from(byType.values());
767
- }
768
- //# sourceMappingURL=catalog.js.map