Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ switchbot --help
switchbot devices command --help
```

> **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`.

### `--dry-run`

Intercepts every non-GET request: the CLI prints the URL/body it would have
Expand Down Expand Up @@ -206,6 +208,7 @@ switchbot config list-profiles # List saved profiles
# Default columns (4): deviceId, deviceName, type, category
# Pass --wide for the full 10-column operator view
switchbot devices list
switchbot devices ls # short alias for 'list'
switchbot devices list --wide
switchbot devices list --json | jq '.deviceList[].deviceId'

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@switchbot/openapi-cli",
"version": "2.2.0",
"version": "2.2.1",
"description": "SwitchBot smart home CLI — control devices, run scenes, stream real-time events, and integrate AI agents via MCP. Full API v1.1 coverage.",
"keywords": [
"switchbot",
Expand Down
12 changes: 7 additions & 5 deletions src/commands/batch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Command } from 'commander';
import type { AxiosInstance } from 'axios';
import { intArg, enumArg, stringArg } from '../utils/arg-parsers.js';
import { printJson, isJsonMode, handleError, buildErrorPayload, type ErrorPayload } from '../utils/output.js';
import {
fetchDeviceList,
Expand Down Expand Up @@ -28,6 +29,7 @@ interface BatchResult {
}

const DEFAULT_CONCURRENCY = 5;
const COMMAND_TYPES = ['command', 'customize'] as const;

/** Run `task(x)` for every element with at most `concurrency` running at once. */
async function runPool<T, R>(
Expand Down Expand Up @@ -120,13 +122,13 @@ export function registerBatchCommand(devices: Command): void {
.description('Send the same command to many devices in one run (filter- or stdin-driven)')
.argument('<command>', 'Command name, e.g. turnOn, turnOff, setBrightness')
.argument('[parameter]', 'Command parameter (same rules as `devices command`; omit for no-arg)')
.option('--filter <expr>', 'Target devices matching a filter, e.g. type=Bot,family=Home')
.option('--ids <csv>', 'Explicit comma-separated list of deviceIds')
.option('--concurrency <n>', 'Max parallel in-flight requests (default 5)', '5')
.option('--filter <expr>', 'Target devices matching a filter, e.g. type=Bot,family=Home', stringArg('--filter'))
.option('--ids <csv>', 'Explicit comma-separated list of deviceIds', stringArg('--ids'))
.option('--concurrency <n>', 'Max parallel in-flight requests (default 5)', intArg('--concurrency', { min: 1 }), '5')
.option('--yes', 'Allow destructive commands (Smart Lock unlock, garage open, ...)')
.option('--type <commandType>', '"command" (default) or "customize" for user-defined IR buttons', 'command')
.option('--type <commandType>', '"command" (default) or "customize" for user-defined IR buttons', enumArg('--type', COMMAND_TYPES), 'command')
.option('--stdin', 'Read deviceIds from stdin, one per line (same as trailing "-")')
.option('--idempotency-key-prefix <prefix>', 'Prefix for idempotency keys (key per device: <prefix>-<deviceId>)')
.option('--idempotency-key-prefix <prefix>', 'Prefix for idempotency keys (key per device: <prefix>-<deviceId>)', stringArg('--idempotency-key-prefix'))
.addHelpText('after', `
Targets are resolved in this priority order:
1. --ids when present (explicit deviceIds)
Expand Down
4 changes: 3 additions & 1 deletion src/commands/cache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from 'commander';
import { enumArg } from '../utils/arg-parsers.js';
import { printJson, isJsonMode, handleError, UsageError } from '../utils/output.js';
import {
clearCache,
Expand All @@ -19,6 +20,7 @@ function formatAge(ms?: number): string {
}

export function registerCacheCommand(program: Command): void {
const CACHE_KEYS = ['list', 'status', 'all'] as const;
const cache = program
.command('cache')
.description('Inspect and manage the local SwitchBot CLI caches')
Expand Down Expand Up @@ -88,7 +90,7 @@ Examples:
cache
.command('clear')
.description('Delete cache files')
.option('--key <which>', 'Which cache to clear: "list" | "status" | "all" (default)', 'all')
.option('--key <which>', 'Which cache to clear: "list" | "status" | "all" (default)', enumArg('--key', CACHE_KEYS), 'all')
.action((options: { key: string }) => {
try {
const key = options.key;
Expand Down
4 changes: 3 additions & 1 deletion src/commands/catalog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from 'commander';
import { enumArg } from '../utils/arg-parsers.js';
import { printTable, printJson, isJsonMode, handleError, UsageError } from '../utils/output.js';
import { resolveFormat, resolveFields, renderRows } from '../utils/format.js';
import {
Expand All @@ -12,6 +13,7 @@ import {
} from '../devices/catalog.js';

export function registerCatalogCommand(program: Command): void {
const SOURCES = ['built-in', 'overlay', 'effective'] as const;
const catalog = program
.command('catalog')
.description('Inspect the built-in device catalog and any local overlay')
Expand Down Expand Up @@ -76,7 +78,7 @@ Examples:
.command('show')
.description("Show the effective catalog (or one entry). Defaults to 'effective' source.")
.argument('[type...]', 'Optional device type/alias (case-insensitive, partial match)')
.option('--source <source>', 'Which catalog to show: built-in | overlay | effective (default)', 'effective')
.option('--source <source>', 'Which catalog to show: built-in | overlay | effective (default)', enumArg('--source', SOURCES), 'effective')
.action((typeParts: string[], options: { source: string }) => {
try {
const source = options.source;
Expand Down
151 changes: 139 additions & 12 deletions src/commands/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ _switchbot_completion() {
cword="\${COMP_CWORD}"
}

local top_cmds="config devices scenes webhook completion help"
local config_sub="set-token show"
local devices_sub="list status command types commands"
local top_cmds="config devices scenes webhook completion mcp quota catalog cache events doctor schema history plan capabilities help"
local config_sub="set-token show list-profiles"
local devices_sub="list ls status command types commands describe batch watch explain expand meta"
local scenes_sub="list execute"
local webhook_sub="setup query update delete"
local events_sub="tail mqtt-tail"
local quota_sub="status reset"
local catalog_sub="path show diff refresh"
local cache_sub="show clear"
local history_sub="show replay"
local plan_sub="schema validate run"
local completion_shells="bash zsh fish powershell"
local global_opts="--json --verbose -v --dry-run --timeout --config --help -h --version -V"
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"

if [[ \${cword} -eq 1 ]]; then
COMPREPLY=( $(compgen -W "\${top_cmds} \${global_opts}" -- "\${cur}") )
Expand All @@ -45,6 +51,36 @@ _switchbot_completion() {
COMPREPLY=( $(compgen -W "\${scenes_sub}" -- "\${cur}") )
fi
;;
events)
if [[ \${cword} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "\${events_sub}" -- "\${cur}") )
fi
;;
quota)
if [[ \${cword} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "\${quota_sub}" -- "\${cur}") )
fi
;;
catalog)
if [[ \${cword} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "\${catalog_sub}" -- "\${cur}") )
fi
;;
cache)
if [[ \${cword} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "\${cache_sub}" -- "\${cur}") )
fi
;;
history)
if [[ \${cword} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "\${history_sub}" -- "\${cur}") )
fi
;;
plan)
if [[ \${cword} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "\${plan_sub}" -- "\${cur}") )
fi
;;
webhook)
if [[ \${cword} -eq 2 ]]; then
COMPREPLY=( $(compgen -W "\${webhook_sub}" -- "\${cur}") )
Expand Down Expand Up @@ -72,22 +108,39 @@ const ZSH_SCRIPT = `# switchbot zsh completion
# source <(switchbot completion zsh)

_switchbot() {
local -a top_cmds config_sub devices_sub scenes_sub webhook_sub completion_shells
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
top_cmds=(
'config:Manage API credentials'
'devices:List and control devices'
'scenes:List and execute scenes'
'webhook:Manage webhook configuration'
'completion:Print a shell completion script'
'mcp:Run the MCP server'
'quota:Inspect local request quota'
'catalog:Inspect the built-in device catalog'
'cache:Inspect local caches'
'events:Receive webhook or MQTT events'
'doctor:Run self-checks'
'schema:Export the device catalog as JSON'
'history:View and replay audited commands'
'plan:Validate and run batch plans'
'capabilities:Print a machine-readable manifest'
'help:Show help for a command'
)
config_sub=('set-token:Save token + secret' 'show:Show current credential source')
config_sub=('set-token:Save token + secret' 'show:Show current credential source' 'list-profiles:List named credential profiles')
devices_sub=(
'list:List all devices'
'ls:Alias for list'
'status:Query device status'
'command:Send a control command'
'types:List known device types (offline)'
'commands:Show commands for a device type (offline)'
'describe:Show metadata + supported commands for one device'
'batch:Send one command to many devices'
'watch:Poll device status and emit changes'
'explain:One-shot device summary'
'expand:Build wire-format params from semantic flags'
'meta:Manage local device metadata'
)
scenes_sub=('list:List manual scenes' 'execute:Run a scene')
webhook_sub=(
Expand All @@ -96,15 +149,32 @@ _switchbot() {
'update:Enable/disable a webhook'
'delete:Delete a webhook'
)
events_sub=('tail:Run a local webhook receiver' 'mqtt-tail:Stream MQTT shadow events')
quota_sub=('status:Show today and recent quota usage' 'reset:Delete the local quota counter')
catalog_sub=('path:Show overlay path' 'show:Show built-in/overlay/effective catalog' 'diff:Show overlay changes' 'refresh:Clear overlay cache')
cache_sub=('show:Summarize cache files' 'clear:Delete cache files')
history_sub=('show:Print recent audit entries' 'replay:Re-run one audited command')
plan_sub=('schema:Print the plan schema' 'validate:Validate a plan file' 'run:Validate and execute a plan')
completion_shells=('bash' 'zsh' 'fish' 'powershell')

local global_opts
global_opts=(
'--json[Raw JSON output]'
'--format[Output format]:type:(table json jsonl tsv yaml id)'
'--fields[Comma-separated output columns]:csv:'
'(-v --verbose)'{-v,--verbose}'[Log HTTP details to stderr]'
'--dry-run[Print mutating requests without sending]'
'--timeout[HTTP timeout in ms]:ms:'
'--retry-on-429[Max 429 retries]:n:'
'--backoff[Retry backoff strategy]:strategy:(linear exponential)'
'--no-retry[Disable 429 retries]'
'--no-quota[Disable the local quota counter]'
'--cache[Cache mode]:mode:'
'--no-cache[Disable cache reads]'
'--config[Override credential file path]:path:_files'
'--profile[Use a named credential profile]:name:'
'--audit-log[Append mutating commands to ~/.switchbot/audit.log]'
'--audit-log-path[Custom audit log file path]:path:_files'
'(-h --help)'{-h,--help}'[Show help]'
'(-V --version)'{-V,--version}'[Show version]'
)
Expand All @@ -125,6 +195,12 @@ _switchbot() {
devices) _describe 'devices' devices_sub ;;
scenes) _describe 'scenes' scenes_sub ;;
webhook) _describe 'webhook' webhook_sub ;;
events) _describe 'events' events_sub ;;
quota) _describe 'quota' quota_sub ;;
catalog) _describe 'catalog' catalog_sub ;;
cache) _describe 'cache' cache_sub ;;
history) _describe 'history' history_sub ;;
plan) _describe 'plan' plan_sub ;;
completion) _values 'shell' $completion_shells ;;
esac
;;
Expand All @@ -147,10 +223,21 @@ complete -c switchbot -f

# Global options
complete -c switchbot -l json -d 'Raw JSON output'
complete -c switchbot -l format -r -d 'Output format'
complete -c switchbot -l fields -r -d 'Comma-separated output columns'
complete -c switchbot -s v -l verbose -d 'Log HTTP details to stderr'
complete -c switchbot -l dry-run -d 'Print mutating requests without sending'
complete -c switchbot -l timeout -r -d 'HTTP timeout in ms'
complete -c switchbot -l retry-on-429 -r -d 'Max 429 retries'
complete -c switchbot -l backoff -r -d 'Retry backoff strategy'
complete -c switchbot -l no-retry -d 'Disable 429 retries'
complete -c switchbot -l no-quota -d 'Disable the local quota counter'
complete -c switchbot -l cache -r -d 'Cache mode'
complete -c switchbot -l no-cache -d 'Disable cache reads'
complete -c switchbot -l config -r -d 'Credential file path'
complete -c switchbot -l profile -r -d 'Named credential profile'
complete -c switchbot -l audit-log -d 'Append mutating commands to audit log'
complete -c switchbot -l audit-log-path -r -d 'Custom audit log file path'
complete -c switchbot -s h -l help -d 'Show help'
complete -c switchbot -s V -l version -d 'Show version'

Expand All @@ -160,13 +247,23 @@ complete -c switchbot -n '__fish_use_subcommand' -a 'devices' -d 'List and co
complete -c switchbot -n '__fish_use_subcommand' -a 'scenes' -d 'List and execute scenes'
complete -c switchbot -n '__fish_use_subcommand' -a 'webhook' -d 'Manage webhook configuration'
complete -c switchbot -n '__fish_use_subcommand' -a 'completion' -d 'Print a shell completion script'
complete -c switchbot -n '__fish_use_subcommand' -a 'mcp' -d 'Run the MCP server'
complete -c switchbot -n '__fish_use_subcommand' -a 'quota' -d 'Inspect local request quota'
complete -c switchbot -n '__fish_use_subcommand' -a 'catalog' -d 'Inspect the built-in device catalog'
complete -c switchbot -n '__fish_use_subcommand' -a 'cache' -d 'Inspect local caches'
complete -c switchbot -n '__fish_use_subcommand' -a 'events' -d 'Receive webhook or MQTT events'
complete -c switchbot -n '__fish_use_subcommand' -a 'doctor' -d 'Run self-checks'
complete -c switchbot -n '__fish_use_subcommand' -a 'schema' -d 'Export the device catalog as JSON'
complete -c switchbot -n '__fish_use_subcommand' -a 'history' -d 'View and replay audited commands'
complete -c switchbot -n '__fish_use_subcommand' -a 'plan' -d 'Validate and run batch plans'
complete -c switchbot -n '__fish_use_subcommand' -a 'capabilities' -d 'Print a machine-readable manifest'
complete -c switchbot -n '__fish_use_subcommand' -a 'help' -d 'Show help'

# config
complete -c switchbot -n '__fish_seen_subcommand_from config' -a 'set-token show'
complete -c switchbot -n '__fish_seen_subcommand_from config' -a 'set-token show list-profiles'

# devices
complete -c switchbot -n '__fish_seen_subcommand_from devices' -a 'list status command types commands'
complete -c switchbot -n '__fish_seen_subcommand_from devices' -a 'list ls status command types commands describe batch watch explain expand meta'

# scenes
complete -c switchbot -n '__fish_seen_subcommand_from scenes' -a 'list execute'
Expand All @@ -176,6 +273,24 @@ complete -c switchbot -n '__fish_seen_subcommand_from webhook' -a 'setup query u
complete -c switchbot -n '__fish_seen_subcommand_from webhook; and __fish_seen_subcommand_from update' -l enable -d 'Enable the webhook'
complete -c switchbot -n '__fish_seen_subcommand_from webhook; and __fish_seen_subcommand_from update' -l disable -d 'Disable the webhook'

# events
complete -c switchbot -n '__fish_seen_subcommand_from events' -a 'tail mqtt-tail'

# quota
complete -c switchbot -n '__fish_seen_subcommand_from quota' -a 'status reset'

# catalog
complete -c switchbot -n '__fish_seen_subcommand_from catalog' -a 'path show diff refresh'

# cache
complete -c switchbot -n '__fish_seen_subcommand_from cache' -a 'show clear'

# history
complete -c switchbot -n '__fish_seen_subcommand_from history' -a 'show replay'

# plan
complete -c switchbot -n '__fish_seen_subcommand_from plan' -a 'schema validate run'

# completion
complete -c switchbot -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish powershell'
`;
Expand All @@ -191,13 +306,19 @@ Register-ArgumentCompleter -Native -CommandName switchbot -ScriptBlock {
$tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
$count = $tokens.Count

$top = 'config','devices','scenes','webhook','completion','help'
$configSub = 'set-token','show'
$devicesSub = 'list','status','command','types','commands'
$top = 'config','devices','scenes','webhook','completion','mcp','quota','catalog','cache','events','doctor','schema','history','plan','capabilities','help'
$configSub = 'set-token','show','list-profiles'
$devicesSub = 'list','ls','status','command','types','commands','describe','batch','watch','explain','expand','meta'
$scenesSub = 'list','execute'
$webhookSub = 'setup','query','update','delete'
$eventsSub = 'tail','mqtt-tail'
$quotaSub = 'status','reset'
$catalogSub = 'path','show','diff','refresh'
$cacheSub = 'show','clear'
$historySub = 'show','replay'
$planSub = 'schema','validate','run'
$shells = 'bash','zsh','fish','powershell'
$globalOpts = '--json','--verbose','-v','--dry-run','--timeout','--config','--help','-h','--version','-V'
$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'

function _emit($values) {
$values |
Expand All @@ -211,6 +332,12 @@ Register-ArgumentCompleter -Native -CommandName switchbot -ScriptBlock {
'config' { if ($count -eq 3) { return _emit $configSub } }
'devices' { if ($count -eq 3) { return _emit $devicesSub } }
'scenes' { if ($count -eq 3) { return _emit $scenesSub } }
'events' { if ($count -eq 3) { return _emit $eventsSub } }
'quota' { if ($count -eq 3) { return _emit $quotaSub } }
'catalog' { if ($count -eq 3) { return _emit $catalogSub } }
'cache' { if ($count -eq 3) { return _emit $cacheSub } }
'history' { if ($count -eq 3) { return _emit $historySub } }
'plan' { if ($count -eq 3) { return _emit $planSub } }
'webhook' {
if ($count -eq 3) { return _emit $webhookSub }
if ($tokens[2] -eq 'update') { return _emit ('--enable','--disable' + $globalOpts) }
Expand Down
Loading
Loading