shelly-cli 0.2.2

CLI for managing and controlling Shelly devices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
use clap::{Args, Parser, Subcommand};

/// Pagination and field-selection flags shared by list commands.
#[derive(Args, Clone)]
pub struct ListArgs {
    /// Maximum number of items to return
    #[arg(long, default_value = "100")]
    pub limit: usize,
    /// Number of items to skip before returning results
    #[arg(long, default_value = "0")]
    pub offset: usize,
    /// Comma-separated list of field names to include in each item
    #[arg(long)]
    pub fields: Option<String>,
}

#[derive(Parser)]
#[command(
    about = "CLI for managing Shelly devices. Run 'shelly schema' for machine-readable introspection.",
    version,
    after_long_help = "\
Examples:
  shelly discover --subnet 192.168.1.0/24
  shelly on \"Kitchen Light\"
  shelly on \"Office Strip\" --id 1
  shelly status -n \"Living Room\"
  shelly power -a
  shelly energy -a
  shelly health
  shelly watch
  shelly -g lights off"
)]
pub struct Cli {
    /// Target device by IP address
    #[arg(long, global = true)]
    pub host: Option<String>,

    /// Target device by name (uses cached device list)
    #[arg(long, short = 'n', global = true)]
    pub name: Option<String>,

    /// Target a device group (defined in groups.toml)
    #[arg(long, short = 'g', global = true)]
    pub group: Option<String>,

    /// Output format: auto detects TTY (default), json, or text
    #[arg(long, short = 'o', global = true, default_value = "auto",
          value_parser = ["auto", "text", "json"])]
    pub output: String,

    /// Force JSON output (kept for backwards compatibility; prefer --output json)
    #[arg(long, short = 'j', global = true, hide = true)]
    pub json: bool,

    /// Suppress non-data output
    #[arg(long, short = 'q', global = true)]
    pub quiet: bool,

    /// Device password for authentication
    #[arg(long, short = 'p', global = true)]
    pub password: Option<String>,

    /// HTTP timeout in milliseconds
    #[arg(long, global = true, default_value = "3000")]
    pub timeout: u64,

    #[command(subcommand)]
    pub command: Command,
}

#[derive(Subcommand)]
pub enum Command {
    /// Scan network for Shelly devices
    Discover {
        /// Subnet to scan (CIDR notation, e.g. 10.10.20.0/24)
        #[arg(long)]
        subnet: Option<String>,
    },

    /// List known/cached devices
    Devices {
        /// Re-scan network before listing
        #[arg(long)]
        refresh: bool,
        #[command(flatten)]
        list: ListArgs,
    },

    /// Get device status
    Status {
        /// Query all known devices
        #[arg(long, short = 'a')]
        all: bool,
        #[command(flatten)]
        list: ListArgs,
    },

    /// Control switch/relay outputs
    Switch {
        #[command(subcommand)]
        action: SwitchAction,
    },

    /// Control RGB / RGBW / CCT / dimmable light outputs (Gen2/Gen3)
    Light {
        #[command(subcommand)]
        action: LightAction,
    },

    /// Turn device(s) on
    On {
        /// Device name (positional for convenience)
        device: Option<String>,
        /// Switch/plug ID for multi-channel devices (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },

    /// Turn device(s) off
    Off {
        /// Device name (positional for convenience)
        device: Option<String>,
        /// Switch/plug ID for multi-channel devices (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },

    /// Toggle device(s)
    Toggle {
        /// Device name (positional for convenience)
        device: Option<String>,
        /// Switch/plug ID for multi-channel devices (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },

    /// Energy and power monitoring
    Power {
        /// Query all known devices
        #[arg(long, short = 'a')]
        all: bool,

        /// Meter ID (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },

    /// Show total energy consumption (kWh) across devices
    Energy {
        /// Query all known devices
        #[arg(long, short = 'a')]
        all: bool,
    },

    /// Check or update firmware
    Firmware {
        #[command(subcommand)]
        action: FirmwareAction,
    },

    /// Get or set device configuration
    Config {
        #[command(subcommand)]
        action: ConfigAction,
    },

    /// Manage device groups
    Group {
        #[command(subcommand)]
        action: GroupAction,
    },

    /// View device schedules
    Schedule {
        #[command(subcommand)]
        action: ScheduleAction,
    },

    /// View device webhooks
    Webhook {
        #[command(subcommand)]
        action: WebhookAction,
    },

    /// Backup device configuration to a JSON file
    Backup {
        /// Backup all known devices
        #[arg(long, short = 'a')]
        all: bool,
        /// Output directory (default: current directory)
        #[arg(long)]
        dir: Option<String>,
    },

    /// Restore device configuration from a backup file
    Restore {
        /// Path to the backup JSON file
        file: String,
        /// Skip confirmation prompt (required when stdin is not a terminal)
        #[arg(long, short = 'y')]
        yes: bool,
    },

    /// Rename a device
    Rename {
        /// New name for the device
        new_name: String,
        /// Skip confirmation prompt (required when stdin is not a terminal)
        #[arg(long, short = 'y')]
        yes: bool,
    },

    /// Reboot a device
    Reboot {
        /// Skip confirmation prompt (required when stdin is not a terminal)
        #[arg(long, short = 'y')]
        yes: bool,
    },

    /// Live-updating dashboard of all devices
    Watch {
        /// Refresh interval in seconds
        #[arg(long, default_value = "2")]
        interval: u64,
    },

    /// Show detailed information about a device
    Info,

    /// Check device health (temperature, WiFi, firmware, online status)
    Health,

    /// Output a machine-readable JSON description of all commands, arguments, and error kinds
    Schema,

    /// Describe supported device generations without network access
    Capabilities,

    /// Generate shell completions (with dynamic device name completion)
    Completions {
        /// Shell to generate completions for
        shell: clap_complete::Shell,
    },

    /// Output cached device names for shell completion
    #[command(name = "_complete-device-names", hide = true)]
    CompleteDeviceNames,

    /// Output group names for shell completion
    #[command(name = "_complete-group-names", hide = true)]
    CompleteGroupNames,
}

#[derive(Subcommand, Clone)]
pub enum SwitchAction {
    /// Get switch status
    Status {
        /// Switch/plug ID for multi-channel devices (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },
    /// Turn switch on
    On {
        /// Switch/plug ID for multi-channel devices (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },
    /// Turn switch off
    Off {
        /// Switch/plug ID for multi-channel devices (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },
    /// Toggle switch
    Toggle {
        /// Switch/plug ID for multi-channel devices (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },
}

/// Color/brightness attributes shared by `light on` and `light set`.
#[derive(Args, Clone)]
pub struct LightSetArgs {
    /// Light component ID (default: 0)
    #[arg(long, default_value = "0")]
    pub id: u8,
    /// Color as hex (#00ff88) or name (red, green, warm, ...)
    #[arg(long, conflicts_with = "rgb")]
    pub color: Option<String>,
    /// Color as comma-separated r,g,b (each 0-255), e.g. 0,255,136
    #[arg(long)]
    pub rgb: Option<String>,
    /// Brightness 1-100 (RGB/RGBW) or 0-100 (CCT/dimmable)
    #[arg(long)]
    pub brightness: Option<u8>,
    /// White channel 0-255 (RGBW only)
    #[arg(long)]
    pub white: Option<u8>,
    /// Color temperature in Kelvin (CCT only)
    #[arg(long)]
    pub temp: Option<u32>,
}

#[derive(Subcommand, Clone)]
pub enum LightAction {
    /// Show light status (on/off, color, brightness)
    Status {
        /// Light component ID (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },
    /// Turn light on, optionally setting color/brightness/white/temp
    On {
        #[command(flatten)]
        args: LightSetArgs,
    },
    /// Turn light off
    Off {
        /// Light component ID (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },
    /// Toggle light on/off
    Toggle {
        /// Light component ID (default: 0)
        #[arg(long, default_value = "0")]
        id: u8,
    },
    /// Change attributes without changing power state
    Set {
        #[command(flatten)]
        args: LightSetArgs,
    },
}

#[derive(Subcommand, Clone)]
pub enum FirmwareAction {
    /// Check for available updates
    Check {
        /// Check all known devices
        #[arg(long, short = 'a')]
        all: bool,
    },
    /// Update firmware to latest stable version
    Update {
        /// Update all known devices
        #[arg(long, short = 'a')]
        all: bool,
        /// Skip confirmation prompt (required when stdin is not a terminal)
        #[arg(long, short = 'y')]
        yes: bool,
    },
}

#[derive(Subcommand, Clone)]
pub enum ConfigAction {
    /// Get device configuration
    Get {
        /// Get config for all devices
        #[arg(long, short = 'a')]
        all: bool,
    },
    /// Set a device configuration value (e.g. eco_mode true)
    Set {
        /// Configuration key (e.g. eco_mode, name, led_status_disable)
        key: String,
        /// Value to set
        value: String,
    },
}

#[derive(Subcommand, Clone)]
pub enum ScheduleAction {
    /// List device schedules
    List {
        /// List schedules for all devices
        #[arg(long, short = 'a')]
        all: bool,
        #[command(flatten)]
        list: ListArgs,
    },
}

#[derive(Subcommand, Clone)]
pub enum WebhookAction {
    /// List device webhooks
    List {
        /// List webhooks for all devices
        #[arg(long, short = 'a')]
        all: bool,
        #[command(flatten)]
        list: ListArgs,
    },
}

#[derive(Subcommand, Clone)]
pub enum GroupAction {
    /// List all defined groups
    List {
        #[command(flatten)]
        list: ListArgs,
    },
    /// Add a new group
    Add {
        /// Group name
        name: String,
        /// Device names to include
        #[arg(required = true)]
        devices: Vec<String>,
    },
    /// Remove a group
    Remove {
        /// Group name to remove
        name: String,
        /// Skip confirmation prompt (required when stdin is not a terminal)
        #[arg(long, short = 'y')]
        yes: bool,
    },
    /// Show devices in a group
    Show {
        /// Group name
        name: String,
    },
}