@switchbot/openapi-cli 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/commands/batch.js +7 -5
- package/dist/commands/cache.js +3 -1
- 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 +56 -15
- package/dist/commands/events.js +30 -17
- package/dist/commands/expand.js +11 -86
- package/dist/commands/history.js +4 -3
- package/dist/commands/mcp.js +6 -5
- package/dist/commands/schema.js +6 -3
- package/dist/commands/watch.js +4 -3
- package/dist/commands/webhook.js +2 -1
- package/dist/config.js +7 -2
- package/dist/devices/param-validator.js +263 -0
- package/dist/index.js +48 -19
- package/dist/lib/devices.js +26 -14
- package/dist/utils/arg-parsers.js +65 -0
- package/package.json +1 -1
package/dist/lib/devices.js
CHANGED
|
@@ -129,8 +129,10 @@ export async function executeCommand(deviceId, cmd, parameter, commandType, clie
|
|
|
129
129
|
/**
|
|
130
130
|
* Validate a command against the locally-cached device → catalog mapping.
|
|
131
131
|
* Returns `{ ok: true }` when validation passes or is skipped (unknown device,
|
|
132
|
-
* custom IR button, etc.)
|
|
133
|
-
*
|
|
132
|
+
* custom IR button, etc.). On a case-only mismatch the canonical command name
|
|
133
|
+
* is returned via `normalized` along with a `caseNormalizedFrom` field so the
|
|
134
|
+
* caller can emit a warning and continue with the canonical name.
|
|
135
|
+
* Returns `{ ok: false, error }` only when the caller should refuse the call.
|
|
134
136
|
*/
|
|
135
137
|
export function validateCommand(deviceId, cmd, parameter, commandType) {
|
|
136
138
|
if (commandType === 'customize')
|
|
@@ -144,24 +146,34 @@ export function validateCommand(deviceId, cmd, parameter, commandType) {
|
|
|
144
146
|
const builtinCommands = match.commands.filter((c) => c.commandType !== 'customize');
|
|
145
147
|
if (builtinCommands.length === 0)
|
|
146
148
|
return { ok: true };
|
|
147
|
-
|
|
149
|
+
let spec = builtinCommands.find((c) => c.command === cmd);
|
|
150
|
+
let caseNormalizedFrom;
|
|
151
|
+
let normalizedCmd = cmd;
|
|
148
152
|
if (!spec) {
|
|
149
153
|
const unique = [...new Set(builtinCommands.map((c) => c.command))];
|
|
150
154
|
const caseMatch = unique.find((c) => c.toLowerCase() === cmd.toLowerCase());
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
if (caseMatch) {
|
|
156
|
+
// Case-only mismatch: normalize and continue.
|
|
157
|
+
caseNormalizedFrom = cmd;
|
|
158
|
+
normalizedCmd = caseMatch;
|
|
159
|
+
spec = builtinCommands.find((c) => c.command === caseMatch);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const hint = `Supported commands: ${unique.join(', ')}`;
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
error: new CommandValidationError(`"${cmd}" is not a supported command for ${cached.name} (${cached.type}).`, 'unknown-command', hint),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
158
168
|
}
|
|
169
|
+
if (!spec)
|
|
170
|
+
return { ok: true, normalized: normalizedCmd, caseNormalizedFrom };
|
|
159
171
|
const noParamExpected = spec.parameter === '—';
|
|
160
172
|
const userProvidedParam = parameter !== undefined && parameter !== 'default';
|
|
161
173
|
if (noParamExpected && userProvidedParam) {
|
|
162
174
|
return {
|
|
163
175
|
ok: false,
|
|
164
|
-
error: new CommandValidationError(`"${
|
|
176
|
+
error: new CommandValidationError(`"${normalizedCmd}" takes no parameter, but one was provided: "${parameter}".`, 'unexpected-parameter', `Try: switchbot devices command ${deviceId} ${normalizedCmd}`),
|
|
165
177
|
};
|
|
166
178
|
}
|
|
167
179
|
// Warn when a parameter is required but the user omitted it
|
|
@@ -170,12 +182,12 @@ export function validateCommand(deviceId, cmd, parameter, commandType) {
|
|
|
170
182
|
const example = spec.exampleParams?.[0];
|
|
171
183
|
return {
|
|
172
184
|
ok: false,
|
|
173
|
-
error: new CommandValidationError(`"${
|
|
174
|
-
? `Example: switchbot devices command <deviceId> ${
|
|
185
|
+
error: new CommandValidationError(`"${normalizedCmd}" requires a parameter (${spec.parameter}).`, 'missing-parameter', example
|
|
186
|
+
? `Example: switchbot devices command <deviceId> ${normalizedCmd} "${example}"`
|
|
175
187
|
: `See: switchbot devices commands ${cached.type}`),
|
|
176
188
|
};
|
|
177
189
|
}
|
|
178
|
-
return { ok: true };
|
|
190
|
+
return { ok: true, normalized: normalizedCmd, caseNormalizedFrom };
|
|
179
191
|
}
|
|
180
192
|
/**
|
|
181
193
|
* Inspect catalog annotations to decide whether a command is destructive,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { InvalidArgumentError } from 'commander';
|
|
2
|
+
import { parseDurationToMs } from './flags.js';
|
|
3
|
+
/**
|
|
4
|
+
* Commander argParser callbacks that fail fast when a required-value flag
|
|
5
|
+
* swallows the next token (another flag, a subcommand name, etc.) — the
|
|
6
|
+
* default Commander behavior is to take the next argv token verbatim.
|
|
7
|
+
*
|
|
8
|
+
* Use `--flag=<val>` form to pass values that legitimately start with `--`.
|
|
9
|
+
*/
|
|
10
|
+
export function intArg(flagName, opts) {
|
|
11
|
+
return (value) => {
|
|
12
|
+
// Flag-like tokens (`--something`, `-x`) are rejected up-front.
|
|
13
|
+
// Pure negative integers (`-1`, `-42`) fall through to min/max so the
|
|
14
|
+
// error classifies as a range error rather than "requires a numeric value".
|
|
15
|
+
if (value.startsWith('-') && !/^-\d+$/.test(value)) {
|
|
16
|
+
throw new InvalidArgumentError(`${flagName} requires a numeric value, got "${value}". ` +
|
|
17
|
+
`Did you forget a value? Use ${flagName}=<n> if the value really starts with "-".`);
|
|
18
|
+
}
|
|
19
|
+
const n = Number(value);
|
|
20
|
+
if (!Number.isInteger(n)) {
|
|
21
|
+
throw new InvalidArgumentError(`${flagName} must be an integer (got "${value}")`);
|
|
22
|
+
}
|
|
23
|
+
if (opts?.min !== undefined && n < opts.min) {
|
|
24
|
+
throw new InvalidArgumentError(`${flagName} must be >= ${opts.min} (got "${value}")`);
|
|
25
|
+
}
|
|
26
|
+
if (opts?.max !== undefined && n > opts.max) {
|
|
27
|
+
throw new InvalidArgumentError(`${flagName} must be <= ${opts.max} (got "${value}")`);
|
|
28
|
+
}
|
|
29
|
+
return String(n);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function durationArg(flagName) {
|
|
33
|
+
return (value) => {
|
|
34
|
+
if (value.startsWith('-')) {
|
|
35
|
+
throw new InvalidArgumentError(`${flagName} requires a duration value, got "${value}". ` +
|
|
36
|
+
`Use ${flagName}=<dur> if the value really starts with "-".`);
|
|
37
|
+
}
|
|
38
|
+
const ms = parseDurationToMs(value);
|
|
39
|
+
if (ms === null) {
|
|
40
|
+
throw new InvalidArgumentError(`${flagName} must look like "30s", "1m", "500ms", "1h" (got "${value}")`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function stringArg(flagName, opts) {
|
|
46
|
+
return (value) => {
|
|
47
|
+
if (value.startsWith('--')) {
|
|
48
|
+
throw new InvalidArgumentError(`${flagName} requires a value. "${value}" looks like another option — ` +
|
|
49
|
+
`did you forget the value? Use ${flagName}=<val> if your value really starts with "--".`);
|
|
50
|
+
}
|
|
51
|
+
if (opts?.disallow?.includes(value)) {
|
|
52
|
+
throw new InvalidArgumentError(`${flagName} requires a value but got "${value}", which is a subcommand name. ` +
|
|
53
|
+
`Did you forget the value? Use ${flagName}=<val> or put ${flagName} after the subcommand.`);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function enumArg(flagName, allowed) {
|
|
59
|
+
return (value) => {
|
|
60
|
+
if (!allowed.includes(value)) {
|
|
61
|
+
throw new InvalidArgumentError(`${flagName} must be one of: ${allowed.join(', ')} (got "${value}")`);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
};
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@switchbot/openapi-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "SwitchBot smart home CLI — control devices, run scenes, stream real-time events, and integrate AI agents via MCP. Full API v1.1 coverage.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"switchbot",
|