@switchbot/openapi-cli 1.0.1 → 1.2.0
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.
- package/README.md +174 -18
- package/dist/api/client.d.ts +7 -1
- package/dist/api/client.js +44 -8
- package/dist/api/client.js.map +1 -1
- package/dist/commands/batch.d.ts +2 -0
- package/dist/commands/batch.js +252 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/cache.d.ts +2 -0
- package/dist/commands/cache.js +108 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/capabilities.d.ts +2 -0
- package/dist/commands/capabilities.js +91 -0
- package/dist/commands/capabilities.js.map +1 -0
- package/dist/commands/catalog.d.ts +2 -0
- package/dist/commands/catalog.js +291 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/config.js +123 -10
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/devices.js +234 -112
- package/dist/commands/devices.js.map +1 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +147 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/events.d.ts +15 -0
- package/dist/commands/events.js +188 -0
- package/dist/commands/events.js.map +1 -0
- package/dist/commands/explain.d.ts +2 -0
- package/dist/commands/explain.js +137 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/history.d.ts +2 -0
- package/dist/commands/history.js +104 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/mcp.d.ts +4 -0
- package/dist/commands/mcp.js +386 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/plan.d.ts +37 -0
- package/dist/commands/plan.js +344 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/quota.d.ts +2 -0
- package/dist/commands/quota.js +77 -0
- package/dist/commands/quota.js.map +1 -0
- package/dist/commands/scenes.js +19 -13
- package/dist/commands/scenes.js.map +1 -1
- package/dist/commands/schema.d.ts +2 -0
- package/dist/commands/schema.js +77 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +161 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/webhook.js +37 -22
- package/dist/commands/webhook.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +32 -6
- package/dist/config.js.map +1 -1
- package/dist/devices/cache.d.ts +75 -0
- package/dist/devices/cache.js +225 -0
- package/dist/devices/cache.js.map +1 -0
- package/dist/devices/catalog.d.ts +49 -0
- package/dist/devices/catalog.js +362 -92
- package/dist/devices/catalog.js.map +1 -1
- package/dist/index.js +31 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/devices.d.ts +144 -0
- package/dist/lib/devices.js +329 -0
- package/dist/lib/devices.js.map +1 -0
- package/dist/lib/scenes.d.ts +7 -0
- package/dist/lib/scenes.js +11 -0
- package/dist/lib/scenes.js.map +1 -0
- package/dist/utils/audit.d.ts +13 -0
- package/dist/utils/audit.js +43 -0
- package/dist/utils/audit.js.map +1 -0
- package/dist/utils/filter.d.ts +45 -0
- package/dist/utils/filter.js +96 -0
- package/dist/utils/filter.js.map +1 -0
- package/dist/utils/flags.d.ts +42 -0
- package/dist/utils/flags.js +108 -0
- package/dist/utils/flags.js.map +1 -1
- package/dist/utils/format.d.ts +9 -0
- package/dist/utils/format.js +109 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/output.d.ts +11 -0
- package/dist/utils/output.js +37 -6
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/quota.d.ts +48 -0
- package/dist/utils/quota.js +144 -0
- package/dist/utils/quota.js.map +1 -0
- package/dist/utils/retry.d.ts +23 -0
- package/dist/utils/retry.js +60 -0
- package/dist/utils/retry.js.map +1 -0
- package/package.json +4 -1
package/dist/devices/catalog.js
CHANGED
|
@@ -2,82 +2,109 @@
|
|
|
2
2
|
* Static catalog of SwitchBot device types, control commands and status fields.
|
|
3
3
|
* Sourced from https://github.com/OpenWonderLabs/SwitchBotAPI — keep in sync
|
|
4
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.destructive: causes a real-world effect that is hard or
|
|
11
|
+
* unsafe to reverse (unlock, garage open, deleteKey). UIs and agents
|
|
12
|
+
* should require explicit confirmation before issuing these.
|
|
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'.
|
|
5
17
|
*/
|
|
18
|
+
// ---- Command fragments (reused across entries) -------------------------
|
|
6
19
|
const onOff = [
|
|
7
|
-
{ command: 'turnOn', parameter: '—', description: 'Power on' },
|
|
8
|
-
{ command: 'turnOff', parameter: '—', description: 'Power off' },
|
|
20
|
+
{ command: 'turnOn', parameter: '—', description: 'Power on', idempotent: true },
|
|
21
|
+
{ command: 'turnOff', parameter: '—', description: 'Power off', idempotent: true },
|
|
9
22
|
];
|
|
10
23
|
const onOffToggle = [
|
|
11
24
|
...onOff,
|
|
12
|
-
{ command: 'toggle', parameter: '—', description: 'Toggle power' },
|
|
25
|
+
{ command: 'toggle', parameter: '—', description: 'Toggle power', idempotent: false },
|
|
13
26
|
];
|
|
14
27
|
const lightControls = [
|
|
15
|
-
{ command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage' },
|
|
16
|
-
{ command: 'setColor', parameter: 'R:G:B (0-255 each)', description: 'Set RGB color, e.g. "255:0:0"' },
|
|
17
|
-
{ command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)' },
|
|
28
|
+
{ command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage', idempotent: true, exampleParams: ['50', '80'] },
|
|
29
|
+
{ 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'] },
|
|
30
|
+
{ command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)', idempotent: true, exampleParams: ['2700', '4000', '6500'] },
|
|
18
31
|
];
|
|
19
32
|
export const DEVICE_CATALOG = [
|
|
20
33
|
// ---------- Physical devices ----------
|
|
21
34
|
{
|
|
22
35
|
type: 'Bot',
|
|
23
36
|
category: 'physical',
|
|
37
|
+
description: 'Mechanical arm robot that physically presses a button or toggles a switch on demand.',
|
|
38
|
+
role: 'other',
|
|
24
39
|
commands: [
|
|
25
40
|
...onOff,
|
|
26
|
-
{ command: 'press', parameter: '—', description: 'Press the button (momentary)' },
|
|
41
|
+
{ command: 'press', parameter: '—', description: 'Press the button (momentary)', idempotent: false },
|
|
27
42
|
],
|
|
28
43
|
statusFields: ['power', 'battery', 'deviceMode', 'version'],
|
|
29
44
|
},
|
|
30
45
|
{
|
|
31
46
|
type: 'Curtain',
|
|
32
47
|
category: 'physical',
|
|
48
|
+
description: 'Motorized curtain track runner that opens/closes curtains by slide position (0=open, 100=closed).',
|
|
49
|
+
role: 'curtain',
|
|
33
50
|
aliases: ['Curtain3', 'Curtain 3'],
|
|
34
51
|
commands: [
|
|
35
52
|
...onOff,
|
|
36
|
-
{ command: 'pause', parameter: '—', description: 'Stop movement' },
|
|
37
|
-
{ command: 'setPosition', parameter: '0-100 (0=open, 100=closed)', description: 'Move to a position' },
|
|
38
|
-
{ command: 'setPosition', parameter: 'index,mode,position (e.g. "0,ff,80")', description: 'Multi-arg form: mode=0 Performance | 1 Silent | ff default' },
|
|
53
|
+
{ command: 'pause', parameter: '—', description: 'Stop movement', idempotent: true },
|
|
54
|
+
{ command: 'setPosition', parameter: '0-100 (0=open, 100=closed)', description: 'Move to a position', idempotent: true, exampleParams: ['0', '50', '100'] },
|
|
55
|
+
{ 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'] },
|
|
39
56
|
],
|
|
40
57
|
statusFields: ['calibrate', 'group', 'moving', 'slidePosition', 'battery', 'version'],
|
|
41
58
|
},
|
|
42
59
|
{
|
|
43
60
|
type: 'Smart Lock',
|
|
44
61
|
category: 'physical',
|
|
62
|
+
description: 'Bluetooth/Wi-Fi electronic deadbolt that locks and unlocks a door via cloud API.',
|
|
63
|
+
role: 'security',
|
|
45
64
|
aliases: ['Smart Lock Pro'],
|
|
46
65
|
commands: [
|
|
47
|
-
{ command: 'lock', parameter: '—', description: 'Lock the door' },
|
|
48
|
-
{ command: 'unlock', parameter: '—', description: 'Unlock the door' },
|
|
49
|
-
{ command: 'deadbolt', parameter: '—', description: 'Pro only: engage deadbolt' },
|
|
66
|
+
{ command: 'lock', parameter: '—', description: 'Lock the door', idempotent: true },
|
|
67
|
+
{ command: 'unlock', parameter: '—', description: 'Unlock the door', idempotent: true, destructive: true, destructiveReason: 'Physically unlocks the door — anyone nearby can open it.' },
|
|
68
|
+
{ command: 'deadbolt', parameter: '—', description: 'Pro only: engage deadbolt', idempotent: true },
|
|
50
69
|
],
|
|
51
70
|
statusFields: ['battery', 'version', 'lockState', 'doorState', 'calibrate'],
|
|
52
71
|
},
|
|
53
72
|
{
|
|
54
73
|
type: 'Smart Lock Lite',
|
|
55
74
|
category: 'physical',
|
|
75
|
+
description: 'Compact electronic deadbolt with lock and unlock control; no deadbolt mode.',
|
|
76
|
+
role: 'security',
|
|
56
77
|
commands: [
|
|
57
|
-
{ command: 'lock', parameter: '—', description: 'Lock the door' },
|
|
58
|
-
{ command: 'unlock', parameter: '—', description: 'Unlock the door' },
|
|
78
|
+
{ command: 'lock', parameter: '—', description: 'Lock the door', idempotent: true },
|
|
79
|
+
{ command: 'unlock', parameter: '—', description: 'Unlock the door', idempotent: true, destructive: true, destructiveReason: 'Physically unlocks the door — anyone nearby can open it.' },
|
|
59
80
|
],
|
|
60
81
|
statusFields: ['battery', 'version', 'lockState', 'doorState', 'calibrate'],
|
|
61
82
|
},
|
|
62
83
|
{
|
|
63
84
|
type: 'Smart Lock Ultra',
|
|
64
85
|
category: 'physical',
|
|
86
|
+
description: 'Premium electronic deadbolt with full lock, unlock, and deadbolt control.',
|
|
87
|
+
role: 'security',
|
|
65
88
|
commands: [
|
|
66
|
-
{ command: 'lock', parameter: '—', description: 'Lock the door' },
|
|
67
|
-
{ command: 'unlock', parameter: '—', description: 'Unlock the door' },
|
|
68
|
-
{ command: 'deadbolt', parameter: '—', description: 'Engage deadbolt' },
|
|
89
|
+
{ command: 'lock', parameter: '—', description: 'Lock the door', idempotent: true },
|
|
90
|
+
{ command: 'unlock', parameter: '—', description: 'Unlock the door', idempotent: true, destructive: true, destructiveReason: 'Physically unlocks the door — anyone nearby can open it.' },
|
|
91
|
+
{ command: 'deadbolt', parameter: '—', description: 'Engage deadbolt', idempotent: true },
|
|
69
92
|
],
|
|
70
93
|
statusFields: ['battery', 'version', 'lockState', 'doorState', 'calibrate'],
|
|
71
94
|
},
|
|
72
95
|
{
|
|
73
96
|
type: 'Plug',
|
|
74
97
|
category: 'physical',
|
|
98
|
+
description: 'Smart wall outlet plug with on/off/toggle control and basic power status.',
|
|
99
|
+
role: 'power',
|
|
75
100
|
commands: onOffToggle,
|
|
76
101
|
statusFields: ['power', 'version'],
|
|
77
102
|
},
|
|
78
103
|
{
|
|
79
104
|
type: 'Plug Mini (US)',
|
|
80
105
|
category: 'physical',
|
|
106
|
+
description: 'Compact smart plug with voltage, current, and daily energy consumption reporting.',
|
|
107
|
+
role: 'power',
|
|
81
108
|
aliases: ['Plug Mini (JP)'],
|
|
82
109
|
commands: onOffToggle,
|
|
83
110
|
statusFields: ['voltage', 'weight', 'electricityOfDay', 'electricCurrent', 'power', 'version'],
|
|
@@ -85,195 +112,237 @@ export const DEVICE_CATALOG = [
|
|
|
85
112
|
{
|
|
86
113
|
type: 'Relay Switch 1',
|
|
87
114
|
category: 'physical',
|
|
115
|
+
description: 'In-wall relay switch with configurable modes (toggle/edge/detached/momentary) and power metering.',
|
|
116
|
+
role: 'power',
|
|
88
117
|
aliases: ['Relay Switch 1PM'],
|
|
89
118
|
commands: [
|
|
90
119
|
...onOffToggle,
|
|
91
|
-
{ command: 'setMode', parameter: '0=toggle | 1=edge | 2=detached | 3=momentary', description: 'Switch operating mode' },
|
|
120
|
+
{ command: 'setMode', parameter: '0=toggle | 1=edge | 2=detached | 3=momentary', description: 'Switch operating mode', idempotent: true, exampleParams: ['0', '1', '2', '3'] },
|
|
92
121
|
],
|
|
93
122
|
statusFields: ['switchStatus', 'voltage', 'version', 'useTime', 'electricCurrent', 'power', 'usedElectricity'],
|
|
94
123
|
},
|
|
95
124
|
{
|
|
96
125
|
type: 'Relay Switch 2PM',
|
|
97
126
|
category: 'physical',
|
|
127
|
+
description: 'Dual-channel relay switch with per-channel on/off/toggle and optional roller-shade mode.',
|
|
128
|
+
role: 'power',
|
|
98
129
|
commands: [
|
|
99
|
-
{ command: 'turnOn', parameter: '1 | 2 (channel)', description: 'Turn on channel 1 or 2' },
|
|
100
|
-
{ command: 'turnOff', parameter: '1 | 2 (channel)', description: 'Turn off channel 1 or 2' },
|
|
101
|
-
{ command: 'toggle', parameter: '1 | 2 (channel)', description: 'Toggle channel 1 or 2' },
|
|
102
|
-
{ command: 'setMode', parameter: '"<channel>;<mode>" e.g. "1;0"', description: 'Per-channel mode (see Relay Switch 1 modes)' },
|
|
103
|
-
{ command: 'setPosition', parameter: '0-100 (roller percentage)', description: 'Roller-shade-pair mode only' },
|
|
130
|
+
{ command: 'turnOn', parameter: '1 | 2 (channel)', description: 'Turn on channel 1 or 2', idempotent: true, exampleParams: ['1', '2'] },
|
|
131
|
+
{ command: 'turnOff', parameter: '1 | 2 (channel)', description: 'Turn off channel 1 or 2', idempotent: true, exampleParams: ['1', '2'] },
|
|
132
|
+
{ command: 'toggle', parameter: '1 | 2 (channel)', description: 'Toggle channel 1 or 2', idempotent: false, exampleParams: ['1', '2'] },
|
|
133
|
+
{ 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'] },
|
|
134
|
+
{ command: 'setPosition', parameter: '0-100 (roller percentage)', description: 'Roller-shade-pair mode only', idempotent: true, exampleParams: ['0', '50', '100'] },
|
|
104
135
|
],
|
|
105
136
|
statusFields: ['switch1Status', 'switch2Status', 'voltage', 'electricCurrent', 'power', 'usedElectricity'],
|
|
106
137
|
},
|
|
107
138
|
{
|
|
108
139
|
type: 'Humidifier',
|
|
109
140
|
category: 'physical',
|
|
141
|
+
description: 'Ultrasonic humidifier with auto and preset humidity level control.',
|
|
142
|
+
role: 'climate',
|
|
110
143
|
commands: [
|
|
111
144
|
...onOff,
|
|
112
|
-
{ command: 'setMode', parameter: 'auto | 101 (34%) | 102 (67%) | 103 (100%) | 0-100', description: 'Set preset or target humidity' },
|
|
145
|
+
{ command: 'setMode', parameter: 'auto | 101 (34%) | 102 (67%) | 103 (100%) | 0-100', description: 'Set preset or target humidity', idempotent: true, exampleParams: ['auto', '101', '50'] },
|
|
113
146
|
],
|
|
114
147
|
statusFields: ['power', 'humidity', 'temperature', 'nebulizationEfficiency', 'auto', 'childLock', 'sound', 'lackWater'],
|
|
115
148
|
},
|
|
116
149
|
{
|
|
117
150
|
type: 'Humidifier2',
|
|
118
151
|
category: 'physical',
|
|
152
|
+
description: 'Evaporative humidifier with multiple speed/auto/sleep/humidity modes and child lock.',
|
|
153
|
+
role: 'climate',
|
|
119
154
|
aliases: ['Evaporative Humidifier'],
|
|
120
155
|
commands: [
|
|
121
156
|
...onOff,
|
|
122
|
-
{ 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' },
|
|
123
|
-
{ command: 'setChildLock', parameter: 'true | false', description: 'Enable or disable child lock' },
|
|
157
|
+
{ 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}'] },
|
|
158
|
+
{ command: 'setChildLock', parameter: 'true | false', description: 'Enable or disable child lock', idempotent: true, exampleParams: ['true', 'false'] },
|
|
124
159
|
],
|
|
125
160
|
statusFields: ['power', 'humidity', 'temperature', 'mode', 'childLock', 'filterElement'],
|
|
126
161
|
},
|
|
127
162
|
{
|
|
128
163
|
type: 'Air Purifier VOC',
|
|
129
164
|
category: 'physical',
|
|
165
|
+
description: 'HEPA air purifier with VOC or PM2.5 sensing, multiple fan modes, and child lock.',
|
|
166
|
+
role: 'climate',
|
|
130
167
|
aliases: ['Air Purifier PM2.5', 'Air Purifier Table VOC', 'Air Purifier Table PM2.5'],
|
|
131
168
|
commands: [
|
|
132
169
|
...onOff,
|
|
133
|
-
{ command: 'setMode', parameter: '\'{"mode":1-4,"fanGear":1-3}\'', description: 'mode: 1=normal 2=auto 3=sleep 4=pet; fanGear only when mode=1' },
|
|
134
|
-
{ command: 'setChildLock', parameter: '0 | 1', description: 'Disable / enable child lock' },
|
|
170
|
+
{ 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}'] },
|
|
171
|
+
{ command: 'setChildLock', parameter: '0 | 1', description: 'Disable / enable child lock', idempotent: true, exampleParams: ['0', '1'] },
|
|
135
172
|
],
|
|
136
173
|
statusFields: ['power', 'mode', 'childLock', 'filterElement'],
|
|
137
174
|
},
|
|
138
175
|
{
|
|
139
176
|
type: 'Color Bulb',
|
|
140
177
|
category: 'physical',
|
|
178
|
+
description: 'Wi-Fi smart bulb with tunable brightness, RGB color, and color temperature.',
|
|
179
|
+
role: 'lighting',
|
|
141
180
|
commands: [...onOffToggle, ...lightControls],
|
|
142
181
|
statusFields: ['power', 'brightness', 'color', 'colorTemperature', 'version'],
|
|
143
182
|
},
|
|
144
183
|
{
|
|
145
184
|
type: 'Strip Light',
|
|
146
185
|
category: 'physical',
|
|
147
|
-
|
|
148
|
-
|
|
186
|
+
description: 'Addressable LED strip with on/off, brightness, RGB color, and color temperature control.',
|
|
187
|
+
role: 'lighting',
|
|
188
|
+
aliases: ['Strip Light 3'],
|
|
189
|
+
commands: [...onOffToggle, ...lightControls],
|
|
190
|
+
statusFields: ['power', 'brightness', 'color', 'colorTemperature', 'version'],
|
|
149
191
|
},
|
|
150
192
|
{
|
|
151
193
|
type: 'Ceiling Light',
|
|
152
194
|
category: 'physical',
|
|
195
|
+
description: 'Smart ceiling fixture with brightness and color-temperature adjustment (no RGB).',
|
|
196
|
+
role: 'lighting',
|
|
153
197
|
aliases: ['Ceiling Light Pro'],
|
|
154
198
|
commands: [
|
|
155
199
|
...onOffToggle,
|
|
156
|
-
{ command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage' },
|
|
157
|
-
{ command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)' },
|
|
200
|
+
{ command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage', idempotent: true, exampleParams: ['50', '80'] },
|
|
201
|
+
{ command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)', idempotent: true, exampleParams: ['2700', '4000', '6500'] },
|
|
158
202
|
],
|
|
159
203
|
statusFields: ['power', 'brightness', 'colorTemperature', 'version'],
|
|
160
204
|
},
|
|
161
205
|
{
|
|
162
206
|
type: 'Smart Radiator Thermostat',
|
|
163
207
|
category: 'physical',
|
|
208
|
+
description: 'Motorized thermostatic valve for radiators with schedule, manual, eco, and comfort modes.',
|
|
209
|
+
role: 'climate',
|
|
164
210
|
commands: [
|
|
165
211
|
...onOff,
|
|
166
|
-
{ command: 'setMode', parameter: '0=schedule 1=manual 2=off 3=eco 4=comfort 5=quickHeat', description: 'Operating mode' },
|
|
167
|
-
{ command: 'setManualModeTemperature', parameter: '5-30 (°C)', description: 'Target temperature in manual mode' },
|
|
212
|
+
{ command: 'setMode', parameter: '0=schedule 1=manual 2=off 3=eco 4=comfort 5=quickHeat', description: 'Operating mode', idempotent: true, exampleParams: ['1', '3'] },
|
|
213
|
+
{ command: 'setManualModeTemperature', parameter: '5-30 (°C)', description: 'Target temperature in manual mode', idempotent: true, exampleParams: ['20', '22'] },
|
|
168
214
|
],
|
|
169
215
|
statusFields: ['power', 'temperature', 'humidity', 'battery', 'version', 'mode', 'targetTemperature'],
|
|
170
216
|
},
|
|
171
217
|
{
|
|
172
218
|
type: 'Robot Vacuum Cleaner S1',
|
|
173
219
|
category: 'physical',
|
|
220
|
+
description: 'Entry-level robot vacuum with start/stop/dock and four suction power levels.',
|
|
221
|
+
role: 'cleaning',
|
|
174
222
|
aliases: ['Robot Vacuum Cleaner S1 Plus', 'K10+'],
|
|
175
223
|
commands: [
|
|
176
|
-
{ command: 'start', parameter: '—', description: 'Start cleaning' },
|
|
177
|
-
{ command: 'stop', parameter: '—', description: 'Stop cleaning' },
|
|
178
|
-
{ command: 'dock', parameter: '—', description: 'Return to dock' },
|
|
179
|
-
{ command: 'PowLevel', parameter: '0-3', description: '0=Quiet 1=Standard 2=Strong 3=Max' },
|
|
224
|
+
{ command: 'start', parameter: '—', description: 'Start cleaning', idempotent: true },
|
|
225
|
+
{ command: 'stop', parameter: '—', description: 'Stop cleaning', idempotent: true },
|
|
226
|
+
{ command: 'dock', parameter: '—', description: 'Return to dock', idempotent: true },
|
|
227
|
+
{ command: 'PowLevel', parameter: '0-3', description: '0=Quiet 1=Standard 2=Strong 3=Max', idempotent: true, exampleParams: ['0', '1', '2', '3'] },
|
|
180
228
|
],
|
|
181
229
|
statusFields: ['workingStatus', 'onlineStatus', 'battery', 'version'],
|
|
182
230
|
},
|
|
183
231
|
{
|
|
184
232
|
type: 'K10+ Pro Combo',
|
|
185
233
|
category: 'physical',
|
|
234
|
+
description: 'Compact robot vacuum and mop combo with sweep/mop sessions, fan level, and water level.',
|
|
235
|
+
role: 'cleaning',
|
|
186
236
|
aliases: ['K20+ Pro'],
|
|
187
237
|
commands: [
|
|
188
|
-
{ command: 'startClean', parameter: '\'{"action":"sweep"|"mop","param":{"fanLevel":1-4,"times":1-2639999}}\'', description: 'Begin a cleaning session' },
|
|
189
|
-
{ command: 'pause', parameter: '—', description: 'Pause cleaning' },
|
|
190
|
-
{ command: 'dock', parameter: '—', description: 'Return to dock' },
|
|
191
|
-
{ command: 'setVolume', parameter: '0-100', description: 'Set voice volume' },
|
|
192
|
-
{ command: 'changeParam', parameter: '\'{"fanLevel":1-4,"waterLevel":1-2,"times":1-2639999}\'', description: 'Change parameters mid-run' },
|
|
238
|
+
{ 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}}'] },
|
|
239
|
+
{ command: 'pause', parameter: '—', description: 'Pause cleaning', idempotent: true },
|
|
240
|
+
{ command: 'dock', parameter: '—', description: 'Return to dock', idempotent: true },
|
|
241
|
+
{ command: 'setVolume', parameter: '0-100', description: 'Set voice volume', idempotent: true, exampleParams: ['0', '50', '100'] },
|
|
242
|
+
{ 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}'] },
|
|
193
243
|
],
|
|
194
244
|
statusFields: ['workingStatus', 'onlineStatus', 'battery', 'taskType'],
|
|
195
245
|
},
|
|
196
246
|
{
|
|
197
247
|
type: 'Floor Cleaning Robot S10',
|
|
198
248
|
category: 'physical',
|
|
249
|
+
description: 'Advanced floor cleaning robot with sweep/mop modes, self-wash dock, and humidifier refill.',
|
|
250
|
+
role: 'cleaning',
|
|
199
251
|
aliases: ['Robot Vacuum Cleaner S10', 'Robot Vacuum Cleaner S20'],
|
|
200
252
|
commands: [
|
|
201
|
-
{ command: 'startClean', parameter: '\'{"action":"sweep"|"sweep_mop","param":{"fanLevel":1-4,"waterLevel":1-2,"times":1-2639999}}\'', description: 'Begin a cleaning session' },
|
|
202
|
-
{ command: 'pause', parameter: '—', description: 'Pause cleaning' },
|
|
203
|
-
{ command: 'dock', parameter: '—', description: 'Return to dock' },
|
|
204
|
-
{ command: 'addWaterForHumi', parameter: '—', description: 'Refill the humidifier water tank' },
|
|
205
|
-
{ command: 'selfClean', parameter: '1 | 2 | 3', description: '1=wash mop | 2=dry | 3=terminate self-clean' },
|
|
206
|
-
{ command: 'setVolume', parameter: '0-100', description: 'Set voice volume' },
|
|
207
|
-
{ command: 'changeParam', parameter: '\'{"fanLevel":1-4,"waterLevel":1-2,"times":1-2639999}\'', description: 'Change parameters mid-run' },
|
|
253
|
+
{ 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}}'] },
|
|
254
|
+
{ command: 'pause', parameter: '—', description: 'Pause cleaning', idempotent: true },
|
|
255
|
+
{ command: 'dock', parameter: '—', description: 'Return to dock', idempotent: true },
|
|
256
|
+
{ command: 'addWaterForHumi', parameter: '—', description: 'Refill the humidifier water tank', idempotent: false },
|
|
257
|
+
{ command: 'selfClean', parameter: '1 | 2 | 3', description: '1=wash mop | 2=dry | 3=terminate self-clean', idempotent: false, exampleParams: ['1', '2', '3'] },
|
|
258
|
+
{ command: 'setVolume', parameter: '0-100', description: 'Set voice volume', idempotent: true, exampleParams: ['0', '50', '100'] },
|
|
259
|
+
{ 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}'] },
|
|
208
260
|
],
|
|
209
261
|
statusFields: ['workingStatus', 'onlineStatus', 'battery', 'taskType'],
|
|
210
262
|
},
|
|
211
263
|
{
|
|
212
264
|
type: 'Battery Circulator Fan',
|
|
213
265
|
category: 'physical',
|
|
266
|
+
description: 'Rechargeable table/floor fan with wind modes, speed control, night-light, and auto-off timer.',
|
|
267
|
+
role: 'fan',
|
|
214
268
|
aliases: ['Circulator Fan'],
|
|
215
269
|
commands: [
|
|
216
270
|
...onOffToggle,
|
|
217
|
-
{ command: 'setNightLightMode', parameter: 'off | 1 | 2', description: 'Night-light mode' },
|
|
218
|
-
{ command: 'setWindMode', parameter: 'direct | natural | sleep | baby', description: 'Wind mode' },
|
|
219
|
-
{ command: 'setWindSpeed', parameter: '1-100', description: 'Fan speed' },
|
|
220
|
-
{ command: 'closeDelay', parameter: 'seconds', description: 'Auto-off timer in seconds' },
|
|
271
|
+
{ command: 'setNightLightMode', parameter: 'off | 1 | 2', description: 'Night-light mode', idempotent: true, exampleParams: ['off', '1', '2'] },
|
|
272
|
+
{ command: 'setWindMode', parameter: 'direct | natural | sleep | baby', description: 'Wind mode', idempotent: true, exampleParams: ['natural', 'sleep'] },
|
|
273
|
+
{ command: 'setWindSpeed', parameter: '1-100', description: 'Fan speed', idempotent: true, exampleParams: ['50', '100'] },
|
|
274
|
+
{ command: 'closeDelay', parameter: 'seconds', description: 'Auto-off timer in seconds', idempotent: true, exampleParams: ['1800', '3600'] },
|
|
221
275
|
],
|
|
222
276
|
statusFields: ['mode', 'version', 'battery', 'power', 'nightStatus', 'oscillation', 'verticalOscillation', 'chargingStatus', 'fanSpeed'],
|
|
223
277
|
},
|
|
224
278
|
{
|
|
225
279
|
type: 'Blind Tilt',
|
|
226
280
|
category: 'physical',
|
|
281
|
+
description: 'Motorized tilt rod for horizontal blinds; controls slat angle (0=closed, 100=open).',
|
|
282
|
+
role: 'curtain',
|
|
227
283
|
commands: [
|
|
228
284
|
...onOff,
|
|
229
|
-
{ command: 'setPosition', parameter: '"<direction>;<angle>" (up|down; 0,2,...,100)', description: 'Tilt direction + angle (0=closed, 100=open)' },
|
|
230
|
-
{ command: 'fullyOpen', parameter: '—', description: 'Open fully' },
|
|
231
|
-
{ command: 'closeUp', parameter: '—', description: 'Close up' },
|
|
232
|
-
{ command: 'closeDown', parameter: '—', description: 'Close down' },
|
|
285
|
+
{ 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'] },
|
|
286
|
+
{ command: 'fullyOpen', parameter: '—', description: 'Open fully', idempotent: true },
|
|
287
|
+
{ command: 'closeUp', parameter: '—', description: 'Close up', idempotent: true },
|
|
288
|
+
{ command: 'closeDown', parameter: '—', description: 'Close down', idempotent: true },
|
|
233
289
|
],
|
|
234
290
|
statusFields: ['version', 'calibrate', 'group', 'moving', 'direction', 'slidePosition', 'battery'],
|
|
235
291
|
},
|
|
236
292
|
{
|
|
237
293
|
type: 'Roller Shade',
|
|
238
294
|
category: 'physical',
|
|
295
|
+
description: 'Motorized roller blind that moves to a set position (0=open, 100=closed).',
|
|
296
|
+
role: 'curtain',
|
|
239
297
|
commands: [
|
|
240
298
|
...onOff,
|
|
241
|
-
{ command: 'setPosition', parameter: '0-100 (0=open, 100=closed)', description: 'Move to a position' },
|
|
299
|
+
{ command: 'setPosition', parameter: '0-100 (0=open, 100=closed)', description: 'Move to a position', idempotent: true, exampleParams: ['0', '50', '100'] },
|
|
242
300
|
],
|
|
243
301
|
statusFields: ['slidePosition', 'battery', 'version', 'moving'],
|
|
244
302
|
},
|
|
245
303
|
{
|
|
246
304
|
type: 'Garage Door Opener',
|
|
247
305
|
category: 'physical',
|
|
248
|
-
|
|
306
|
+
description: 'Cloud-connected garage door controller; turnOn opens and turnOff closes the door.',
|
|
307
|
+
role: 'security',
|
|
308
|
+
commands: [
|
|
309
|
+
{ command: 'turnOn', parameter: '—', description: 'Open the garage door', idempotent: true, destructive: true, destructiveReason: 'Opens the garage door — anyone nearby can enter the space.' },
|
|
310
|
+
{ command: 'turnOff', parameter: '—', description: 'Close the garage door', idempotent: true, destructive: true, destructiveReason: 'Closes the garage door — verify no person or obstacle is in the way.' },
|
|
311
|
+
],
|
|
249
312
|
statusFields: ['switchStatus', 'version', 'online'],
|
|
250
313
|
},
|
|
251
314
|
{
|
|
252
315
|
type: 'Video Doorbell',
|
|
253
316
|
category: 'physical',
|
|
317
|
+
description: 'Wi-Fi video doorbell with motion detection enable/disable control.',
|
|
318
|
+
role: 'security',
|
|
254
319
|
commands: [
|
|
255
|
-
{ command: 'enableMotionDetection', parameter: '—', description: 'Enable motion detection' },
|
|
256
|
-
{ command: 'disableMotionDetection', parameter: '—', description: 'Disable motion detection' },
|
|
320
|
+
{ command: 'enableMotionDetection', parameter: '—', description: 'Enable motion detection', idempotent: true },
|
|
321
|
+
{ command: 'disableMotionDetection', parameter: '—', description: 'Disable motion detection', idempotent: true },
|
|
257
322
|
],
|
|
258
323
|
statusFields: ['battery', 'version'],
|
|
259
324
|
},
|
|
260
325
|
{
|
|
261
326
|
type: 'Keypad',
|
|
262
327
|
category: 'physical',
|
|
328
|
+
description: 'PIN-pad access controller that creates and deletes door passcodes for a Smart Lock.',
|
|
329
|
+
role: 'security',
|
|
263
330
|
aliases: ['Keypad Touch'],
|
|
264
331
|
commands: [
|
|
265
|
-
{ 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)' },
|
|
266
|
-
{ command: 'deleteKey', parameter: '\'{"id":<passcode_id>}\'', description: 'Delete a passcode (async; result via webhook)' },
|
|
332
|
+
{ 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, destructive: true, destructiveReason: 'Provisions a new access credential — anyone with this passcode can unlock the door.' },
|
|
333
|
+
{ command: 'deleteKey', parameter: '\'{"id":<passcode_id>}\'', description: 'Delete a passcode (async; result via webhook)', idempotent: true, destructive: true, destructiveReason: 'Permanently removes a passcode — the holder immediately loses door access.' },
|
|
267
334
|
],
|
|
268
335
|
statusFields: ['version'],
|
|
269
336
|
},
|
|
270
337
|
{
|
|
271
338
|
type: 'Candle Warmer Lamp',
|
|
272
339
|
category: 'physical',
|
|
340
|
+
description: 'Decorative candle-warmer lamp with adjustable brightness and color temperature.',
|
|
341
|
+
role: 'lighting',
|
|
273
342
|
commands: [
|
|
274
343
|
...onOffToggle,
|
|
275
|
-
{ command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage' },
|
|
276
|
-
{ command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)' },
|
|
344
|
+
{ command: 'setBrightness', parameter: '1-100', description: 'Set brightness percentage', idempotent: true, exampleParams: ['50', '80'] },
|
|
345
|
+
{ command: 'setColorTemperature', parameter: '2700-6500', description: 'Set color temperature (Kelvin)', idempotent: true, exampleParams: ['2700', '4000'] },
|
|
277
346
|
],
|
|
278
347
|
statusFields: ['power', 'brightness', 'colorTemperature', 'version'],
|
|
279
348
|
},
|
|
@@ -281,6 +350,9 @@ export const DEVICE_CATALOG = [
|
|
|
281
350
|
{
|
|
282
351
|
type: 'Meter',
|
|
283
352
|
category: 'physical',
|
|
353
|
+
description: 'Battery-powered temperature and humidity sensor; read-only, no control commands.',
|
|
354
|
+
role: 'sensor',
|
|
355
|
+
readOnly: true,
|
|
284
356
|
aliases: ['Meter Plus', 'MeterPro', 'MeterPro(CO2)', 'WoIOSensor', 'Hub 2'],
|
|
285
357
|
commands: [],
|
|
286
358
|
statusFields: ['temperature', 'humidity', 'CO2', 'battery', 'version'],
|
|
@@ -288,85 +360,162 @@ export const DEVICE_CATALOG = [
|
|
|
288
360
|
{
|
|
289
361
|
type: 'Motion Sensor',
|
|
290
362
|
category: 'physical',
|
|
363
|
+
description: 'PIR motion detector that reports movement and ambient brightness; read-only.',
|
|
364
|
+
role: 'sensor',
|
|
365
|
+
readOnly: true,
|
|
291
366
|
commands: [],
|
|
292
367
|
statusFields: ['battery', 'version', 'moveDetected', 'brightness', 'openState'],
|
|
293
368
|
},
|
|
294
369
|
{
|
|
295
370
|
type: 'Contact Sensor',
|
|
296
371
|
category: 'physical',
|
|
372
|
+
description: 'Door or window open/close sensor that also reports movement and brightness; read-only.',
|
|
373
|
+
role: 'sensor',
|
|
374
|
+
readOnly: true,
|
|
297
375
|
commands: [],
|
|
298
376
|
statusFields: ['battery', 'version', 'moveDetected', 'openState', 'brightness'],
|
|
299
377
|
},
|
|
300
378
|
{
|
|
301
379
|
type: 'Water Leak Detector',
|
|
302
380
|
category: 'physical',
|
|
381
|
+
description: 'Water sensor that reports leak status; read-only, no control commands.',
|
|
382
|
+
role: 'sensor',
|
|
383
|
+
readOnly: true,
|
|
303
384
|
commands: [],
|
|
304
385
|
statusFields: ['battery', 'version', 'status'],
|
|
305
386
|
},
|
|
387
|
+
// Status-only hub-class devices (no control commands)
|
|
388
|
+
{
|
|
389
|
+
type: 'Hub Mini',
|
|
390
|
+
category: 'physical',
|
|
391
|
+
description: 'IR hub that bridges BLE devices to the cloud and learns IR remotes; no direct control commands.',
|
|
392
|
+
role: 'hub',
|
|
393
|
+
readOnly: true,
|
|
394
|
+
aliases: ['Hub Mini2'],
|
|
395
|
+
commands: [],
|
|
396
|
+
statusFields: ['version'],
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
type: 'Hub 3',
|
|
400
|
+
category: 'physical',
|
|
401
|
+
description: 'Wi-Fi hub with built-in temperature, humidity, and light sensors; manages local BLE devices.',
|
|
402
|
+
role: 'hub',
|
|
403
|
+
readOnly: true,
|
|
404
|
+
commands: [],
|
|
405
|
+
statusFields: ['version', 'temperature', 'humidity', 'lightLevel'],
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
type: 'AI Hub',
|
|
409
|
+
category: 'physical',
|
|
410
|
+
description: 'Advanced hub with AI-based automations; bridges BLE devices to the cloud; read-only status.',
|
|
411
|
+
role: 'hub',
|
|
412
|
+
readOnly: true,
|
|
413
|
+
commands: [],
|
|
414
|
+
statusFields: ['version'],
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
type: 'Home Climate Panel',
|
|
418
|
+
category: 'physical',
|
|
419
|
+
description: 'Wall-mounted display showing temperature and humidity; sensor-only, no control.',
|
|
420
|
+
role: 'climate',
|
|
421
|
+
readOnly: true,
|
|
422
|
+
commands: [],
|
|
423
|
+
statusFields: ['temperature', 'humidity', 'version'],
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
type: 'Wallet Finder Card',
|
|
427
|
+
category: 'physical',
|
|
428
|
+
description: 'Slim Bluetooth tracker card for locating wallets; reports battery status only.',
|
|
429
|
+
role: 'sensor',
|
|
430
|
+
readOnly: true,
|
|
431
|
+
commands: [],
|
|
432
|
+
statusFields: ['battery', 'version'],
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
type: 'Outdoor Spotlight Cam',
|
|
436
|
+
category: 'physical',
|
|
437
|
+
description: 'Battery-powered outdoor security camera with spotlight; status-only via cloud API.',
|
|
438
|
+
role: 'security',
|
|
439
|
+
readOnly: true,
|
|
440
|
+
commands: [],
|
|
441
|
+
statusFields: ['battery', 'version'],
|
|
442
|
+
},
|
|
306
443
|
// ---------- Virtual IR remotes ----------
|
|
307
444
|
{
|
|
308
445
|
type: 'Air Conditioner',
|
|
309
446
|
category: 'ir',
|
|
447
|
+
description: 'IR-controlled air conditioner with on/off and full HVAC parameter control (mode, fan, temp).',
|
|
448
|
+
role: 'climate',
|
|
310
449
|
commands: [
|
|
311
450
|
...onOff,
|
|
312
|
-
{ 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' },
|
|
451
|
+
{ 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'] },
|
|
313
452
|
],
|
|
314
453
|
},
|
|
315
454
|
{
|
|
316
455
|
type: 'TV',
|
|
317
456
|
category: 'ir',
|
|
457
|
+
description: 'IR-controlled television or streaming device with channel, volume, and power commands.',
|
|
458
|
+
role: 'media',
|
|
318
459
|
aliases: ['IPTV', 'Streamer', 'Set Top Box'],
|
|
319
460
|
commands: [
|
|
320
461
|
...onOff,
|
|
321
|
-
{ command: 'SetChannel', parameter: '1-999 (channel number)', description: 'Switch to a specific channel' },
|
|
322
|
-
{ command: 'volumeAdd', parameter: '—', description: 'Volume up' },
|
|
323
|
-
{ command: 'volumeSub', parameter: '—', description: 'Volume down' },
|
|
324
|
-
{ command: 'channelAdd', parameter: '—', description: 'Channel up' },
|
|
325
|
-
{ command: 'channelSub', parameter: '—', description: 'Channel down' },
|
|
462
|
+
{ command: 'SetChannel', parameter: '1-999 (channel number)', description: 'Switch to a specific channel', idempotent: true, exampleParams: ['1', '15'] },
|
|
463
|
+
{ command: 'volumeAdd', parameter: '—', description: 'Volume up', idempotent: false },
|
|
464
|
+
{ command: 'volumeSub', parameter: '—', description: 'Volume down', idempotent: false },
|
|
465
|
+
{ command: 'channelAdd', parameter: '—', description: 'Channel up', idempotent: false },
|
|
466
|
+
{ command: 'channelSub', parameter: '—', description: 'Channel down', idempotent: false },
|
|
326
467
|
],
|
|
327
468
|
},
|
|
328
469
|
{
|
|
329
470
|
type: 'DVD',
|
|
330
471
|
category: 'ir',
|
|
472
|
+
description: 'IR-controlled disc player or speaker with playback, track navigation, and volume commands.',
|
|
473
|
+
role: 'media',
|
|
331
474
|
aliases: ['Speaker'],
|
|
332
475
|
commands: [
|
|
333
476
|
...onOff,
|
|
334
|
-
{ command: 'setMute', parameter: '—', description: 'Toggle mute' },
|
|
335
|
-
{ command: 'FastForward', parameter: '—', description: 'Fast forward' },
|
|
336
|
-
{ command: 'Rewind', parameter: '—', description: 'Rewind' },
|
|
337
|
-
{ command: 'Next', parameter: '—', description: 'Next track' },
|
|
338
|
-
{ command: 'Previous', parameter: '—', description: 'Previous track' },
|
|
339
|
-
{ command: 'Pause', parameter: '—', description: 'Pause' },
|
|
340
|
-
{ command: 'Play', parameter: '—', description: 'Play' },
|
|
341
|
-
{ command: 'Stop', parameter: '—', description: 'Stop' },
|
|
342
|
-
{ command: 'volumeAdd', parameter: '—', description: 'Volume up' },
|
|
343
|
-
{ command: 'volumeSub', parameter: '—', description: 'Volume down' },
|
|
477
|
+
{ command: 'setMute', parameter: '—', description: 'Toggle mute', idempotent: false },
|
|
478
|
+
{ command: 'FastForward', parameter: '—', description: 'Fast forward', idempotent: false },
|
|
479
|
+
{ command: 'Rewind', parameter: '—', description: 'Rewind', idempotent: false },
|
|
480
|
+
{ command: 'Next', parameter: '—', description: 'Next track', idempotent: false },
|
|
481
|
+
{ command: 'Previous', parameter: '—', description: 'Previous track', idempotent: false },
|
|
482
|
+
{ command: 'Pause', parameter: '—', description: 'Pause', idempotent: true },
|
|
483
|
+
{ command: 'Play', parameter: '—', description: 'Play', idempotent: true },
|
|
484
|
+
{ command: 'Stop', parameter: '—', description: 'Stop', idempotent: true },
|
|
485
|
+
{ command: 'volumeAdd', parameter: '—', description: 'Volume up', idempotent: false },
|
|
486
|
+
{ command: 'volumeSub', parameter: '—', description: 'Volume down', idempotent: false },
|
|
344
487
|
],
|
|
345
488
|
},
|
|
346
489
|
{
|
|
347
490
|
type: 'Fan',
|
|
348
491
|
category: 'ir',
|
|
492
|
+
description: 'IR-controlled fan with on/off, swing, timer, and speed preset commands.',
|
|
493
|
+
role: 'fan',
|
|
349
494
|
commands: [
|
|
350
495
|
...onOff,
|
|
351
|
-
{ command: 'swing', parameter: '—', description: 'Toggle swing' },
|
|
352
|
-
{ command: 'timer', parameter: '—', description: 'Toggle timer' },
|
|
353
|
-
{ command: 'lowSpeed', parameter: '—', description: 'Low speed' },
|
|
354
|
-
{ command: 'middleSpeed', parameter: '—', description: 'Medium speed' },
|
|
355
|
-
{ command: 'highSpeed', parameter: '—', description: 'High speed' },
|
|
496
|
+
{ command: 'swing', parameter: '—', description: 'Toggle swing', idempotent: false },
|
|
497
|
+
{ command: 'timer', parameter: '—', description: 'Toggle timer', idempotent: false },
|
|
498
|
+
{ command: 'lowSpeed', parameter: '—', description: 'Low speed', idempotent: true },
|
|
499
|
+
{ command: 'middleSpeed', parameter: '—', description: 'Medium speed', idempotent: true },
|
|
500
|
+
{ command: 'highSpeed', parameter: '—', description: 'High speed', idempotent: true },
|
|
356
501
|
],
|
|
357
502
|
},
|
|
358
503
|
{
|
|
359
504
|
type: 'Light',
|
|
360
505
|
category: 'ir',
|
|
506
|
+
description: 'IR-controlled light fixture with on/off and relative brightness adjustment commands.',
|
|
507
|
+
role: 'lighting',
|
|
361
508
|
commands: [
|
|
362
509
|
...onOff,
|
|
363
|
-
{ command: 'brightnessUp', parameter: '—', description: 'Brightness up' },
|
|
364
|
-
{ command: 'brightnessDown', parameter: '—', description: 'Brightness down' },
|
|
510
|
+
{ command: 'brightnessUp', parameter: '—', description: 'Brightness up', idempotent: false },
|
|
511
|
+
{ command: 'brightnessDown', parameter: '—', description: 'Brightness down', idempotent: false },
|
|
365
512
|
],
|
|
366
513
|
},
|
|
367
514
|
{
|
|
368
515
|
type: 'Others',
|
|
369
516
|
category: 'ir',
|
|
517
|
+
description: 'Catch-all for custom IR remotes with user-defined button names learned by a Hub.',
|
|
518
|
+
role: 'other',
|
|
370
519
|
commands: [
|
|
371
520
|
{ command: '<buttonName>', parameter: '—', description: 'User-defined custom IR button (requires --type customize)', commandType: 'customize' },
|
|
372
521
|
],
|
|
@@ -378,14 +527,135 @@ export function findCatalogEntry(query) {
|
|
|
378
527
|
if (!q)
|
|
379
528
|
return null;
|
|
380
529
|
const names = (e) => [e.type, ...(e.aliases ?? [])];
|
|
381
|
-
const
|
|
530
|
+
const catalog = getEffectiveCatalog();
|
|
531
|
+
const exact = catalog.find((e) => names(e).some((n) => n.toLowerCase() === q));
|
|
382
532
|
if (exact)
|
|
383
533
|
return exact;
|
|
384
|
-
const matches =
|
|
534
|
+
const matches = catalog.filter((e) => names(e).some((n) => n.toLowerCase().includes(q)));
|
|
385
535
|
if (matches.length === 0)
|
|
386
536
|
return null;
|
|
387
537
|
if (matches.length === 1)
|
|
388
538
|
return matches[0];
|
|
389
539
|
return matches;
|
|
390
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Pick up to 3 non-destructive, idempotent commands an agent can safely invoke
|
|
543
|
+
* to explore or exercise a device. Used by `devices describe --json` to hint
|
|
544
|
+
* at concrete next steps.
|
|
545
|
+
*/
|
|
546
|
+
export function suggestedActions(entry) {
|
|
547
|
+
const safe = entry.commands.filter((c) => c.idempotent === true && !c.destructive && c.commandType !== 'customize');
|
|
548
|
+
const picks = [];
|
|
549
|
+
const seen = new Set();
|
|
550
|
+
for (const c of safe) {
|
|
551
|
+
if (seen.has(c.command))
|
|
552
|
+
continue;
|
|
553
|
+
seen.add(c.command);
|
|
554
|
+
picks.push(c);
|
|
555
|
+
if (picks.length >= 3)
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
return picks.map((c) => ({
|
|
559
|
+
command: c.command,
|
|
560
|
+
parameter: c.exampleParams?.[0],
|
|
561
|
+
description: c.description,
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
564
|
+
// ---- Overlay loader ----------------------------------------------------
|
|
565
|
+
//
|
|
566
|
+
// Users can drop a `~/.switchbot/catalog.json` file to override or extend
|
|
567
|
+
// the built-in catalog without waiting on a CLI release. The overlay is a
|
|
568
|
+
// list of DeviceCatalogEntry objects; each entry matches on `type`:
|
|
569
|
+
// - Entry with `type` matching a built-in replaces that built-in entry.
|
|
570
|
+
// - Entry with a new `type` is appended.
|
|
571
|
+
// - Entry with `{ type: "X", remove: true }` deletes the built-in.
|
|
572
|
+
//
|
|
573
|
+
// The overlay is loaded once per process and cached. Malformed JSON or
|
|
574
|
+
// files that don't match the expected shape are ignored (with a warning
|
|
575
|
+
// to stderr in verbose mode).
|
|
576
|
+
import fs from 'node:fs';
|
|
577
|
+
import path from 'node:path';
|
|
578
|
+
import os from 'node:os';
|
|
579
|
+
function overlayFilePath() {
|
|
580
|
+
return path.join(os.homedir(), '.switchbot', 'catalog.json');
|
|
581
|
+
}
|
|
582
|
+
export function getCatalogOverlayPath() {
|
|
583
|
+
return overlayFilePath();
|
|
584
|
+
}
|
|
585
|
+
/** Read the overlay file. Never throws — returns `error` on bad files. */
|
|
586
|
+
export function loadCatalogOverlay() {
|
|
587
|
+
const file = overlayFilePath();
|
|
588
|
+
if (!fs.existsSync(file)) {
|
|
589
|
+
return { path: file, exists: false, entries: [] };
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
593
|
+
const parsed = JSON.parse(raw);
|
|
594
|
+
if (!Array.isArray(parsed)) {
|
|
595
|
+
return {
|
|
596
|
+
path: file,
|
|
597
|
+
exists: true,
|
|
598
|
+
entries: [],
|
|
599
|
+
error: 'overlay must be a JSON array of device catalog entries',
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const entries = [];
|
|
603
|
+
for (const item of parsed) {
|
|
604
|
+
if (!item || typeof item !== 'object' || typeof item.type !== 'string') {
|
|
605
|
+
return {
|
|
606
|
+
path: file,
|
|
607
|
+
exists: true,
|
|
608
|
+
entries: [],
|
|
609
|
+
error: 'every overlay entry must be an object with a string `type`',
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
entries.push(item);
|
|
613
|
+
}
|
|
614
|
+
return { path: file, exists: true, entries };
|
|
615
|
+
}
|
|
616
|
+
catch (err) {
|
|
617
|
+
return {
|
|
618
|
+
path: file,
|
|
619
|
+
exists: true,
|
|
620
|
+
entries: [],
|
|
621
|
+
error: err instanceof Error ? err.message : String(err),
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
let overlayCache = null;
|
|
626
|
+
function overlayOnce() {
|
|
627
|
+
if (overlayCache === null)
|
|
628
|
+
overlayCache = loadCatalogOverlay();
|
|
629
|
+
return overlayCache;
|
|
630
|
+
}
|
|
631
|
+
/** Clear the overlay cache (test helper; also useful for `catalog refresh`). */
|
|
632
|
+
export function resetCatalogOverlayCache() {
|
|
633
|
+
overlayCache = null;
|
|
634
|
+
}
|
|
635
|
+
/** Merge built-in catalog with the on-disk overlay. */
|
|
636
|
+
export function getEffectiveCatalog() {
|
|
637
|
+
const overlay = overlayOnce();
|
|
638
|
+
if (!overlay.entries.length)
|
|
639
|
+
return DEVICE_CATALOG;
|
|
640
|
+
const byType = new Map();
|
|
641
|
+
for (const e of DEVICE_CATALOG)
|
|
642
|
+
byType.set(e.type, e);
|
|
643
|
+
for (const entry of overlay.entries) {
|
|
644
|
+
if (entry.remove) {
|
|
645
|
+
byType.delete(entry.type);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
const existing = byType.get(entry.type);
|
|
649
|
+
if (existing) {
|
|
650
|
+
byType.set(entry.type, { ...existing, ...entry });
|
|
651
|
+
}
|
|
652
|
+
else if (entry.category && entry.commands) {
|
|
653
|
+
// New entry — require the fields the renderer needs. Missing fields
|
|
654
|
+
// would make the new entry crash later, so skip silently rather than
|
|
655
|
+
// ship half-valid data to the user.
|
|
656
|
+
byType.set(entry.type, entry);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return Array.from(byType.values());
|
|
660
|
+
}
|
|
391
661
|
//# sourceMappingURL=catalog.js.map
|