@switchbot/openapi-cli 2.6.4 → 3.0.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 +385 -103
- package/dist/api/client.js +13 -12
- package/dist/commands/agent-bootstrap.js +67 -16
- package/dist/commands/auth.js +354 -0
- package/dist/commands/batch.js +26 -21
- package/dist/commands/capabilities.js +29 -21
- package/dist/commands/catalog.js +4 -3
- package/dist/commands/config.js +57 -37
- package/dist/commands/devices.js +63 -37
- package/dist/commands/doctor.js +539 -26
- package/dist/commands/events.js +115 -26
- package/dist/commands/expand.js +7 -15
- package/dist/commands/explain.js +10 -7
- package/dist/commands/history.js +12 -18
- package/dist/commands/identity.js +59 -0
- package/dist/commands/install.js +246 -0
- package/dist/commands/mcp.js +895 -15
- package/dist/commands/plan.js +111 -15
- package/dist/commands/policy.js +469 -0
- package/dist/commands/rules.js +657 -0
- package/dist/commands/schema.js +20 -12
- package/dist/commands/status-sync.js +131 -0
- package/dist/commands/uninstall.js +237 -0
- package/dist/commands/watch.js +15 -2
- package/dist/config.js +14 -0
- package/dist/credentials/backends/file.js +101 -0
- package/dist/credentials/backends/linux.js +129 -0
- package/dist/credentials/backends/macos.js +129 -0
- package/dist/credentials/backends/windows.js +215 -0
- package/dist/credentials/keychain.js +88 -0
- package/dist/credentials/prime.js +52 -0
- package/dist/devices/catalog.js +118 -11
- package/dist/devices/resources.js +270 -0
- package/dist/index.js +39 -4
- package/dist/install/default-steps.js +257 -0
- package/dist/install/preflight.js +212 -0
- package/dist/install/steps.js +67 -0
- package/dist/lib/command-keywords.js +17 -0
- package/dist/lib/devices.js +15 -5
- package/dist/policy/add-rule.js +124 -0
- package/dist/policy/diff.js +91 -0
- package/dist/policy/examples/policy.example.yaml +99 -0
- package/dist/policy/format.js +57 -0
- package/dist/policy/load.js +61 -0
- package/dist/policy/migrate.js +67 -0
- package/dist/policy/schema/v0.2.json +302 -0
- package/dist/policy/schema.js +18 -0
- package/dist/policy/validate.js +262 -0
- package/dist/rules/action.js +205 -0
- package/dist/rules/audit-query.js +89 -0
- package/dist/rules/cron-scheduler.js +186 -0
- package/dist/rules/destructive.js +52 -0
- package/dist/rules/engine.js +567 -0
- package/dist/rules/matcher.js +230 -0
- package/dist/rules/pid-file.js +95 -0
- package/dist/rules/quiet-hours.js +45 -0
- package/dist/rules/suggest.js +95 -0
- package/dist/rules/throttle.js +78 -0
- package/dist/rules/types.js +34 -0
- package/dist/rules/webhook-listener.js +223 -0
- package/dist/rules/webhook-token.js +90 -0
- package/dist/schema/field-aliases.js +95 -0
- package/dist/status-sync/manager.js +268 -0
- package/dist/utils/audit.js +12 -2
- package/dist/utils/help-json.js +54 -0
- package/dist/utils/output.js +17 -0
- package/package.json +12 -4
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { tryLoadConfig } from '../config.js';
|
|
6
|
+
import { getActiveProfile } from '../lib/request-context.js';
|
|
7
|
+
import { UsageError } from '../utils/output.js';
|
|
8
|
+
import { getConfigPath } from '../utils/flags.js';
|
|
9
|
+
const DEFAULT_OPENCLAW_URL = 'http://localhost:18789';
|
|
10
|
+
function resolveStatusSyncRuntime(options) {
|
|
11
|
+
if (!tryLoadConfig()) {
|
|
12
|
+
throw new UsageError('No credentials found. Run \'switchbot config set-token\' or set SWITCHBOT_TOKEN and SWITCHBOT_SECRET.');
|
|
13
|
+
}
|
|
14
|
+
const openclawToken = options.openclawToken ?? process.env.OPENCLAW_TOKEN;
|
|
15
|
+
if (!openclawToken) {
|
|
16
|
+
throw new UsageError('--openclaw-token is required or set OPENCLAW_TOKEN in the environment.');
|
|
17
|
+
}
|
|
18
|
+
const openclawModel = options.openclawModel ?? process.env.OPENCLAW_MODEL;
|
|
19
|
+
if (!openclawModel) {
|
|
20
|
+
throw new UsageError('--openclaw-model is required or set OPENCLAW_MODEL in the environment.');
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
openclawUrl: options.openclawUrl ?? process.env.OPENCLAW_URL ?? DEFAULT_OPENCLAW_URL,
|
|
24
|
+
openclawToken,
|
|
25
|
+
openclawModel,
|
|
26
|
+
...(options.topic ? { topic: options.topic } : {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function resolveStatusSyncPaths(explicitStateDir) {
|
|
30
|
+
const stateDir = path.resolve(explicitStateDir
|
|
31
|
+
?? process.env.SWITCHBOT_STATUS_SYNC_HOME
|
|
32
|
+
?? path.join(os.homedir(), '.switchbot', 'status-sync'));
|
|
33
|
+
return {
|
|
34
|
+
stateDir,
|
|
35
|
+
stateFile: path.join(stateDir, 'state.json'),
|
|
36
|
+
stdoutLog: path.join(stateDir, 'stdout.log'),
|
|
37
|
+
stderrLog: path.join(stateDir, 'stderr.log'),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function buildStatusSyncChildArgs(options) {
|
|
41
|
+
const scriptPath = process.argv[1];
|
|
42
|
+
if (!scriptPath) {
|
|
43
|
+
throw new Error('Cannot determine the current CLI entrypoint path.');
|
|
44
|
+
}
|
|
45
|
+
const args = [path.resolve(scriptPath)];
|
|
46
|
+
const configPath = getConfigPath();
|
|
47
|
+
const profile = getActiveProfile();
|
|
48
|
+
if (configPath) {
|
|
49
|
+
args.push('--config', path.resolve(configPath));
|
|
50
|
+
}
|
|
51
|
+
else if (profile) {
|
|
52
|
+
args.push('--profile', profile);
|
|
53
|
+
}
|
|
54
|
+
args.push('events', 'mqtt-tail', '--sink', 'openclaw', '--openclaw-url', options.openclawUrl, '--openclaw-model', options.openclawModel);
|
|
55
|
+
if (options.topic) {
|
|
56
|
+
args.push('--topic', options.topic);
|
|
57
|
+
}
|
|
58
|
+
return args;
|
|
59
|
+
}
|
|
60
|
+
function safeUnlink(filePath) {
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(filePath);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// best-effort cleanup
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function isProcessRunning(pid) {
|
|
69
|
+
try {
|
|
70
|
+
process.kill(pid, 0);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const code = err.code;
|
|
75
|
+
if (code === 'EPERM')
|
|
76
|
+
return true;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function readStateFile(paths) {
|
|
81
|
+
if (!fs.existsSync(paths.stateFile))
|
|
82
|
+
return null;
|
|
83
|
+
try {
|
|
84
|
+
const raw = JSON.parse(fs.readFileSync(paths.stateFile, 'utf-8'));
|
|
85
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
86
|
+
safeUnlink(paths.stateFile);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const parsed = raw;
|
|
90
|
+
if (typeof parsed.pid !== 'number' ||
|
|
91
|
+
!Number.isInteger(parsed.pid) ||
|
|
92
|
+
parsed.pid < 1 ||
|
|
93
|
+
typeof parsed.startedAt !== 'string' ||
|
|
94
|
+
!Array.isArray(parsed.command) ||
|
|
95
|
+
typeof parsed.stdoutLog !== 'string' ||
|
|
96
|
+
typeof parsed.stderrLog !== 'string') {
|
|
97
|
+
safeUnlink(paths.stateFile);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
pid: parsed.pid,
|
|
102
|
+
startedAt: parsed.startedAt,
|
|
103
|
+
command: parsed.command.map(String),
|
|
104
|
+
openclawUrl: typeof parsed.openclawUrl === 'string' ? parsed.openclawUrl : DEFAULT_OPENCLAW_URL,
|
|
105
|
+
openclawModel: typeof parsed.openclawModel === 'string' ? parsed.openclawModel : '',
|
|
106
|
+
topic: typeof parsed.topic === 'string' ? parsed.topic : null,
|
|
107
|
+
configPath: typeof parsed.configPath === 'string' ? parsed.configPath : null,
|
|
108
|
+
profile: typeof parsed.profile === 'string' ? parsed.profile : null,
|
|
109
|
+
stdoutLog: parsed.stdoutLog,
|
|
110
|
+
stderrLog: parsed.stderrLog,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
safeUnlink(paths.stateFile);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function toStatus(paths, state, running) {
|
|
119
|
+
return {
|
|
120
|
+
running,
|
|
121
|
+
pid: running && state ? state.pid : null,
|
|
122
|
+
startedAt: running && state ? state.startedAt : null,
|
|
123
|
+
stateDir: paths.stateDir,
|
|
124
|
+
stateFile: paths.stateFile,
|
|
125
|
+
stdoutLog: state?.stdoutLog ?? paths.stdoutLog,
|
|
126
|
+
stderrLog: state?.stderrLog ?? paths.stderrLog,
|
|
127
|
+
command: running && state ? state.command : null,
|
|
128
|
+
openclawUrl: running && state ? state.openclawUrl : null,
|
|
129
|
+
openclawModel: running && state ? state.openclawModel : null,
|
|
130
|
+
topic: running && state ? state.topic : null,
|
|
131
|
+
configPath: running && state ? state.configPath : null,
|
|
132
|
+
profile: running && state ? state.profile : null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function killProcessTree(pid) {
|
|
136
|
+
if (process.platform === 'win32') {
|
|
137
|
+
const result = spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });
|
|
138
|
+
if (result.error)
|
|
139
|
+
throw result.error;
|
|
140
|
+
if (result.status !== 0 && isProcessRunning(pid)) {
|
|
141
|
+
throw new Error(`Failed to stop status-sync process tree (PID ${pid}).`);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
process.kill(-pid, 'SIGTERM');
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const code = err.code;
|
|
150
|
+
if (code === 'ESRCH') {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
process.kill(pid, 'SIGTERM');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function getStatusSyncStatus(options = {}) {
|
|
157
|
+
const paths = resolveStatusSyncPaths(options.stateDir);
|
|
158
|
+
const state = readStateFile(paths);
|
|
159
|
+
if (!state) {
|
|
160
|
+
return toStatus(paths, null, false);
|
|
161
|
+
}
|
|
162
|
+
if (!isProcessRunning(state.pid)) {
|
|
163
|
+
safeUnlink(paths.stateFile);
|
|
164
|
+
return toStatus(paths, null, false);
|
|
165
|
+
}
|
|
166
|
+
return toStatus(paths, state, true);
|
|
167
|
+
}
|
|
168
|
+
export function stopStatusSync(options = {}) {
|
|
169
|
+
const paths = resolveStatusSyncPaths(options.stateDir);
|
|
170
|
+
const state = readStateFile(paths);
|
|
171
|
+
if (!state) {
|
|
172
|
+
return {
|
|
173
|
+
stopped: false,
|
|
174
|
+
stale: false,
|
|
175
|
+
pid: null,
|
|
176
|
+
status: toStatus(paths, null, false),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (!isProcessRunning(state.pid)) {
|
|
180
|
+
safeUnlink(paths.stateFile);
|
|
181
|
+
return {
|
|
182
|
+
stopped: false,
|
|
183
|
+
stale: true,
|
|
184
|
+
pid: state.pid,
|
|
185
|
+
status: toStatus(paths, null, false),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
killProcessTree(state.pid);
|
|
189
|
+
if (isProcessRunning(state.pid)) {
|
|
190
|
+
throw new Error(`Failed to stop status-sync process (PID ${state.pid}); process is still running.`);
|
|
191
|
+
}
|
|
192
|
+
safeUnlink(paths.stateFile);
|
|
193
|
+
return {
|
|
194
|
+
stopped: true,
|
|
195
|
+
stale: false,
|
|
196
|
+
pid: state.pid,
|
|
197
|
+
status: toStatus(paths, null, false),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
export function startStatusSync(options = {}) {
|
|
201
|
+
const runtime = resolveStatusSyncRuntime(options);
|
|
202
|
+
const paths = resolveStatusSyncPaths(options.stateDir);
|
|
203
|
+
const existing = getStatusSyncStatus({ stateDir: paths.stateDir });
|
|
204
|
+
if (existing.running) {
|
|
205
|
+
if (!options.force) {
|
|
206
|
+
throw new UsageError(`status-sync is already running (PID ${existing.pid}). Run 'switchbot status-sync stop' first or re-run with --force.`);
|
|
207
|
+
}
|
|
208
|
+
stopStatusSync({ stateDir: paths.stateDir });
|
|
209
|
+
}
|
|
210
|
+
fs.mkdirSync(paths.stateDir, { recursive: true });
|
|
211
|
+
const configPath = getConfigPath();
|
|
212
|
+
const command = buildStatusSyncChildArgs(runtime);
|
|
213
|
+
let stdoutFd = null;
|
|
214
|
+
let stderrFd = null;
|
|
215
|
+
try {
|
|
216
|
+
stdoutFd = fs.openSync(paths.stdoutLog, 'a');
|
|
217
|
+
stderrFd = fs.openSync(paths.stderrLog, 'a');
|
|
218
|
+
const child = spawn(process.execPath, command, {
|
|
219
|
+
detached: true,
|
|
220
|
+
stdio: ['ignore', stdoutFd, stderrFd],
|
|
221
|
+
windowsHide: true,
|
|
222
|
+
env: { ...process.env, OPENCLAW_TOKEN: runtime.openclawToken },
|
|
223
|
+
});
|
|
224
|
+
if (!child.pid) {
|
|
225
|
+
throw new Error('Failed to start status-sync child process.');
|
|
226
|
+
}
|
|
227
|
+
child.unref();
|
|
228
|
+
const state = {
|
|
229
|
+
pid: child.pid,
|
|
230
|
+
startedAt: new Date().toISOString(),
|
|
231
|
+
command: [process.execPath, ...command],
|
|
232
|
+
openclawUrl: runtime.openclawUrl,
|
|
233
|
+
openclawModel: runtime.openclawModel,
|
|
234
|
+
topic: runtime.topic ?? null,
|
|
235
|
+
configPath: configPath ? path.resolve(configPath) : null,
|
|
236
|
+
profile: configPath ? null : (getActiveProfile() ?? null),
|
|
237
|
+
stdoutLog: paths.stdoutLog,
|
|
238
|
+
stderrLog: paths.stderrLog,
|
|
239
|
+
};
|
|
240
|
+
fs.writeFileSync(paths.stateFile, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
241
|
+
return toStatus(paths, state, true);
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
if (stdoutFd !== null)
|
|
245
|
+
fs.closeSync(stdoutFd);
|
|
246
|
+
if (stderrFd !== null)
|
|
247
|
+
fs.closeSync(stderrFd);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
export async function runStatusSyncForeground(options = {}) {
|
|
251
|
+
const runtime = resolveStatusSyncRuntime(options);
|
|
252
|
+
const command = buildStatusSyncChildArgs(runtime);
|
|
253
|
+
return await new Promise((resolve, reject) => {
|
|
254
|
+
const child = spawn(process.execPath, command, {
|
|
255
|
+
stdio: 'inherit',
|
|
256
|
+
windowsHide: true,
|
|
257
|
+
env: { ...process.env, OPENCLAW_TOKEN: runtime.openclawToken },
|
|
258
|
+
});
|
|
259
|
+
child.once('error', reject);
|
|
260
|
+
child.once('exit', (code, signal) => {
|
|
261
|
+
if (signal) {
|
|
262
|
+
resolve(1);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
resolve(code ?? 0);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
}
|
package/dist/utils/audit.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getAuditLog } from './flags.js';
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Bump when breaking changes to the audit line shape land.
|
|
6
|
+
*
|
|
7
|
+
* History:
|
|
8
|
+
* 1 — initial command audit (kind: 'command' only).
|
|
9
|
+
* 2 — adds rule-engine kinds ('rule-fire', 'rule-fire-dry',
|
|
10
|
+
* 'rule-throttled', 'rule-webhook-rejected') and a sibling `rule`
|
|
11
|
+
* block describing which rule fired and why. Reader stays backwards
|
|
12
|
+
* compatible: v1 lines parse as command entries with `rule`
|
|
13
|
+
* undefined.
|
|
14
|
+
*/
|
|
15
|
+
export const AUDIT_VERSION = 2;
|
|
6
16
|
function resolveAuditPath() {
|
|
7
17
|
const flag = getAuditLog();
|
|
8
18
|
if (flag === null)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { IDENTITY } from '../commands/identity.js';
|
|
2
|
+
export function commandToJson(cmd, opts = {}) {
|
|
3
|
+
const args = cmd.registeredArguments.map((a) => ({
|
|
4
|
+
name: a.name(),
|
|
5
|
+
required: a.required,
|
|
6
|
+
variadic: a.variadic,
|
|
7
|
+
description: a.description ?? '',
|
|
8
|
+
}));
|
|
9
|
+
const options = cmd.options
|
|
10
|
+
.filter((o) => o.long !== '--help' && o.long !== '--version')
|
|
11
|
+
.map((o) => {
|
|
12
|
+
const entry = { flags: o.flags, description: o.description ?? '' };
|
|
13
|
+
if (o.defaultValue !== undefined)
|
|
14
|
+
entry.defaultValue = o.defaultValue;
|
|
15
|
+
if (o.argChoices && o.argChoices.length > 0)
|
|
16
|
+
entry.choices = o.argChoices;
|
|
17
|
+
return entry;
|
|
18
|
+
});
|
|
19
|
+
const subcommands = cmd.commands
|
|
20
|
+
.filter((c) => !c.name().startsWith('_'))
|
|
21
|
+
.map((c) => ({ name: c.name(), description: c.description() }));
|
|
22
|
+
const out = {
|
|
23
|
+
name: cmd.name(),
|
|
24
|
+
description: cmd.description(),
|
|
25
|
+
arguments: args,
|
|
26
|
+
options,
|
|
27
|
+
subcommands,
|
|
28
|
+
};
|
|
29
|
+
if (opts.includeIdentity) {
|
|
30
|
+
out.product = IDENTITY.product;
|
|
31
|
+
out.domain = IDENTITY.domain;
|
|
32
|
+
out.vendor = IDENTITY.vendor;
|
|
33
|
+
out.apiVersion = IDENTITY.apiVersion;
|
|
34
|
+
out.apiDocs = IDENTITY.apiDocs;
|
|
35
|
+
out.productCategories = IDENTITY.productCategories;
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/** Walk argv tokens (skipping flags) to find the deepest matching subcommand. */
|
|
40
|
+
export function resolveTargetCommand(root, argv) {
|
|
41
|
+
let cmd = root;
|
|
42
|
+
for (const token of argv) {
|
|
43
|
+
if (token.startsWith('-'))
|
|
44
|
+
continue;
|
|
45
|
+
const sub = cmd.commands.find((c) => c.name() === token || c.aliases().includes(token));
|
|
46
|
+
if (sub) {
|
|
47
|
+
cmd = sub;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return cmd;
|
|
54
|
+
}
|
package/dist/utils/output.js
CHANGED
|
@@ -28,6 +28,23 @@ export function emitJsonError(errorPayload) {
|
|
|
28
28
|
console.error(chalk.red(msg));
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* P7: emit the stream-header first line for any NDJSON/streaming command
|
|
33
|
+
* running under `--json`. Downstream JSON consumers can key on
|
|
34
|
+
* `{ stream: true }` to distinguish the header from subsequent event
|
|
35
|
+
* lines, and on `eventKind` / `cadence` to pick a parser strategy.
|
|
36
|
+
*
|
|
37
|
+
* Non-streaming commands (single-object / array output) do NOT emit this
|
|
38
|
+
* header — only watch / events tail / events mqtt-tail.
|
|
39
|
+
*/
|
|
40
|
+
export function emitStreamHeader(opts) {
|
|
41
|
+
console.log(JSON.stringify({
|
|
42
|
+
schemaVersion: '1',
|
|
43
|
+
stream: true,
|
|
44
|
+
eventKind: opts.eventKind,
|
|
45
|
+
cadence: opts.cadence,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
31
48
|
export function exitWithError(messageOrOpts) {
|
|
32
49
|
const opts = typeof messageOrOpts === 'string' ? { message: messageOrOpts } : messageOrOpts;
|
|
33
50
|
const { message, kind = 'usage', code = 2, hint, context, extra } = opts;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@switchbot/openapi-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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",
|
|
@@ -36,10 +36,12 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"build": "tsc",
|
|
40
|
-
"build:prod": "tsc -p tsconfig.build.json",
|
|
39
|
+
"build": "tsc && node scripts/copy-assets.mjs",
|
|
40
|
+
"build:prod": "tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
41
41
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
42
42
|
"dev": "tsx src/index.ts",
|
|
43
|
+
"lint:md": "markdownlint \"**/*.md\"",
|
|
44
|
+
"lint:md:changelog": "markdownlint CHANGELOG.md",
|
|
43
45
|
"start": "node dist/index.js",
|
|
44
46
|
"test": "vitest run",
|
|
45
47
|
"test:watch": "vitest",
|
|
@@ -48,20 +50,26 @@
|
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
52
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
53
|
+
"ajv": "^8.18.0",
|
|
54
|
+
"ajv-formats": "^3.0.1",
|
|
51
55
|
"axios": "^1.7.9",
|
|
52
56
|
"chalk": "^5.4.1",
|
|
53
57
|
"cli-table3": "^0.6.5",
|
|
54
58
|
"commander": "^12.1.0",
|
|
59
|
+
"croner": "^10.0.1",
|
|
55
60
|
"js-yaml": "^4.1.1",
|
|
56
61
|
"mqtt": "^5.3.0",
|
|
57
62
|
"pino": "^9.0.0",
|
|
58
|
-
"uuid": "^11.0.5"
|
|
63
|
+
"uuid": "^11.0.5",
|
|
64
|
+
"yaml": "^2.8.3",
|
|
65
|
+
"zod": "^4.3.6"
|
|
59
66
|
},
|
|
60
67
|
"devDependencies": {
|
|
61
68
|
"@types/js-yaml": "^4.0.9",
|
|
62
69
|
"@types/node": "^22.10.7",
|
|
63
70
|
"@types/uuid": "^10.0.0",
|
|
64
71
|
"@vitest/coverage-v8": "^2.1.9",
|
|
72
|
+
"markdownlint-cli": "^0.48.0",
|
|
65
73
|
"tsx": "^4.19.2",
|
|
66
74
|
"typescript": "^5.7.3",
|
|
67
75
|
"vitest": "^2.1.9"
|