@switchbot/openapi-cli 2.1.0 → 2.2.1

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 CHANGED
@@ -165,7 +165,8 @@ switchbot config show
165
165
  | `--no-retry` | Disable automatic 429 retries |
166
166
  | `--backoff <strategy>` | Retry backoff: `exponential` (default) or `linear` |
167
167
  | `--no-quota` | Disable local request-quota tracking |
168
- | `--audit-log [path]` | Append mutating commands to a JSONL audit log (default path: `~/.switchbot/audit.log`) |
168
+ | `--audit-log` | Append mutating commands to a JSONL audit log (default path: `~/.switchbot/audit.log`) |
169
+ | `--audit-log-path <path>` | Custom audit log path; use together with `--audit-log` |
169
170
  | `-V`, `--version` | Print the CLI version |
170
171
  | `-h`, `--help` | Show help for any command or subcommand |
171
172
 
@@ -176,6 +177,8 @@ switchbot --help
176
177
  switchbot devices command --help
177
178
  ```
178
179
 
180
+ > **Tip — required-value flags and subcommands.** Flags like `--profile`, `--timeout`, `--max`, and `--interval` take a value. If you omit it, Commander will happily consume the next token — including a subcommand name. Since v2.2.1 the CLI rejects that eagerly (exit 2 with a clear error), but if you ever hit `unknown command 'list'` after something like `switchbot --profile list`, use the `--flag=value` form: `switchbot --profile=home devices list`.
181
+
179
182
  ### `--dry-run`
180
183
 
181
184
  Intercepts every non-GET request: the CLI prints the URL/body it would have
@@ -205,6 +208,7 @@ switchbot config list-profiles # List saved profiles
205
208
  # Default columns (4): deviceId, deviceName, type, category
206
209
  # Pass --wide for the full 10-column operator view
207
210
  switchbot devices list
211
+ switchbot devices ls # short alias for 'list'
208
212
  switchbot devices list --wide
209
213
  switchbot devices list --json | jq '.deviceList[].deviceId'
210
214
 
@@ -212,6 +216,11 @@ switchbot devices list --json | jq '.deviceList[].deviceId'
212
216
  # Physical: category = "physical"
213
217
  switchbot devices list --format=tsv --fields=deviceId,type,category
214
218
 
219
+ # Filter devices by type / name / category / room (server-side filter keys)
220
+ switchbot devices list --filter category=physical
221
+ switchbot devices list --filter type=Bot
222
+ switchbot devices list --filter name=living,category=physical
223
+
215
224
  # Filter by family / room (family & room info requires the 'src: OpenClaw'
216
225
  # header, which this CLI sends on every request)
217
226
  switchbot devices list --json | jq '.deviceList[] | select(.familyName == "Home")'
@@ -221,6 +230,16 @@ switchbot devices list --json | jq '[.deviceList[], .infraredRemoteList[]] | gro
221
230
  switchbot devices status <deviceId>
222
231
  switchbot devices status <deviceId> --json
223
232
 
233
+ # Resolve device by fuzzy name instead of ID (status, command, describe, expand, watch)
234
+ switchbot devices status --name "客厅空调"
235
+ switchbot devices command --name "Office Light" turnOn
236
+ switchbot devices describe --name "Kitchen Bot"
237
+
238
+ # Batch status across multiple devices
239
+ switchbot devices status --ids ABC,DEF,GHI
240
+ switchbot devices status --ids ABC,DEF --fields power,battery # only show specific fields
241
+ switchbot devices status --ids ABC,DEF --format jsonl # one JSON line per device
242
+
224
243
  # Send a control command
225
244
  switchbot devices command <deviceId> <cmd> [parameter] [--type command|customize]
226
245
 
@@ -229,7 +248,7 @@ switchbot devices describe <deviceId>
229
248
  switchbot devices describe <deviceId> --json
230
249
 
231
250
  # Discover what's supported (offline reference, no API call)
232
- switchbot devices types # List all device types + IR remote types
251
+ switchbot devices types # List all device types + IR remote types (incl. role column)
233
252
  switchbot devices commands <type> # Show commands, parameter formats, and status fields
234
253
  switchbot devices commands Bot
235
254
  switchbot devices commands "Smart Lock"
@@ -268,6 +287,8 @@ Some commands require a packed string like `"26,2,2,on"`. `devices expand` build
268
287
  ```bash
269
288
  # Air Conditioner — setAll
270
289
  switchbot devices expand <acId> setAll --temp 26 --mode cool --fan low --power on
290
+ # Resolve by name
291
+ switchbot devices expand --name "客厅空调" setAll --temp 26 --mode cool --fan low --power on
271
292
 
272
293
  # Curtain / Roller Shade — setPosition
273
294
  switchbot devices expand <curtainId> setPosition --position 50 --mode silent
@@ -412,6 +433,40 @@ nohup switchbot events mqtt-tail --json >> ~/switchbot-events.log 2>&1 &
412
433
 
413
434
  Run `switchbot doctor` to verify MQTT credentials are configured correctly before connecting.
414
435
 
436
+ #### `mqtt-tail` sinks — route events to external services
437
+
438
+ By default `mqtt-tail` prints JSONL to stdout. Use `--sink` (repeatable) to route events to one or more destinations instead:
439
+
440
+ | Sink | Required flags |
441
+ |---|---|
442
+ | `stdout` | (default when no `--sink` given) |
443
+ | `file` | `--sink-file <path>` — append JSONL |
444
+ | `webhook` | `--webhook-url <url>` — HTTP POST each event |
445
+ | `openclaw` | `--openclaw-url`, `--openclaw-token` (or `$OPENCLAW_TOKEN`), `--openclaw-model` |
446
+ | `telegram` | `--telegram-token` (or `$TELEGRAM_TOKEN`), `--telegram-chat <chatId>` |
447
+ | `homeassistant` | `--ha-url <url>` + `--ha-webhook-id` (no auth) or `--ha-token` (REST event API) |
448
+
449
+ ```bash
450
+ # Push events to an OpenClaw agent (replaces the SwitchBot channel plugin)
451
+ switchbot events mqtt-tail \
452
+ --sink openclaw \
453
+ --openclaw-token <token> \
454
+ --openclaw-model my-home-agent
455
+
456
+ # Write to file + push to OpenClaw simultaneously
457
+ switchbot events mqtt-tail \
458
+ --sink file --sink-file ~/.switchbot/events.jsonl \
459
+ --sink openclaw --openclaw-token <token> --openclaw-model home
460
+
461
+ # Generic webhook (n8n, Make, etc.)
462
+ switchbot events mqtt-tail --sink webhook --webhook-url https://n8n.local/hook/abc
463
+
464
+ # Forward to Home Assistant via webhook trigger
465
+ switchbot events mqtt-tail --sink homeassistant --ha-url http://homeassistant.local:8123 --ha-webhook-id switchbot
466
+ ```
467
+
468
+ Device state is also persisted to `~/.switchbot/device-history/<deviceId>.json` (latest + 100-entry ring buffer) regardless of sink configuration. This enables the `get_device_history` MCP tool to answer state queries without an API call.
469
+
415
470
  ### `completion` — shell tab-completion
416
471
 
417
472
  ```bash
@@ -498,7 +553,7 @@ switchbot history replay 7 # re-run entry #7
498
553
  switchbot --json history show --limit 50 | jq '.entries[] | select(.result=="error")'
499
554
  ```
500
555
 
501
- Reads the JSONL audit log (`~/.switchbot/audit.log` by default; override with `--audit-log`). Each entry records the timestamp, command, device ID, result, and dry-run flag. `replay` re-runs the original command with the original arguments.
556
+ Reads the JSONL audit log (`~/.switchbot/audit.log` by default; override with `--audit-log --audit-log-path <path>`). Each entry records the timestamp, command, device ID, result, and dry-run flag. `replay` re-runs the original command with the original arguments.
502
557
 
503
558
  ### `catalog` — device type catalog
504
559
 
@@ -1,3 +1,4 @@
1
+ import { intArg, enumArg, stringArg } from '../utils/arg-parsers.js';
1
2
  import { printJson, isJsonMode, handleError, buildErrorPayload } from '../utils/output.js';
2
3
  import { fetchDeviceList, executeCommand, isDestructiveCommand, buildHubLocationMap, } from '../lib/devices.js';
3
4
  import { createClient } from '../api/client.js';
@@ -6,6 +7,7 @@ import { isDryRun } from '../utils/flags.js';
6
7
  import { DryRunSignal } from '../api/client.js';
7
8
  import { getCachedTypeMap } from '../devices/cache.js';
8
9
  const DEFAULT_CONCURRENCY = 5;
10
+ const COMMAND_TYPES = ['command', 'customize'];
9
11
  /** Run `task(x)` for every element with at most `concurrency` running at once. */
10
12
  async function runPool(items, concurrency, task) {
11
13
  const results = new Array(items.length);
@@ -84,13 +86,13 @@ export function registerBatchCommand(devices) {
84
86
  .description('Send the same command to many devices in one run (filter- or stdin-driven)')
85
87
  .argument('<command>', 'Command name, e.g. turnOn, turnOff, setBrightness')
86
88
  .argument('[parameter]', 'Command parameter (same rules as `devices command`; omit for no-arg)')
87
- .option('--filter <expr>', 'Target devices matching a filter, e.g. type=Bot,family=Home')
88
- .option('--ids <csv>', 'Explicit comma-separated list of deviceIds')
89
- .option('--concurrency <n>', 'Max parallel in-flight requests (default 5)', '5')
89
+ .option('--filter <expr>', 'Target devices matching a filter, e.g. type=Bot,family=Home', stringArg('--filter'))
90
+ .option('--ids <csv>', 'Explicit comma-separated list of deviceIds', stringArg('--ids'))
91
+ .option('--concurrency <n>', 'Max parallel in-flight requests (default 5)', intArg('--concurrency', { min: 1 }), '5')
90
92
  .option('--yes', 'Allow destructive commands (Smart Lock unlock, garage open, ...)')
91
- .option('--type <commandType>', '"command" (default) or "customize" for user-defined IR buttons', 'command')
93
+ .option('--type <commandType>', '"command" (default) or "customize" for user-defined IR buttons', enumArg('--type', COMMAND_TYPES), 'command')
92
94
  .option('--stdin', 'Read deviceIds from stdin, one per line (same as trailing "-")')
93
- .option('--idempotency-key-prefix <prefix>', 'Prefix for idempotency keys (key per device: <prefix>-<deviceId>)')
95
+ .option('--idempotency-key-prefix <prefix>', 'Prefix for idempotency keys (key per device: <prefix>-<deviceId>)', stringArg('--idempotency-key-prefix'))
94
96
  .addHelpText('after', `
95
97
  Targets are resolved in this priority order:
96
98
  1. --ids when present (explicit deviceIds)
@@ -1,3 +1,4 @@
1
+ import { enumArg } from '../utils/arg-parsers.js';
1
2
  import { printJson, isJsonMode, handleError, UsageError } from '../utils/output.js';
2
3
  import { clearCache, clearStatusCache, describeCache, loadStatusCache, } from '../devices/cache.js';
3
4
  function formatAge(ms) {
@@ -15,6 +16,7 @@ function formatAge(ms) {
15
16
  return `${h}h ${m % 60}m`;
16
17
  }
17
18
  export function registerCacheCommand(program) {
19
+ const CACHE_KEYS = ['list', 'status', 'all'];
18
20
  const cache = program
19
21
  .command('cache')
20
22
  .description('Inspect and manage the local SwitchBot CLI caches')
@@ -78,7 +80,7 @@ Examples:
78
80
  cache
79
81
  .command('clear')
80
82
  .description('Delete cache files')
81
- .option('--key <which>', 'Which cache to clear: "list" | "status" | "all" (default)', 'all')
83
+ .option('--key <which>', 'Which cache to clear: "list" | "status" | "all" (default)', enumArg('--key', CACHE_KEYS), 'all')
82
84
  .action((options) => {
83
85
  try {
84
86
  const key = options.key;
@@ -26,32 +26,42 @@ const MCP_TOOLS = [
26
26
  'run_scene',
27
27
  'search_catalog',
28
28
  'account_overview',
29
+ 'get_device_history',
29
30
  ];
30
31
  export function registerCapabilitiesCommand(program) {
31
32
  program
32
33
  .command('capabilities')
33
34
  .description('Print a machine-readable manifest of CLI capabilities (for agent bootstrap)')
34
- .action(() => {
35
+ .option('--minimal', 'Omit per-subcommand flag details to reduce output size')
36
+ .action((opts) => {
35
37
  const catalog = getEffectiveCatalog();
36
- const commands = program.commands
37
- .filter((c) => c.name() !== 'capabilities')
38
- .map((c) => ({
39
- name: c.name(),
40
- description: c.description(),
41
- subcommands: c.commands.map((s) => ({
42
- name: s.name(),
43
- description: s.description(),
44
- args: s.registeredArguments.map((a) => ({
45
- name: a.name(),
46
- required: a.required,
47
- variadic: a.variadic,
48
- })),
49
- flags: s.options.map((o) => ({
50
- flags: o.flags,
51
- description: o.description,
52
- })),
53
- })),
54
- }));
38
+ const allCommands = [
39
+ ...program.commands,
40
+ // Commander adds 'help' implicitly; include it explicitly so it appears in the manifest
41
+ { name: () => 'help', description: () => 'Display help for a command', commands: [], options: [], registeredArguments: [] },
42
+ ];
43
+ const commands = allCommands.map((c) => {
44
+ const entry = {
45
+ name: c.name(),
46
+ description: c.description(),
47
+ };
48
+ if (!opts.minimal) {
49
+ entry.subcommands = c.commands.map((s) => ({
50
+ name: s.name(),
51
+ description: s.description(),
52
+ args: s.registeredArguments.map((a) => ({
53
+ name: a.name(),
54
+ required: a.required,
55
+ variadic: a.variadic,
56
+ })),
57
+ flags: s.options.map((o) => ({
58
+ flags: o.flags,
59
+ description: o.description,
60
+ })),
61
+ }));
62
+ }
63
+ return entry;
64
+ });
55
65
  const globalFlags = program.options.map((opt) => ({
56
66
  flags: opt.flags,
57
67
  description: opt.description,
@@ -1,7 +1,9 @@
1
+ import { enumArg } from '../utils/arg-parsers.js';
1
2
  import { printTable, printJson, isJsonMode, handleError, UsageError } from '../utils/output.js';
2
3
  import { resolveFormat, resolveFields, renderRows } from '../utils/format.js';
3
4
  import { DEVICE_CATALOG, findCatalogEntry, getCatalogOverlayPath, getEffectiveCatalog, loadCatalogOverlay, resetCatalogOverlayCache, } from '../devices/catalog.js';
4
5
  export function registerCatalogCommand(program) {
6
+ const SOURCES = ['built-in', 'overlay', 'effective'];
5
7
  const catalog = program
6
8
  .command('catalog')
7
9
  .description('Inspect the built-in device catalog and any local overlay')
@@ -66,7 +68,7 @@ Examples:
66
68
  .command('show')
67
69
  .description("Show the effective catalog (or one entry). Defaults to 'effective' source.")
68
70
  .argument('[type...]', 'Optional device type/alias (case-insensitive, partial match)')
69
- .option('--source <source>', 'Which catalog to show: built-in | overlay | effective (default)', 'effective')
71
+ .option('--source <source>', 'Which catalog to show: built-in | overlay | effective (default)', enumArg('--source', SOURCES), 'effective')
70
72
  .action((typeParts, options) => {
71
73
  try {
72
74
  const source = options.source;
@@ -12,13 +12,19 @@ _switchbot_completion() {
12
12
  cword="\${COMP_CWORD}"
13
13
  }
14
14
 
15
- local top_cmds="config devices scenes webhook completion help"
16
- local config_sub="set-token show"
17
- local devices_sub="list status command types commands"
15
+ local top_cmds="config devices scenes webhook completion mcp quota catalog cache events doctor schema history plan capabilities help"
16
+ local config_sub="set-token show list-profiles"
17
+ local devices_sub="list ls status command types commands describe batch watch explain expand meta"
18
18
  local scenes_sub="list execute"
19
19
  local webhook_sub="setup query update delete"
20
+ local events_sub="tail mqtt-tail"
21
+ local quota_sub="status reset"
22
+ local catalog_sub="path show diff refresh"
23
+ local cache_sub="show clear"
24
+ local history_sub="show replay"
25
+ local plan_sub="schema validate run"
20
26
  local completion_shells="bash zsh fish powershell"
21
- local global_opts="--json --verbose -v --dry-run --timeout --config --help -h --version -V"
27
+ local global_opts="--json --format --fields --verbose -v --dry-run --timeout --retry-on-429 --backoff --no-retry --no-quota --cache --no-cache --config --profile --audit-log --audit-log-path --help -h --version -V"
22
28
 
23
29
  if [[ \${cword} -eq 1 ]]; then
24
30
  COMPREPLY=( $(compgen -W "\${top_cmds} \${global_opts}" -- "\${cur}") )
@@ -43,6 +49,36 @@ _switchbot_completion() {
43
49
  COMPREPLY=( $(compgen -W "\${scenes_sub}" -- "\${cur}") )
44
50
  fi
45
51
  ;;
52
+ events)
53
+ if [[ \${cword} -eq 2 ]]; then
54
+ COMPREPLY=( $(compgen -W "\${events_sub}" -- "\${cur}") )
55
+ fi
56
+ ;;
57
+ quota)
58
+ if [[ \${cword} -eq 2 ]]; then
59
+ COMPREPLY=( $(compgen -W "\${quota_sub}" -- "\${cur}") )
60
+ fi
61
+ ;;
62
+ catalog)
63
+ if [[ \${cword} -eq 2 ]]; then
64
+ COMPREPLY=( $(compgen -W "\${catalog_sub}" -- "\${cur}") )
65
+ fi
66
+ ;;
67
+ cache)
68
+ if [[ \${cword} -eq 2 ]]; then
69
+ COMPREPLY=( $(compgen -W "\${cache_sub}" -- "\${cur}") )
70
+ fi
71
+ ;;
72
+ history)
73
+ if [[ \${cword} -eq 2 ]]; then
74
+ COMPREPLY=( $(compgen -W "\${history_sub}" -- "\${cur}") )
75
+ fi
76
+ ;;
77
+ plan)
78
+ if [[ \${cword} -eq 2 ]]; then
79
+ COMPREPLY=( $(compgen -W "\${plan_sub}" -- "\${cur}") )
80
+ fi
81
+ ;;
46
82
  webhook)
47
83
  if [[ \${cword} -eq 2 ]]; then
48
84
  COMPREPLY=( $(compgen -W "\${webhook_sub}" -- "\${cur}") )
@@ -69,22 +105,39 @@ const ZSH_SCRIPT = `# switchbot zsh completion
69
105
  # source <(switchbot completion zsh)
70
106
 
71
107
  _switchbot() {
72
- local -a top_cmds config_sub devices_sub scenes_sub webhook_sub completion_shells
108
+ local -a top_cmds config_sub devices_sub scenes_sub webhook_sub events_sub quota_sub catalog_sub cache_sub history_sub plan_sub completion_shells
73
109
  top_cmds=(
74
110
  'config:Manage API credentials'
75
111
  'devices:List and control devices'
76
112
  'scenes:List and execute scenes'
77
113
  'webhook:Manage webhook configuration'
78
114
  'completion:Print a shell completion script'
115
+ 'mcp:Run the MCP server'
116
+ 'quota:Inspect local request quota'
117
+ 'catalog:Inspect the built-in device catalog'
118
+ 'cache:Inspect local caches'
119
+ 'events:Receive webhook or MQTT events'
120
+ 'doctor:Run self-checks'
121
+ 'schema:Export the device catalog as JSON'
122
+ 'history:View and replay audited commands'
123
+ 'plan:Validate and run batch plans'
124
+ 'capabilities:Print a machine-readable manifest'
79
125
  'help:Show help for a command'
80
126
  )
81
- config_sub=('set-token:Save token + secret' 'show:Show current credential source')
127
+ config_sub=('set-token:Save token + secret' 'show:Show current credential source' 'list-profiles:List named credential profiles')
82
128
  devices_sub=(
83
129
  'list:List all devices'
130
+ 'ls:Alias for list'
84
131
  'status:Query device status'
85
132
  'command:Send a control command'
86
133
  'types:List known device types (offline)'
87
134
  'commands:Show commands for a device type (offline)'
135
+ 'describe:Show metadata + supported commands for one device'
136
+ 'batch:Send one command to many devices'
137
+ 'watch:Poll device status and emit changes'
138
+ 'explain:One-shot device summary'
139
+ 'expand:Build wire-format params from semantic flags'
140
+ 'meta:Manage local device metadata'
88
141
  )
89
142
  scenes_sub=('list:List manual scenes' 'execute:Run a scene')
90
143
  webhook_sub=(
@@ -93,15 +146,32 @@ _switchbot() {
93
146
  'update:Enable/disable a webhook'
94
147
  'delete:Delete a webhook'
95
148
  )
149
+ events_sub=('tail:Run a local webhook receiver' 'mqtt-tail:Stream MQTT shadow events')
150
+ quota_sub=('status:Show today and recent quota usage' 'reset:Delete the local quota counter')
151
+ catalog_sub=('path:Show overlay path' 'show:Show built-in/overlay/effective catalog' 'diff:Show overlay changes' 'refresh:Clear overlay cache')
152
+ cache_sub=('show:Summarize cache files' 'clear:Delete cache files')
153
+ history_sub=('show:Print recent audit entries' 'replay:Re-run one audited command')
154
+ plan_sub=('schema:Print the plan schema' 'validate:Validate a plan file' 'run:Validate and execute a plan')
96
155
  completion_shells=('bash' 'zsh' 'fish' 'powershell')
97
156
 
98
157
  local global_opts
99
158
  global_opts=(
100
159
  '--json[Raw JSON output]'
160
+ '--format[Output format]:type:(table json jsonl tsv yaml id)'
161
+ '--fields[Comma-separated output columns]:csv:'
101
162
  '(-v --verbose)'{-v,--verbose}'[Log HTTP details to stderr]'
102
163
  '--dry-run[Print mutating requests without sending]'
103
164
  '--timeout[HTTP timeout in ms]:ms:'
165
+ '--retry-on-429[Max 429 retries]:n:'
166
+ '--backoff[Retry backoff strategy]:strategy:(linear exponential)'
167
+ '--no-retry[Disable 429 retries]'
168
+ '--no-quota[Disable the local quota counter]'
169
+ '--cache[Cache mode]:mode:'
170
+ '--no-cache[Disable cache reads]'
104
171
  '--config[Override credential file path]:path:_files'
172
+ '--profile[Use a named credential profile]:name:'
173
+ '--audit-log[Append mutating commands to ~/.switchbot/audit.log]'
174
+ '--audit-log-path[Custom audit log file path]:path:_files'
105
175
  '(-h --help)'{-h,--help}'[Show help]'
106
176
  '(-V --version)'{-V,--version}'[Show version]'
107
177
  )
@@ -122,6 +192,12 @@ _switchbot() {
122
192
  devices) _describe 'devices' devices_sub ;;
123
193
  scenes) _describe 'scenes' scenes_sub ;;
124
194
  webhook) _describe 'webhook' webhook_sub ;;
195
+ events) _describe 'events' events_sub ;;
196
+ quota) _describe 'quota' quota_sub ;;
197
+ catalog) _describe 'catalog' catalog_sub ;;
198
+ cache) _describe 'cache' cache_sub ;;
199
+ history) _describe 'history' history_sub ;;
200
+ plan) _describe 'plan' plan_sub ;;
125
201
  completion) _values 'shell' $completion_shells ;;
126
202
  esac
127
203
  ;;
@@ -143,10 +219,21 @@ complete -c switchbot -f
143
219
 
144
220
  # Global options
145
221
  complete -c switchbot -l json -d 'Raw JSON output'
222
+ complete -c switchbot -l format -r -d 'Output format'
223
+ complete -c switchbot -l fields -r -d 'Comma-separated output columns'
146
224
  complete -c switchbot -s v -l verbose -d 'Log HTTP details to stderr'
147
225
  complete -c switchbot -l dry-run -d 'Print mutating requests without sending'
148
226
  complete -c switchbot -l timeout -r -d 'HTTP timeout in ms'
227
+ complete -c switchbot -l retry-on-429 -r -d 'Max 429 retries'
228
+ complete -c switchbot -l backoff -r -d 'Retry backoff strategy'
229
+ complete -c switchbot -l no-retry -d 'Disable 429 retries'
230
+ complete -c switchbot -l no-quota -d 'Disable the local quota counter'
231
+ complete -c switchbot -l cache -r -d 'Cache mode'
232
+ complete -c switchbot -l no-cache -d 'Disable cache reads'
149
233
  complete -c switchbot -l config -r -d 'Credential file path'
234
+ complete -c switchbot -l profile -r -d 'Named credential profile'
235
+ complete -c switchbot -l audit-log -d 'Append mutating commands to audit log'
236
+ complete -c switchbot -l audit-log-path -r -d 'Custom audit log file path'
150
237
  complete -c switchbot -s h -l help -d 'Show help'
151
238
  complete -c switchbot -s V -l version -d 'Show version'
152
239
 
@@ -156,13 +243,23 @@ complete -c switchbot -n '__fish_use_subcommand' -a 'devices' -d 'List and co
156
243
  complete -c switchbot -n '__fish_use_subcommand' -a 'scenes' -d 'List and execute scenes'
157
244
  complete -c switchbot -n '__fish_use_subcommand' -a 'webhook' -d 'Manage webhook configuration'
158
245
  complete -c switchbot -n '__fish_use_subcommand' -a 'completion' -d 'Print a shell completion script'
246
+ complete -c switchbot -n '__fish_use_subcommand' -a 'mcp' -d 'Run the MCP server'
247
+ complete -c switchbot -n '__fish_use_subcommand' -a 'quota' -d 'Inspect local request quota'
248
+ complete -c switchbot -n '__fish_use_subcommand' -a 'catalog' -d 'Inspect the built-in device catalog'
249
+ complete -c switchbot -n '__fish_use_subcommand' -a 'cache' -d 'Inspect local caches'
250
+ complete -c switchbot -n '__fish_use_subcommand' -a 'events' -d 'Receive webhook or MQTT events'
251
+ complete -c switchbot -n '__fish_use_subcommand' -a 'doctor' -d 'Run self-checks'
252
+ complete -c switchbot -n '__fish_use_subcommand' -a 'schema' -d 'Export the device catalog as JSON'
253
+ complete -c switchbot -n '__fish_use_subcommand' -a 'history' -d 'View and replay audited commands'
254
+ complete -c switchbot -n '__fish_use_subcommand' -a 'plan' -d 'Validate and run batch plans'
255
+ complete -c switchbot -n '__fish_use_subcommand' -a 'capabilities' -d 'Print a machine-readable manifest'
159
256
  complete -c switchbot -n '__fish_use_subcommand' -a 'help' -d 'Show help'
160
257
 
161
258
  # config
162
- complete -c switchbot -n '__fish_seen_subcommand_from config' -a 'set-token show'
259
+ complete -c switchbot -n '__fish_seen_subcommand_from config' -a 'set-token show list-profiles'
163
260
 
164
261
  # devices
165
- complete -c switchbot -n '__fish_seen_subcommand_from devices' -a 'list status command types commands'
262
+ complete -c switchbot -n '__fish_seen_subcommand_from devices' -a 'list ls status command types commands describe batch watch explain expand meta'
166
263
 
167
264
  # scenes
168
265
  complete -c switchbot -n '__fish_seen_subcommand_from scenes' -a 'list execute'
@@ -172,6 +269,24 @@ complete -c switchbot -n '__fish_seen_subcommand_from webhook' -a 'setup query u
172
269
  complete -c switchbot -n '__fish_seen_subcommand_from webhook; and __fish_seen_subcommand_from update' -l enable -d 'Enable the webhook'
173
270
  complete -c switchbot -n '__fish_seen_subcommand_from webhook; and __fish_seen_subcommand_from update' -l disable -d 'Disable the webhook'
174
271
 
272
+ # events
273
+ complete -c switchbot -n '__fish_seen_subcommand_from events' -a 'tail mqtt-tail'
274
+
275
+ # quota
276
+ complete -c switchbot -n '__fish_seen_subcommand_from quota' -a 'status reset'
277
+
278
+ # catalog
279
+ complete -c switchbot -n '__fish_seen_subcommand_from catalog' -a 'path show diff refresh'
280
+
281
+ # cache
282
+ complete -c switchbot -n '__fish_seen_subcommand_from cache' -a 'show clear'
283
+
284
+ # history
285
+ complete -c switchbot -n '__fish_seen_subcommand_from history' -a 'show replay'
286
+
287
+ # plan
288
+ complete -c switchbot -n '__fish_seen_subcommand_from plan' -a 'schema validate run'
289
+
175
290
  # completion
176
291
  complete -c switchbot -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish powershell'
177
292
  `;
@@ -186,13 +301,19 @@ Register-ArgumentCompleter -Native -CommandName switchbot -ScriptBlock {
186
301
  $tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
187
302
  $count = $tokens.Count
188
303
 
189
- $top = 'config','devices','scenes','webhook','completion','help'
190
- $configSub = 'set-token','show'
191
- $devicesSub = 'list','status','command','types','commands'
304
+ $top = 'config','devices','scenes','webhook','completion','mcp','quota','catalog','cache','events','doctor','schema','history','plan','capabilities','help'
305
+ $configSub = 'set-token','show','list-profiles'
306
+ $devicesSub = 'list','ls','status','command','types','commands','describe','batch','watch','explain','expand','meta'
192
307
  $scenesSub = 'list','execute'
193
308
  $webhookSub = 'setup','query','update','delete'
309
+ $eventsSub = 'tail','mqtt-tail'
310
+ $quotaSub = 'status','reset'
311
+ $catalogSub = 'path','show','diff','refresh'
312
+ $cacheSub = 'show','clear'
313
+ $historySub = 'show','replay'
314
+ $planSub = 'schema','validate','run'
194
315
  $shells = 'bash','zsh','fish','powershell'
195
- $globalOpts = '--json','--verbose','-v','--dry-run','--timeout','--config','--help','-h','--version','-V'
316
+ $globalOpts = '--json','--format','--fields','--verbose','-v','--dry-run','--timeout','--retry-on-429','--backoff','--no-retry','--no-quota','--cache','--no-cache','--config','--profile','--audit-log','--audit-log-path','--help','-h','--version','-V'
196
317
 
197
318
  function _emit($values) {
198
319
  $values |
@@ -206,6 +327,12 @@ Register-ArgumentCompleter -Native -CommandName switchbot -ScriptBlock {
206
327
  'config' { if ($count -eq 3) { return _emit $configSub } }
207
328
  'devices' { if ($count -eq 3) { return _emit $devicesSub } }
208
329
  'scenes' { if ($count -eq 3) { return _emit $scenesSub } }
330
+ 'events' { if ($count -eq 3) { return _emit $eventsSub } }
331
+ 'quota' { if ($count -eq 3) { return _emit $quotaSub } }
332
+ 'catalog' { if ($count -eq 3) { return _emit $catalogSub } }
333
+ 'cache' { if ($count -eq 3) { return _emit $cacheSub } }
334
+ 'history' { if ($count -eq 3) { return _emit $historySub } }
335
+ 'plan' { if ($count -eq 3) { return _emit $planSub } }
209
336
  'webhook' {
210
337
  if ($count -eq 3) { return _emit $webhookSub }
211
338
  if ($tokens[2] -eq 'update') { return _emit ('--enable','--disable' + $globalOpts) }
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import { execFileSync } from 'node:child_process';
3
+ import { stringArg } from '../utils/arg-parsers.js';
3
4
  import { saveConfig, showConfig, listProfiles } from '../config.js';
4
5
  import { isJsonMode, printJson } from '../utils/output.js';
5
6
  import chalk from 'chalk';
@@ -49,9 +50,9 @@ Obtain your token/secret from the SwitchBot mobile app:
49
50
  .description('Save token and secret (mode 0600). Use --profile to target a named profile.')
50
51
  .argument('[token]', 'API token; omit when using --from-env-file / --from-op')
51
52
  .argument('[secret]', 'API client secret; omit when using --from-env-file / --from-op')
52
- .option('--from-env-file <path>', 'Read SWITCHBOT_TOKEN and SWITCHBOT_SECRET from a dotenv file')
53
- .option('--from-op <tokenRef>', 'Read token via 1Password CLI (op read). Pair with --op-secret <ref>')
54
- .option('--op-secret <secretRef>', '1Password reference for the secret, used with --from-op')
53
+ .option('--from-env-file <path>', 'Read SWITCHBOT_TOKEN and SWITCHBOT_SECRET from a dotenv file', stringArg('--from-env-file'))
54
+ .option('--from-op <tokenRef>', 'Read token via 1Password CLI (op read). Pair with --op-secret <ref>', stringArg('--from-op'))
55
+ .option('--op-secret <secretRef>', '1Password reference for the secret, used with --from-op', stringArg('--op-secret'))
55
56
  .addHelpText('after', `
56
57
  Examples:
57
58
  $ switchbot config set-token <token> <secret>
@@ -1,3 +1,4 @@
1
+ import { stringArg } from '../utils/arg-parsers.js';
1
2
  import { handleError, isJsonMode, printJson, printTable, UsageError } from '../utils/output.js';
2
3
  import { loadDeviceMeta, setDeviceMeta, clearDeviceMeta, getDeviceMeta, getMetaFilePath, } from '../devices/device-meta.js';
3
4
  export function registerDevicesMetaCommand(devices) {
@@ -9,10 +10,10 @@ export function registerDevicesMetaCommand(devices) {
9
10
  .command('set')
10
11
  .description('Set local metadata for a device (alias, hide/show, notes)')
11
12
  .argument('<deviceId>', 'Target device ID')
12
- .option('--alias <name>', 'Local alias for the device (used with --name flag)')
13
+ .option('--alias <name>', 'Local alias for the device (used with --name flag)', stringArg('--alias'))
13
14
  .option('--hide', 'Hide this device from "devices list"')
14
15
  .option('--show', 'Un-hide this device')
15
- .option('--notes <text>', 'Freeform notes shown in "devices describe"')
16
+ .option('--notes <text>', 'Freeform notes shown in "devices describe"', stringArg('--notes'))
16
17
  .action((deviceId, options) => {
17
18
  try {
18
19
  if (options.hide && options.show) {