@switchbot/openapi-cli 3.1.0 → 3.2.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 +34 -42
- package/dist/index.js +56945 -169
- package/dist/policy/schema/v0.2.json +1 -1
- package/package.json +3 -2
- package/dist/api/client.js +0 -235
- package/dist/auth.js +0 -20
- package/dist/commands/agent-bootstrap.js +0 -182
- package/dist/commands/auth.js +0 -354
- package/dist/commands/batch.js +0 -413
- package/dist/commands/cache.js +0 -126
- package/dist/commands/capabilities.js +0 -385
- package/dist/commands/catalog.js +0 -359
- package/dist/commands/completion.js +0 -385
- package/dist/commands/config.js +0 -376
- package/dist/commands/daemon.js +0 -367
- package/dist/commands/device-meta.js +0 -159
- package/dist/commands/devices.js +0 -948
- package/dist/commands/doctor.js +0 -1015
- package/dist/commands/events.js +0 -563
- package/dist/commands/expand.js +0 -130
- package/dist/commands/explain.js +0 -139
- package/dist/commands/health.js +0 -113
- package/dist/commands/history.js +0 -320
- package/dist/commands/identity.js +0 -59
- package/dist/commands/install.js +0 -246
- package/dist/commands/mcp.js +0 -2017
- package/dist/commands/plan.js +0 -653
- package/dist/commands/policy.js +0 -586
- package/dist/commands/quota.js +0 -78
- package/dist/commands/rules.js +0 -875
- package/dist/commands/scenes.js +0 -264
- package/dist/commands/schema.js +0 -177
- package/dist/commands/status-sync.js +0 -131
- package/dist/commands/uninstall.js +0 -237
- package/dist/commands/upgrade-check.js +0 -88
- package/dist/commands/watch.js +0 -194
- package/dist/commands/webhook.js +0 -182
- package/dist/config.js +0 -258
- package/dist/credentials/backends/file.js +0 -101
- package/dist/credentials/backends/linux.js +0 -129
- package/dist/credentials/backends/macos.js +0 -129
- package/dist/credentials/backends/windows.js +0 -215
- package/dist/credentials/keychain.js +0 -88
- package/dist/credentials/prime.js +0 -52
- package/dist/devices/cache.js +0 -293
- package/dist/devices/catalog.js +0 -767
- package/dist/devices/device-meta.js +0 -56
- package/dist/devices/history-agg.js +0 -138
- package/dist/devices/history-query.js +0 -181
- package/dist/devices/param-validator.js +0 -433
- package/dist/devices/resources.js +0 -270
- package/dist/install/default-steps.js +0 -257
- package/dist/install/preflight.js +0 -212
- package/dist/install/steps.js +0 -67
- package/dist/lib/command-keywords.js +0 -17
- package/dist/lib/daemon-state.js +0 -46
- package/dist/lib/destructive-mode.js +0 -12
- package/dist/lib/devices.js +0 -382
- package/dist/lib/idempotency.js +0 -106
- package/dist/lib/plan-store.js +0 -68
- package/dist/lib/request-context.js +0 -12
- package/dist/lib/scenes.js +0 -10
- package/dist/logger.js +0 -16
- package/dist/mcp/device-history.js +0 -145
- package/dist/mcp/events-subscription.js +0 -213
- package/dist/mqtt/client.js +0 -180
- package/dist/mqtt/credential.js +0 -30
- package/dist/policy/add-rule.js +0 -124
- package/dist/policy/diff.js +0 -91
- package/dist/policy/format.js +0 -57
- package/dist/policy/load.js +0 -61
- package/dist/policy/migrate.js +0 -67
- package/dist/policy/schema.js +0 -18
- package/dist/policy/validate.js +0 -262
- package/dist/rules/action.js +0 -205
- package/dist/rules/audit-query.js +0 -89
- package/dist/rules/conflict-analyzer.js +0 -203
- package/dist/rules/cron-scheduler.js +0 -186
- package/dist/rules/destructive.js +0 -52
- package/dist/rules/engine.js +0 -757
- package/dist/rules/matcher.js +0 -230
- package/dist/rules/pid-file.js +0 -95
- package/dist/rules/quiet-hours.js +0 -45
- package/dist/rules/suggest.js +0 -95
- package/dist/rules/throttle.js +0 -116
- package/dist/rules/types.js +0 -34
- package/dist/rules/webhook-listener.js +0 -223
- package/dist/rules/webhook-token.js +0 -90
- package/dist/schema/field-aliases.js +0 -131
- package/dist/sinks/dispatcher.js +0 -12
- package/dist/sinks/file.js +0 -19
- package/dist/sinks/format.js +0 -56
- package/dist/sinks/homeassistant.js +0 -44
- package/dist/sinks/openclaw.js +0 -33
- package/dist/sinks/stdout.js +0 -5
- package/dist/sinks/telegram.js +0 -28
- package/dist/sinks/types.js +0 -1
- package/dist/sinks/webhook.js +0 -22
- package/dist/status-sync/manager.js +0 -268
- package/dist/utils/arg-parsers.js +0 -66
- package/dist/utils/audit.js +0 -117
- package/dist/utils/filter.js +0 -189
- package/dist/utils/flags.js +0 -186
- package/dist/utils/format.js +0 -117
- package/dist/utils/health.js +0 -101
- package/dist/utils/help-json.js +0 -54
- package/dist/utils/name-resolver.js +0 -137
- package/dist/utils/output.js +0 -404
- package/dist/utils/quota.js +0 -227
- package/dist/utils/redact.js +0 -68
- package/dist/utils/retry.js +0 -140
- package/dist/utils/string.js +0 -22
- package/dist/version.js +0 -4
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Install-orchestrator pre-flight (Phase 3A · F5).
|
|
3
|
-
*
|
|
4
|
-
* Pure library — no CLI entry. Consumers (e.g. a future
|
|
5
|
-
* `openclaw plugins install` command) call `runPreflight()` and decide
|
|
6
|
-
* whether to proceed based on the returned result. Nothing here mutates
|
|
7
|
-
* user state: every check is read-only.
|
|
8
|
-
*
|
|
9
|
-
* The check list mirrors `docs/design/phase3-install.md` step 1 minus
|
|
10
|
-
* the bits that require external services (npm registry / SwitchBot API
|
|
11
|
-
* reachability are left for the installer itself to probe when it has
|
|
12
|
-
* a plan to retry, since they are the flakiest of the lot).
|
|
13
|
-
*/
|
|
14
|
-
import fs from 'node:fs';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import os from 'node:os';
|
|
17
|
-
import { resolvePolicyPath, loadPolicyFile, PolicyFileNotFoundError } from '../policy/load.js';
|
|
18
|
-
import { validateLoadedPolicy } from '../policy/validate.js';
|
|
19
|
-
import { selectCredentialStore } from '../credentials/keychain.js';
|
|
20
|
-
function parseMajor(version) {
|
|
21
|
-
const m = /^v?(\d+)\./.exec(version);
|
|
22
|
-
if (!m)
|
|
23
|
-
return null;
|
|
24
|
-
const n = Number(m[1]);
|
|
25
|
-
return Number.isFinite(n) ? n : null;
|
|
26
|
-
}
|
|
27
|
-
function checkNodeVersion(opts) {
|
|
28
|
-
const required = opts.minNodeMajor ?? 18;
|
|
29
|
-
const version = opts.nodeVersion ?? process.version;
|
|
30
|
-
const major = parseMajor(version);
|
|
31
|
-
if (major === null) {
|
|
32
|
-
return {
|
|
33
|
-
name: 'node',
|
|
34
|
-
status: 'fail',
|
|
35
|
-
message: `unrecognised Node.js version string: ${version}`,
|
|
36
|
-
hint: 'reinstall Node.js from https://nodejs.org',
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
if (major < required) {
|
|
40
|
-
return {
|
|
41
|
-
name: 'node',
|
|
42
|
-
status: 'fail',
|
|
43
|
-
message: `Node.js ${version} < required v${required}`,
|
|
44
|
-
hint: `upgrade Node.js to v${required} or later`,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return { name: 'node', status: 'ok', message: `Node.js ${version}` };
|
|
48
|
-
}
|
|
49
|
-
function checkPolicy() {
|
|
50
|
-
const policyPath = resolvePolicyPath();
|
|
51
|
-
try {
|
|
52
|
-
const loaded = loadPolicyFile(policyPath);
|
|
53
|
-
const result = validateLoadedPolicy(loaded);
|
|
54
|
-
if (result.valid) {
|
|
55
|
-
return {
|
|
56
|
-
name: 'policy',
|
|
57
|
-
status: 'ok',
|
|
58
|
-
message: `policy at ${policyPath} validates (v${result.schemaVersion ?? '?'})`,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
name: 'policy',
|
|
63
|
-
status: 'warn',
|
|
64
|
-
message: `policy at ${policyPath} has ${result.errors.length} validation error(s)`,
|
|
65
|
-
hint: 'run "switchbot policy validate" to see details before installing',
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
if (err instanceof PolicyFileNotFoundError) {
|
|
70
|
-
return {
|
|
71
|
-
name: 'policy',
|
|
72
|
-
status: 'ok',
|
|
73
|
-
message: `no policy at ${policyPath} (installer will scaffold one)`,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
name: 'policy',
|
|
78
|
-
status: 'warn',
|
|
79
|
-
message: `policy at ${policyPath} is unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
80
|
-
hint: 'move the file aside, then re-run — the installer will scaffold a fresh copy',
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
async function checkKeychain() {
|
|
85
|
-
try {
|
|
86
|
-
const store = await selectCredentialStore();
|
|
87
|
-
const desc = store.describe();
|
|
88
|
-
if (desc.writable) {
|
|
89
|
-
return {
|
|
90
|
-
name: 'keychain',
|
|
91
|
-
status: 'ok',
|
|
92
|
-
message: `credential backend: ${desc.backend}`,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
name: 'keychain',
|
|
97
|
-
status: 'warn',
|
|
98
|
-
message: `credential backend ${desc.backend} is not writable — will fall back to file`,
|
|
99
|
-
hint: desc.notes ?? 'install the OS keychain helper to get native credential storage',
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
return {
|
|
104
|
-
name: 'keychain',
|
|
105
|
-
status: 'warn',
|
|
106
|
-
message: `keychain probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
107
|
-
hint: 'the installer will fall back to the file backend',
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function checkHomeDirWritable() {
|
|
112
|
-
const home = os.homedir();
|
|
113
|
-
const switchbotDir = path.join(home, '.switchbot');
|
|
114
|
-
try {
|
|
115
|
-
const homeStat = fs.statSync(home);
|
|
116
|
-
if (!homeStat.isDirectory()) {
|
|
117
|
-
return {
|
|
118
|
-
name: 'home',
|
|
119
|
-
status: 'fail',
|
|
120
|
-
message: `home path is not a directory: ${home}`,
|
|
121
|
-
hint: 'check your HOME/USERPROFILE environment configuration',
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
if (fs.existsSync(switchbotDir)) {
|
|
125
|
-
const sbStat = fs.statSync(switchbotDir);
|
|
126
|
-
if (!sbStat.isDirectory()) {
|
|
127
|
-
return {
|
|
128
|
-
name: 'home',
|
|
129
|
-
status: 'fail',
|
|
130
|
-
message: `${switchbotDir} exists but is not a directory`,
|
|
131
|
-
hint: 'move the file aside and re-run install',
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
fs.accessSync(switchbotDir, fs.constants.W_OK);
|
|
135
|
-
return { name: 'home', status: 'ok', message: `writable: ${switchbotDir}` };
|
|
136
|
-
}
|
|
137
|
-
fs.accessSync(home, fs.constants.W_OK);
|
|
138
|
-
return { name: 'home', status: 'ok', message: `writable: ${home}` };
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
return {
|
|
142
|
-
name: 'home',
|
|
143
|
-
status: 'fail',
|
|
144
|
-
message: `cannot write under ${home}: ${err instanceof Error ? err.message : String(err)}`,
|
|
145
|
-
hint: 'check ownership and permissions on your home directory',
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function nearestExistingPath(target) {
|
|
150
|
-
let cur = target;
|
|
151
|
-
while (true) {
|
|
152
|
-
if (fs.existsSync(cur))
|
|
153
|
-
return cur;
|
|
154
|
-
const parent = path.dirname(cur);
|
|
155
|
-
if (parent === cur)
|
|
156
|
-
return null;
|
|
157
|
-
cur = parent;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function checkAgentSkillDirWritable(opts) {
|
|
161
|
-
const shouldCheck = opts.agent === 'claude-code' && (opts.expectSkillLink ?? true);
|
|
162
|
-
if (!shouldCheck)
|
|
163
|
-
return null;
|
|
164
|
-
const home = os.homedir();
|
|
165
|
-
const target = path.join(home, '.claude', 'skills');
|
|
166
|
-
try {
|
|
167
|
-
const existing = nearestExistingPath(target);
|
|
168
|
-
if (!existing) {
|
|
169
|
-
return {
|
|
170
|
-
name: 'agent-skills-dir',
|
|
171
|
-
status: 'fail',
|
|
172
|
-
message: `cannot resolve an existing parent for ${target}`,
|
|
173
|
-
hint: 'check your home directory path and permissions',
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const stat = fs.statSync(existing);
|
|
177
|
-
if (!stat.isDirectory()) {
|
|
178
|
-
return {
|
|
179
|
-
name: 'agent-skills-dir',
|
|
180
|
-
status: 'fail',
|
|
181
|
-
message: `path component is not a directory: ${existing}`,
|
|
182
|
-
hint: 'move the blocking file aside and re-run install',
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
fs.accessSync(existing, fs.constants.W_OK);
|
|
186
|
-
return { name: 'agent-skills-dir', status: 'ok', message: `writable: ${target}` };
|
|
187
|
-
}
|
|
188
|
-
catch (err) {
|
|
189
|
-
return {
|
|
190
|
-
name: 'agent-skills-dir',
|
|
191
|
-
status: 'fail',
|
|
192
|
-
message: `cannot write to ${target}: ${err instanceof Error ? err.message : String(err)}`,
|
|
193
|
-
hint: 'open Claude Code once (it will create ~/.claude) or create the directory manually',
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Run every pre-flight check and return a combined result. Safe to
|
|
199
|
-
* call multiple times; no state is cached.
|
|
200
|
-
*/
|
|
201
|
-
export async function runPreflight(options = {}) {
|
|
202
|
-
const checks = [];
|
|
203
|
-
checks.push(checkNodeVersion(options));
|
|
204
|
-
checks.push(checkPolicy());
|
|
205
|
-
checks.push(await checkKeychain());
|
|
206
|
-
checks.push(checkHomeDirWritable());
|
|
207
|
-
const agentCheck = checkAgentSkillDirWritable(options);
|
|
208
|
-
if (agentCheck)
|
|
209
|
-
checks.push(agentCheck);
|
|
210
|
-
const ok = checks.every((c) => c.status !== 'fail');
|
|
211
|
-
return { checks, ok };
|
|
212
|
-
}
|
package/dist/install/steps.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Install-orchestrator step runner (Phase 3A · F5).
|
|
3
|
-
*
|
|
4
|
-
* Each step has a deterministic `execute` and a matching `undo`. The
|
|
5
|
-
* runner executes steps in order; on any failure it walks the
|
|
6
|
-
* already-completed steps in reverse and invokes their `undo`. If an
|
|
7
|
-
* `undo` itself fails, the error is captured and surfaced — the
|
|
8
|
-
* runner does NOT abort the rollback. The caller gets a full report
|
|
9
|
-
* and can decide how to surface partial cleanup failures.
|
|
10
|
-
*
|
|
11
|
-
* The module is intentionally agnostic of what steps do; consumers
|
|
12
|
-
* (future `openclaw plugins install`) plug in concrete steps like
|
|
13
|
-
* "npm i -g the CLI" or "write the credential to the keychain".
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* Run the given steps in order. On the first failure, the runner
|
|
17
|
-
* walks already-executed steps in reverse and invokes each step's
|
|
18
|
-
* undo. Returns a report describing every step's fate.
|
|
19
|
-
*/
|
|
20
|
-
export async function runInstall(steps, options = {}) {
|
|
21
|
-
const ctx = (options.context ?? {});
|
|
22
|
-
const outcomes = [];
|
|
23
|
-
const executed = [];
|
|
24
|
-
let failedAt;
|
|
25
|
-
for (const step of steps) {
|
|
26
|
-
try {
|
|
27
|
-
await step.execute(ctx);
|
|
28
|
-
outcomes.push({ step: step.name, status: 'succeeded' });
|
|
29
|
-
executed.push(step);
|
|
30
|
-
}
|
|
31
|
-
catch (err) {
|
|
32
|
-
outcomes.push({
|
|
33
|
-
step: step.name,
|
|
34
|
-
status: 'failed',
|
|
35
|
-
error: err instanceof Error ? err.message : String(err),
|
|
36
|
-
});
|
|
37
|
-
failedAt = step.name;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
if (options.stopAfter === step.name)
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
if (failedAt !== undefined) {
|
|
44
|
-
// Roll back completed steps in reverse. Undo failures are captured
|
|
45
|
-
// but do not abort further rollback attempts — the goal is to
|
|
46
|
-
// leave as little residue as possible.
|
|
47
|
-
for (let i = executed.length - 1; i >= 0; i--) {
|
|
48
|
-
const step = executed[i];
|
|
49
|
-
try {
|
|
50
|
-
await step.undo(ctx);
|
|
51
|
-
outcomes.push({ step: step.name, status: 'rolled-back' });
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
outcomes.push({
|
|
55
|
-
step: step.name,
|
|
56
|
-
status: 'rollback-failed',
|
|
57
|
-
error: err instanceof Error ? err.message : String(err),
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
ok: failedAt === undefined,
|
|
64
|
-
outcomes,
|
|
65
|
-
...(failedAt !== undefined ? { failedAt } : {}),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export const COMMAND_KEYWORDS = [
|
|
2
|
-
{ pattern: /\boff\b|\bturn.?off\b|\bstop\b/i, command: 'turnOff' },
|
|
3
|
-
{ pattern: /\bon\b|\bturn.?on\b|\bstart\b/i, command: 'turnOn' },
|
|
4
|
-
{ pattern: /\bpress\b|\bclick\b|\btap\b/i, command: 'press' },
|
|
5
|
-
{ pattern: /\block\b/i, command: 'lock' },
|
|
6
|
-
{ pattern: /\bunlock\b/i, command: 'unlock' },
|
|
7
|
-
{ pattern: /\bopen\b|\braise\b|\bup\b/i, command: 'open' },
|
|
8
|
-
{ pattern: /\bclose\b|\blower\b|\bdown\b/i, command: 'close' },
|
|
9
|
-
{ pattern: /\bpause\b/i, command: 'pause' },
|
|
10
|
-
];
|
|
11
|
-
export function inferCommandFromIntent(intent) {
|
|
12
|
-
for (const k of COMMAND_KEYWORDS) {
|
|
13
|
-
if (k.pattern.test(intent))
|
|
14
|
-
return k.command;
|
|
15
|
-
}
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
package/dist/lib/daemon-state.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
function getStateDir() {
|
|
5
|
-
return path.join(os.homedir(), '.switchbot');
|
|
6
|
-
}
|
|
7
|
-
function getDaemonPidFile() {
|
|
8
|
-
return path.join(getStateDir(), 'daemon.pid');
|
|
9
|
-
}
|
|
10
|
-
function getDaemonLogFile() {
|
|
11
|
-
return path.join(getStateDir(), 'daemon.log');
|
|
12
|
-
}
|
|
13
|
-
function getDaemonStateFile() {
|
|
14
|
-
return path.join(getStateDir(), 'daemon.state.json');
|
|
15
|
-
}
|
|
16
|
-
function getHealthzPidFile() {
|
|
17
|
-
return path.join(getStateDir(), 'healthz.pid');
|
|
18
|
-
}
|
|
19
|
-
export const DAEMON_PID_FILE = getDaemonPidFile();
|
|
20
|
-
export const DAEMON_LOG_FILE = getDaemonLogFile();
|
|
21
|
-
export const DAEMON_STATE_FILE = getDaemonStateFile();
|
|
22
|
-
export const HEALTHZ_PID_FILE = getHealthzPidFile();
|
|
23
|
-
function ensureStateDir() {
|
|
24
|
-
fs.mkdirSync(getStateDir(), { recursive: true, mode: 0o700 });
|
|
25
|
-
}
|
|
26
|
-
export function writeDaemonState(state) {
|
|
27
|
-
ensureStateDir();
|
|
28
|
-
fs.writeFileSync(getDaemonStateFile(), JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
29
|
-
}
|
|
30
|
-
export function readDaemonState() {
|
|
31
|
-
try {
|
|
32
|
-
const raw = fs.readFileSync(getDaemonStateFile(), 'utf-8');
|
|
33
|
-
return JSON.parse(raw);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export function removeDaemonState() {
|
|
40
|
-
try {
|
|
41
|
-
fs.unlinkSync(getDaemonStateFile());
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// best effort
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { getActiveProfile } from './request-context.js';
|
|
2
|
-
const DIRECT_DESTRUCTIVE_PROFILES = new Set(['dev', 'development']);
|
|
3
|
-
export function allowsDirectDestructiveExecution(profile = getActiveProfile()) {
|
|
4
|
-
if (process.env.SWITCHBOT_ALLOW_DIRECT_DESTRUCTIVE === '1')
|
|
5
|
-
return true;
|
|
6
|
-
if (!profile)
|
|
7
|
-
return false;
|
|
8
|
-
return DIRECT_DESTRUCTIVE_PROFILES.has(profile.toLowerCase());
|
|
9
|
-
}
|
|
10
|
-
export function destructiveExecutionHint() {
|
|
11
|
-
return "Use 'switchbot plan save <file>' -> 'switchbot plan review <planId>' -> 'switchbot plan approve <planId>' -> 'switchbot plan execute <planId>' instead.";
|
|
12
|
-
}
|