@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 +58 -3
- package/dist/commands/batch.js +7 -5
- package/dist/commands/cache.js +3 -1
- package/dist/commands/capabilities.js +30 -20
- package/dist/commands/catalog.js +3 -1
- package/dist/commands/completion.js +139 -12
- package/dist/commands/config.js +4 -3
- package/dist/commands/device-meta.js +3 -2
- package/dist/commands/devices.js +217 -71
- package/dist/commands/events.js +133 -15
- package/dist/commands/expand.js +29 -11
- package/dist/commands/history.js +4 -3
- package/dist/commands/mcp.js +41 -5
- package/dist/commands/plan.js +10 -2
- package/dist/commands/scenes.js +1 -1
- package/dist/commands/schema.js +6 -3
- package/dist/commands/watch.js +16 -4
- package/dist/commands/webhook.js +2 -1
- package/dist/config.js +7 -2
- package/dist/index.js +49 -19
- package/dist/lib/devices.js +16 -1
- package/dist/mcp/device-history.js +66 -0
- package/dist/mcp/events-subscription.js +10 -3
- package/dist/mqtt/client.js +8 -0
- package/dist/mqtt/credential.js +3 -2
- package/dist/sinks/dispatcher.js +12 -0
- package/dist/sinks/file.js +19 -0
- package/dist/sinks/format.js +56 -0
- package/dist/sinks/homeassistant.js +44 -0
- package/dist/sinks/openclaw.js +33 -0
- package/dist/sinks/stdout.js +5 -0
- package/dist/sinks/telegram.js +28 -0
- package/dist/sinks/types.js +1 -0
- package/dist/sinks/webhook.js +22 -0
- package/dist/utils/arg-parsers.js +62 -0
- package/dist/utils/flags.js +13 -12
- package/dist/utils/format.js +6 -5
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
package/dist/commands/batch.js
CHANGED
|
@@ -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)
|
package/dist/commands/cache.js
CHANGED
|
@@ -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
|
-
.
|
|
35
|
+
.option('--minimal', 'Omit per-subcommand flag details to reduce output size')
|
|
36
|
+
.action((opts) => {
|
|
35
37
|
const catalog = getEffectiveCatalog();
|
|
36
|
-
const
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
name:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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,
|
package/dist/commands/catalog.js
CHANGED
|
@@ -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) }
|
package/dist/commands/config.js
CHANGED
|
@@ -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) {
|