@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.
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerScenesCommand(program: Command): void;
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerWebhookCommand(program: Command): void;
@@ -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