@switchbot/openapi-cli 1.0.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/LICENSE +21 -0
- package/README.md +278 -0
- package/dist/api/client.d.ts +12 -0
- package/dist/api/client.js +95 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +21 -0
- package/dist/auth.js.map +1 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +259 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +37 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/devices.d.ts +2 -0
- package/dist/commands/devices.js +402 -0
- package/dist/commands/devices.js.map +1 -0
- package/dist/commands/scenes.d.ts +2 -0
- package/dist/commands/scenes.js +57 -0
- package/dist/commands/scenes.js.map +1 -0
- package/dist/commands/webhook.d.ts +2 -0
- package/dist/commands/webhook.js +167 -0
- package/dist/commands/webhook.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/devices/catalog.d.ts +21 -0
- package/dist/devices/catalog.js +391 -0
- package/dist/devices/catalog.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/flags.d.ts +10 -0
- package/dist/utils/flags.js +34 -0
- package/dist/utils/flags.js.map +1 -0
- package/dist/utils/output.d.ts +5 -0
- package/dist/utils/output.js +78 -0
- package/dist/utils/output.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { createClient } from '../api/client.js';
|
|
2
|
+
import { printTable, printKeyValue, printJson, isJsonMode, handleError } from '../utils/output.js';
|
|
3
|
+
import { DEVICE_CATALOG, findCatalogEntry } from '../devices/catalog.js';
|
|
4
|
+
export function registerDevicesCommand(program) {
|
|
5
|
+
const devices = program
|
|
6
|
+
.command('devices')
|
|
7
|
+
.description('Manage and control SwitchBot devices')
|
|
8
|
+
.addHelpText('after', `
|
|
9
|
+
Typical workflow:
|
|
10
|
+
1. Discover your devices → switchbot devices list
|
|
11
|
+
2. Describe a specific device → switchbot devices describe <id>
|
|
12
|
+
3. Or look up a type offline → switchbot devices types
|
|
13
|
+
switchbot devices commands <type>
|
|
14
|
+
4. Send a command → switchbot devices command <id> <cmd> [param]
|
|
15
|
+
|
|
16
|
+
Online subcommands (hit the SwitchBot API):
|
|
17
|
+
list List all physical + IR remote devices on your account
|
|
18
|
+
status Query a device's real-time status values
|
|
19
|
+
command Send a control command (turnOn, setColor, setAll, startClean, …)
|
|
20
|
+
describe Show one device's metadata + its supported commands + status fields
|
|
21
|
+
|
|
22
|
+
Offline subcommands (built-in catalog, no API call):
|
|
23
|
+
types List every device type this CLI knows about
|
|
24
|
+
commands Show commands + parameter formats + status fields for a type
|
|
25
|
+
|
|
26
|
+
Run any subcommand with --help for its own flags and examples.
|
|
27
|
+
`);
|
|
28
|
+
// switchbot devices list
|
|
29
|
+
devices
|
|
30
|
+
.command('list')
|
|
31
|
+
.description('List all physical devices and IR remote devices on the account')
|
|
32
|
+
.addHelpText('after', `
|
|
33
|
+
Output columns: deviceId, deviceName, type, controlType, family, roomID, room, hub, cloud
|
|
34
|
+
|
|
35
|
+
type - physical deviceType (e.g. "Bot", "Curtain") or "[IR] <remoteType>"
|
|
36
|
+
controlType - functional classification from the API (e.g. "Bot", "Switch",
|
|
37
|
+
"TV") — may differ from 'type' and groups devices by behavior
|
|
38
|
+
family - home/family name (IR remotes inherit this from their bound Hub)
|
|
39
|
+
roomID - internal room identifier (IR remotes inherit from their
|
|
40
|
+
bound Hub; — when unassigned/unknown)
|
|
41
|
+
room - room name this device is assigned to (IR remotes inherit from
|
|
42
|
+
Hub; — when unassigned/unknown)
|
|
43
|
+
hub - "—" when the device is its own hub or hubDeviceId is empty
|
|
44
|
+
cloud - ✓/✗: whether cloud service is enabled (— for IR remotes)
|
|
45
|
+
|
|
46
|
+
controlType, family/room, and roomID require the 'src: OpenClaw' header, which
|
|
47
|
+
this CLI always sends. (IR family/room inheritance is computed client-side for
|
|
48
|
+
the table; --json returns the raw API body unchanged.)
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
$ switchbot devices list
|
|
52
|
+
$ switchbot devices list --json | jq '.deviceList[] | select(.familyName == "家里")'
|
|
53
|
+
$ switchbot devices list --json | jq '[.deviceList[], .infraredRemoteList[]] | group_by(.familyName)'
|
|
54
|
+
`)
|
|
55
|
+
.action(async () => {
|
|
56
|
+
try {
|
|
57
|
+
const client = createClient();
|
|
58
|
+
const res = await client.get('/v1.1/devices');
|
|
59
|
+
const { deviceList, infraredRemoteList } = res.data.body;
|
|
60
|
+
if (isJsonMode()) {
|
|
61
|
+
printJson(res.data.body);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const hubLocation = buildHubLocationMap(deviceList);
|
|
65
|
+
const rows = [];
|
|
66
|
+
for (const d of deviceList) {
|
|
67
|
+
rows.push([
|
|
68
|
+
d.deviceId,
|
|
69
|
+
d.deviceName,
|
|
70
|
+
d.deviceType || '—',
|
|
71
|
+
d.controlType || '—',
|
|
72
|
+
d.familyName || '—',
|
|
73
|
+
d.roomID || '—',
|
|
74
|
+
d.roomName || '—',
|
|
75
|
+
!d.hubDeviceId || d.hubDeviceId === '000000000000' ? '—' : d.hubDeviceId,
|
|
76
|
+
d.enableCloudService,
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
for (const d of infraredRemoteList) {
|
|
80
|
+
const inherited = hubLocation.get(d.hubDeviceId);
|
|
81
|
+
rows.push([
|
|
82
|
+
d.deviceId,
|
|
83
|
+
d.deviceName,
|
|
84
|
+
`[IR] ${d.remoteType}`,
|
|
85
|
+
d.controlType || '—',
|
|
86
|
+
inherited?.family || '—',
|
|
87
|
+
inherited?.roomID || '—',
|
|
88
|
+
inherited?.room || '—',
|
|
89
|
+
d.hubDeviceId,
|
|
90
|
+
null,
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
if (rows.length === 0) {
|
|
94
|
+
console.log('No devices found');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
printTable(['deviceId', 'deviceName', 'type', 'controlType', 'family', 'roomID', 'room', 'hub', 'cloud'], rows);
|
|
98
|
+
console.log(`\nTotal: ${deviceList.length} physical device(s), ${infraredRemoteList.length} IR remote device(s)`);
|
|
99
|
+
console.log(`Tip: 'switchbot devices describe <deviceId>' shows a device's supported commands.`);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
handleError(error);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// switchbot devices status <deviceId>
|
|
106
|
+
devices
|
|
107
|
+
.command('status')
|
|
108
|
+
.description('Query the real-time status of a specific device')
|
|
109
|
+
.argument('<deviceId>', 'Device ID from "devices list" (physical devices only; IR remotes have no status)')
|
|
110
|
+
.addHelpText('after', `
|
|
111
|
+
Returned fields vary by device type — e.g. Bot returns power/battery, Meter
|
|
112
|
+
returns temperature/humidity/battery, Curtain returns slidePosition/moving,
|
|
113
|
+
Color Bulb returns brightness/color/colorTemperature, etc.
|
|
114
|
+
|
|
115
|
+
To see exactly which status fields a given type returns BEFORE calling the
|
|
116
|
+
API, use the offline catalog:
|
|
117
|
+
|
|
118
|
+
switchbot devices commands <type> (prints the "Status fields" section)
|
|
119
|
+
|
|
120
|
+
IR remote devices cannot be queried — the SwitchBot API returns no status
|
|
121
|
+
channel for them. Use 'devices list' to confirm a deviceId is a physical
|
|
122
|
+
device (not in the 'infraredRemoteList').
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
$ switchbot devices status ABC123DEF456
|
|
126
|
+
$ switchbot devices status ABC123DEF456 --json
|
|
127
|
+
$ switchbot devices status ABC123DEF456 --json | jq '.battery'
|
|
128
|
+
`)
|
|
129
|
+
.action(async (deviceId) => {
|
|
130
|
+
try {
|
|
131
|
+
const client = createClient();
|
|
132
|
+
const res = await client.get(`/v1.1/devices/${deviceId}/status`);
|
|
133
|
+
if (isJsonMode()) {
|
|
134
|
+
printJson(res.data.body);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
printKeyValue(res.data.body);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
handleError(error);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
// switchbot devices command <deviceId> <command> [parameter]
|
|
144
|
+
devices
|
|
145
|
+
.command('command')
|
|
146
|
+
.description('Send a control command to a device')
|
|
147
|
+
.argument('<deviceId>', 'Target device ID from "devices list"')
|
|
148
|
+
.argument('<cmd>', 'Command name, e.g. turnOn, turnOff, setColor, setBrightness, setAll, startClean')
|
|
149
|
+
.argument('[parameter]', 'Command parameter. Omit for commands like turnOn/turnOff (defaults to "default"). Format depends on the command (see below).')
|
|
150
|
+
.option('--type <commandType>', 'Command type: "command" for built-in commands (default), "customize" for user-defined IR buttons', 'command')
|
|
151
|
+
.addHelpText('after', `
|
|
152
|
+
────────────────────────────────────────────────────────────────────────
|
|
153
|
+
For the full list of commands a specific device supports — and their
|
|
154
|
+
exact parameter formats — run:
|
|
155
|
+
|
|
156
|
+
switchbot devices commands <type> (e.g. Bot, Curtain, "Smart Lock")
|
|
157
|
+
|
|
158
|
+
The catalog is the authoritative per-device reference. This page only
|
|
159
|
+
covers the generic mechanics that apply to every device.
|
|
160
|
+
────────────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
Rules:
|
|
163
|
+
• Command names are CASE-SENSITIVE (e.g. SetChannel, FastForward, volumeAdd).
|
|
164
|
+
• Quote any parameter containing ':' ',' ';' or '{ }' to protect it from the shell.
|
|
165
|
+
• The parameter is parsed as JSON when possible; otherwise passed through as a string.
|
|
166
|
+
• Omit the parameter for no-arg commands — it auto-defaults to "default".
|
|
167
|
+
• Use --type customize to trigger a user-defined IR button by name.
|
|
168
|
+
|
|
169
|
+
Generic parameter shapes (see 'devices commands <type>' for which one applies):
|
|
170
|
+
|
|
171
|
+
(none) turnOn, turnOff, toggle, press, play, pause, …
|
|
172
|
+
<integer> setBrightness 75, setColorTemperature 4000, SetChannel 15
|
|
173
|
+
<R:G:B> setColor "255:0:0"
|
|
174
|
+
<direction;angle> setPosition "up;60" (Blind Tilt)
|
|
175
|
+
<a,b,c,…> setAll "26,1,3,on" (IR AC)
|
|
176
|
+
<json object> startClean '{"action":"sweep","param":{"fanLevel":2,"times":1}}'
|
|
177
|
+
|
|
178
|
+
Common errors:
|
|
179
|
+
160 command not supported by this device
|
|
180
|
+
161 device offline (BLE devices need a Hub bridge)
|
|
181
|
+
171 hub offline
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
$ switchbot devices command ABC123 turnOn
|
|
185
|
+
$ switchbot devices command ABC123 setColor "255:0:0"
|
|
186
|
+
$ switchbot devices command ABC123 setAll "26,1,3,on"
|
|
187
|
+
$ switchbot devices command ABC123 startClean '{"action":"sweep","param":{"fanLevel":2,"times":1}}'
|
|
188
|
+
$ switchbot devices command ABC123 "MyButton" --type customize
|
|
189
|
+
`)
|
|
190
|
+
.action(async (deviceId, cmd, parameter, options) => {
|
|
191
|
+
try {
|
|
192
|
+
const client = createClient();
|
|
193
|
+
// parameter may be a JSON object string (e.g. S10 startClean) or a plain string
|
|
194
|
+
let parsedParam = parameter ?? 'default';
|
|
195
|
+
if (parameter) {
|
|
196
|
+
try {
|
|
197
|
+
parsedParam = JSON.parse(parameter);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// keep as string
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const body = {
|
|
204
|
+
command: cmd,
|
|
205
|
+
parameter: parsedParam,
|
|
206
|
+
commandType: options.type,
|
|
207
|
+
};
|
|
208
|
+
const res = await client.post(`/v1.1/devices/${deviceId}/commands`, body);
|
|
209
|
+
if (isJsonMode()) {
|
|
210
|
+
printJson(res.data.body);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
console.log(`✓ Command sent: ${cmd}`);
|
|
214
|
+
if (res.data.body && typeof res.data.body === 'object' && Object.keys(res.data.body).length > 0) {
|
|
215
|
+
printKeyValue(res.data.body);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
handleError(error);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
// switchbot devices types
|
|
223
|
+
devices
|
|
224
|
+
.command('types')
|
|
225
|
+
.description('List all device types known to this CLI (offline reference, no API call)')
|
|
226
|
+
.addHelpText('after', `
|
|
227
|
+
Output columns: type, category (physical | ir), commands, aliases
|
|
228
|
+
Use 'switchbot devices commands <type>' to see what a given type supports.
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
$ switchbot devices types
|
|
232
|
+
$ switchbot devices types --json
|
|
233
|
+
`)
|
|
234
|
+
.action(() => {
|
|
235
|
+
if (isJsonMode()) {
|
|
236
|
+
printJson(DEVICE_CATALOG);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const rows = DEVICE_CATALOG.map((e) => [
|
|
240
|
+
e.type,
|
|
241
|
+
e.category,
|
|
242
|
+
String(e.commands.length),
|
|
243
|
+
(e.aliases ?? []).join(', ') || '—',
|
|
244
|
+
]);
|
|
245
|
+
printTable(['type', 'category', 'commands', 'aliases'], rows);
|
|
246
|
+
console.log(`\nTotal: ${DEVICE_CATALOG.length} device type(s)`);
|
|
247
|
+
});
|
|
248
|
+
// switchbot devices commands <type>
|
|
249
|
+
devices
|
|
250
|
+
.command('commands')
|
|
251
|
+
.description('Show supported commands, parameter formats, and status fields for a device type')
|
|
252
|
+
.argument('<type...>', 'Device type name or alias (case-insensitive, partial matches supported; multi-word types do not need quoting)')
|
|
253
|
+
.addHelpText('after', `
|
|
254
|
+
This is the authoritative per-device reference — every command the CLI
|
|
255
|
+
can send to a given type, its parameter format, and the status fields
|
|
256
|
+
'devices status' will return. Runs fully offline (no API call).
|
|
257
|
+
|
|
258
|
+
Multi-word types can be passed either quoted or unquoted — both work:
|
|
259
|
+
$ switchbot devices commands "Air Conditioner"
|
|
260
|
+
$ switchbot devices commands Air Conditioner
|
|
261
|
+
$ switchbot devices commands "Smart Lock"
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
$ switchbot devices commands Bot
|
|
265
|
+
$ switchbot devices commands curtain
|
|
266
|
+
$ switchbot devices commands Robot --json
|
|
267
|
+
`)
|
|
268
|
+
.action((typeParts) => {
|
|
269
|
+
const type = typeParts.join(' ');
|
|
270
|
+
const match = findCatalogEntry(type);
|
|
271
|
+
if (!match) {
|
|
272
|
+
console.error(`No device type matches "${type}".`);
|
|
273
|
+
console.error(`Try 'switchbot devices types' to see the full list.`);
|
|
274
|
+
process.exit(2);
|
|
275
|
+
}
|
|
276
|
+
if (Array.isArray(match)) {
|
|
277
|
+
console.error(`"${type}" matches multiple types. Be more specific:`);
|
|
278
|
+
for (const m of match)
|
|
279
|
+
console.error(` • ${m.type}`);
|
|
280
|
+
process.exit(2);
|
|
281
|
+
}
|
|
282
|
+
if (isJsonMode()) {
|
|
283
|
+
printJson(match);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
renderCatalogEntry(match);
|
|
287
|
+
});
|
|
288
|
+
// switchbot devices describe <deviceId>
|
|
289
|
+
devices
|
|
290
|
+
.command('describe')
|
|
291
|
+
.description('Describe a device by ID: metadata + supported commands + status fields (1 API call)')
|
|
292
|
+
.argument('<deviceId>', 'Target device ID from "devices list"')
|
|
293
|
+
.addHelpText('after', `
|
|
294
|
+
Makes a single GET /v1.1/devices call to look up the device's type, then
|
|
295
|
+
prints its metadata alongside the matching catalog entry (supported
|
|
296
|
+
commands + parameter formats + status field names).
|
|
297
|
+
|
|
298
|
+
Does NOT fetch live status values. Use 'switchbot devices status <id>' for that.
|
|
299
|
+
|
|
300
|
+
Examples:
|
|
301
|
+
$ switchbot devices describe ABC123DEF456
|
|
302
|
+
$ switchbot devices describe ABC123DEF456 --json
|
|
303
|
+
`)
|
|
304
|
+
.action(async (deviceId) => {
|
|
305
|
+
try {
|
|
306
|
+
const client = createClient();
|
|
307
|
+
const res = await client.get('/v1.1/devices');
|
|
308
|
+
const { deviceList, infraredRemoteList } = res.data.body;
|
|
309
|
+
const physical = deviceList.find((d) => d.deviceId === deviceId);
|
|
310
|
+
const ir = infraredRemoteList.find((d) => d.deviceId === deviceId);
|
|
311
|
+
if (!physical && !ir) {
|
|
312
|
+
console.error(`No device with id "${deviceId}" found on this account.`);
|
|
313
|
+
console.error(`Try 'switchbot devices list' to see the full list.`);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
const typeName = physical ? (physical.deviceType ?? '') : ir.remoteType;
|
|
317
|
+
const match = typeName ? findCatalogEntry(typeName) : null;
|
|
318
|
+
const catalogEntry = !match || Array.isArray(match) ? null : match;
|
|
319
|
+
if (isJsonMode()) {
|
|
320
|
+
printJson({
|
|
321
|
+
device: physical ?? ir,
|
|
322
|
+
controlType: (physical?.controlType ?? ir?.controlType) ?? null,
|
|
323
|
+
catalog: catalogEntry,
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (physical) {
|
|
328
|
+
printKeyValue({
|
|
329
|
+
deviceId: physical.deviceId,
|
|
330
|
+
deviceName: physical.deviceName,
|
|
331
|
+
deviceType: physical.deviceType || '—',
|
|
332
|
+
controlType: physical.controlType || '—',
|
|
333
|
+
family: physical.familyName || '—',
|
|
334
|
+
roomID: physical.roomID || '—',
|
|
335
|
+
room: physical.roomName || '—',
|
|
336
|
+
hub: !physical.hubDeviceId || physical.hubDeviceId === '000000000000' ? '—' : physical.hubDeviceId,
|
|
337
|
+
cloudService: physical.enableCloudService,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
else if (ir) {
|
|
341
|
+
const inherited = buildHubLocationMap(deviceList).get(ir.hubDeviceId);
|
|
342
|
+
printKeyValue({
|
|
343
|
+
deviceId: ir.deviceId,
|
|
344
|
+
deviceName: ir.deviceName,
|
|
345
|
+
remoteType: ir.remoteType,
|
|
346
|
+
controlType: ir.controlType || '—',
|
|
347
|
+
family: inherited?.family || '—',
|
|
348
|
+
roomID: inherited?.roomID || '—',
|
|
349
|
+
room: inherited?.room || '—',
|
|
350
|
+
hub: ir.hubDeviceId || '—',
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
console.log('');
|
|
354
|
+
if (!catalogEntry) {
|
|
355
|
+
console.log(`(Type "${typeName}" is not in the built-in catalog — no command reference available.)`);
|
|
356
|
+
console.log(`Send custom IR buttons with: switchbot devices command ${deviceId} "<buttonName>" --type customize`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
renderCatalogEntry(catalogEntry);
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
handleError(error);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function buildHubLocationMap(deviceList) {
|
|
367
|
+
const map = new Map();
|
|
368
|
+
for (const d of deviceList) {
|
|
369
|
+
if (!d.deviceId)
|
|
370
|
+
continue;
|
|
371
|
+
map.set(d.deviceId, {
|
|
372
|
+
family: d.familyName ?? undefined,
|
|
373
|
+
room: d.roomName ?? undefined,
|
|
374
|
+
roomID: d.roomID ?? undefined,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return map;
|
|
378
|
+
}
|
|
379
|
+
function renderCatalogEntry(entry) {
|
|
380
|
+
console.log(`Type: ${entry.type}`);
|
|
381
|
+
console.log(`Category: ${entry.category === 'ir' ? 'IR remote' : 'Physical device'}`);
|
|
382
|
+
if (entry.aliases && entry.aliases.length > 0) {
|
|
383
|
+
console.log(`Aliases: ${entry.aliases.join(', ')}`);
|
|
384
|
+
}
|
|
385
|
+
if (entry.commands.length === 0) {
|
|
386
|
+
console.log('\nCommands: (none — status-only device)');
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
console.log('\nCommands:');
|
|
390
|
+
const rows = entry.commands.map((c) => [
|
|
391
|
+
c.commandType === 'customize' ? `${c.command} [customize]` : c.command,
|
|
392
|
+
c.parameter,
|
|
393
|
+
c.description,
|
|
394
|
+
]);
|
|
395
|
+
printTable(['command', 'parameter', 'description'], rows);
|
|
396
|
+
}
|
|
397
|
+
if (entry.statusFields && entry.statusFields.length > 0) {
|
|
398
|
+
console.log('\nStatus fields (from "devices status"):');
|
|
399
|
+
console.log(' ' + entry.statusFields.join(', '));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
//# sourceMappingURL=devices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/commands/devices.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createClient } from '../api/client.js';
|
|
2
|
+
import { printTable, printJson, isJsonMode, handleError } from '../utils/output.js';
|
|
3
|
+
export function registerScenesCommand(program) {
|
|
4
|
+
const scenes = program
|
|
5
|
+
.command('scenes')
|
|
6
|
+
.description('Manage and execute SwitchBot scenes');
|
|
7
|
+
// switchbot scenes list
|
|
8
|
+
scenes
|
|
9
|
+
.command('list')
|
|
10
|
+
.description('List all manual scenes (scenes created in the SwitchBot app)')
|
|
11
|
+
.addHelpText('after', `
|
|
12
|
+
Output columns: sceneId, sceneName
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
$ switchbot scenes list
|
|
16
|
+
$ switchbot scenes list --json
|
|
17
|
+
`)
|
|
18
|
+
.action(async () => {
|
|
19
|
+
try {
|
|
20
|
+
const client = createClient();
|
|
21
|
+
const res = await client.get('/v1.1/scenes');
|
|
22
|
+
if (isJsonMode()) {
|
|
23
|
+
printJson(res.data.body);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const scenes = res.data.body;
|
|
27
|
+
if (scenes.length === 0) {
|
|
28
|
+
console.log('No scenes found');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
printTable(['sceneId', 'sceneName'], scenes.map((s) => [s.sceneId, s.sceneName]));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
handleError(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// switchbot scenes execute <sceneId>
|
|
38
|
+
scenes
|
|
39
|
+
.command('execute')
|
|
40
|
+
.description('Execute a manual scene by its ID')
|
|
41
|
+
.argument('<sceneId>', 'Scene ID from "scenes list"')
|
|
42
|
+
.addHelpText('after', `
|
|
43
|
+
Example:
|
|
44
|
+
$ switchbot scenes execute T12345678
|
|
45
|
+
`)
|
|
46
|
+
.action(async (sceneId) => {
|
|
47
|
+
try {
|
|
48
|
+
const client = createClient();
|
|
49
|
+
await client.post(`/v1.1/scenes/${sceneId}/execute`);
|
|
50
|
+
console.log(`✓ Scene executed: ${sceneId}`);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
handleError(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=scenes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenes.js","sourceRoot":"","sources":["../../src/commands/scenes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAOpF,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,MAAM,GAAG,OAAO;SACnB,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,qCAAqC,CAAC,CAAC;IAEtD,wBAAwB;IACxB,MAAM;SACH,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,8DAA8D,CAAC;SAC3E,WAAW,CAAC,OAAO,EAAE;;;;;;CAMzB,CAAC;SACG,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,CAAoB,cAAc,CAAC,CAAC;YAEhE,IAAI,UAAU,EAAE,EAAE,CAAC;gBACjB,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACzB,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YAED,UAAU,CACR,CAAC,SAAS,EAAE,WAAW,CAAC,EACxB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAC5C,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,qCAAqC;IACrC,MAAM;SACH,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,kCAAkC,CAAC;SAC/C,QAAQ,CAAC,WAAW,EAAE,6BAA6B,CAAC;SACpD,WAAW,CAAC,OAAO,EAAE;;;CAGzB,CAAC;SACG,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;QAChC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;YAC9B,MAAM,MAAM,CAAC,IAAI,CAAC,gBAAgB,OAAO,UAAU,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { createClient } from '../api/client.js';
|
|
2
|
+
import { printKeyValue, printJson, isJsonMode, handleError } from '../utils/output.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
function assertValidUrl(cmd, url) {
|
|
5
|
+
let parsed;
|
|
6
|
+
try {
|
|
7
|
+
parsed = new URL(url);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
cmd.error(`error: invalid URL "${url}" (expected absolute URL, e.g. https://example.com/hook)`, { exitCode: 2, code: 'switchbot.invalidUrl' });
|
|
11
|
+
}
|
|
12
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
13
|
+
cmd.error(`error: URL must use http:// or https:// (got "${parsed.protocol}")`, { exitCode: 2, code: 'switchbot.invalidUrl' });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function registerWebhookCommand(program) {
|
|
17
|
+
const webhook = program
|
|
18
|
+
.command('webhook')
|
|
19
|
+
.description('Manage SwitchBot Webhook configuration')
|
|
20
|
+
.addHelpText('after', `
|
|
21
|
+
A webhook lets SwitchBot POST device state-change events to a URL you host.
|
|
22
|
+
Only one webhook URL can be active per account; "setup" registers it for ALL devices.
|
|
23
|
+
`);
|
|
24
|
+
// switchbot webhook setup <url>
|
|
25
|
+
const setup = webhook
|
|
26
|
+
.command('setup')
|
|
27
|
+
.description('Configure the webhook receiver URL (receives events from all devices)')
|
|
28
|
+
.argument('<url>', 'Absolute http(s):// URL where SwitchBot will POST events')
|
|
29
|
+
.addHelpText('after', `
|
|
30
|
+
Example:
|
|
31
|
+
$ switchbot webhook setup https://example.com/switchbot/events
|
|
32
|
+
`);
|
|
33
|
+
setup.action(async (url) => {
|
|
34
|
+
assertValidUrl(setup, url);
|
|
35
|
+
try {
|
|
36
|
+
const client = createClient();
|
|
37
|
+
await client.post('/v1.1/webhook/setupWebhook', {
|
|
38
|
+
action: 'setupWebhook',
|
|
39
|
+
url,
|
|
40
|
+
deviceList: 'ALL',
|
|
41
|
+
});
|
|
42
|
+
console.log(chalk.green(`✓ Webhook configured: ${url}`));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
handleError(error);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// switchbot webhook query [--details <url>]
|
|
49
|
+
webhook
|
|
50
|
+
.command('query')
|
|
51
|
+
.description('Query webhook configuration')
|
|
52
|
+
.option('--details <url>', 'Query detailed configuration (enable/deviceList/timestamps) for a specific URL')
|
|
53
|
+
.addHelpText('after', `
|
|
54
|
+
Without --details, lists all configured webhook URLs.
|
|
55
|
+
With --details, prints enable/deviceList/createTime/lastUpdateTime for the given URL.
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
$ switchbot webhook query
|
|
59
|
+
$ switchbot webhook query --details https://example.com/hook
|
|
60
|
+
$ switchbot webhook query --json
|
|
61
|
+
`)
|
|
62
|
+
.action(async (options) => {
|
|
63
|
+
try {
|
|
64
|
+
const client = createClient();
|
|
65
|
+
if (options.details) {
|
|
66
|
+
const res = await client.post('/v1.1/webhook/queryWebhook', { action: 'queryDetails', urls: [options.details] });
|
|
67
|
+
if (isJsonMode()) {
|
|
68
|
+
printJson(res.data.body);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const details = res.data.body;
|
|
72
|
+
if (!details || details.length === 0) {
|
|
73
|
+
console.log('No webhook configuration found for this URL');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (const d of details) {
|
|
77
|
+
printKeyValue({
|
|
78
|
+
url: d.url,
|
|
79
|
+
enable: d.enable,
|
|
80
|
+
deviceList: d.deviceList,
|
|
81
|
+
createTime: new Date(d.createTime).toLocaleString(),
|
|
82
|
+
lastUpdateTime: new Date(d.lastUpdateTime).toLocaleString(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const res = await client.post('/v1.1/webhook/queryWebhook', { action: 'queryUrl' });
|
|
88
|
+
if (isJsonMode()) {
|
|
89
|
+
printJson(res.data.body);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const urls = res.data.body.urls ?? [];
|
|
93
|
+
if (urls.length === 0) {
|
|
94
|
+
console.log('No webhooks configured');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
console.log('Configured Webhook URLs:');
|
|
98
|
+
urls.forEach((u) => console.log(` ${chalk.cyan(u)}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
handleError(error);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// switchbot webhook update <url> [--enable | --disable]
|
|
106
|
+
const update = webhook
|
|
107
|
+
.command('update')
|
|
108
|
+
.description('Update webhook configuration (enable / disable a registered URL)')
|
|
109
|
+
.argument('<url>', 'URL of the webhook to update (must already be configured)')
|
|
110
|
+
.option('--enable', 'Enable the webhook')
|
|
111
|
+
.option('--disable', 'Disable the webhook')
|
|
112
|
+
.addHelpText('after', `
|
|
113
|
+
--enable and --disable are mutually exclusive. If neither is provided, the
|
|
114
|
+
webhook is re-submitted with no change to its enabled state.
|
|
115
|
+
|
|
116
|
+
Examples:
|
|
117
|
+
$ switchbot webhook update https://example.com/hook --enable
|
|
118
|
+
$ switchbot webhook update https://example.com/hook --disable
|
|
119
|
+
`);
|
|
120
|
+
update.action(async (url, options) => {
|
|
121
|
+
if (options.enable && options.disable) {
|
|
122
|
+
update.error('error: --enable and --disable are mutually exclusive', { exitCode: 2, code: 'switchbot.conflictingOptions' });
|
|
123
|
+
}
|
|
124
|
+
assertValidUrl(update, url);
|
|
125
|
+
try {
|
|
126
|
+
const client = createClient();
|
|
127
|
+
const config = { url };
|
|
128
|
+
if (options.enable)
|
|
129
|
+
config.enable = true;
|
|
130
|
+
if (options.disable)
|
|
131
|
+
config.enable = false;
|
|
132
|
+
await client.post('/v1.1/webhook/updateWebhook', {
|
|
133
|
+
action: 'updateWebhook',
|
|
134
|
+
config,
|
|
135
|
+
});
|
|
136
|
+
const statusText = options.enable ? 'enabled' : options.disable ? 'disabled' : 'updated';
|
|
137
|
+
console.log(chalk.green(`✓ Webhook ${statusText}: ${url}`));
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
handleError(error);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
// switchbot webhook delete <url>
|
|
144
|
+
const del = webhook
|
|
145
|
+
.command('delete')
|
|
146
|
+
.description('Delete webhook configuration')
|
|
147
|
+
.argument('<url>', 'URL of the webhook to remove')
|
|
148
|
+
.addHelpText('after', `
|
|
149
|
+
Example:
|
|
150
|
+
$ switchbot webhook delete https://example.com/hook
|
|
151
|
+
`);
|
|
152
|
+
del.action(async (url) => {
|
|
153
|
+
assertValidUrl(del, url);
|
|
154
|
+
try {
|
|
155
|
+
const client = createClient();
|
|
156
|
+
await client.post('/v1.1/webhook/deleteWebhook', {
|
|
157
|
+
action: 'deleteWebhook',
|
|
158
|
+
url,
|
|
159
|
+
});
|
|
160
|
+
console.log(chalk.green(`✓ Webhook deleted: ${url}`));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
handleError(error);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=webhook.js.map
|