@switchbot/openapi-cli 3.1.1 → 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.
Files changed (113) hide show
  1. package/README.md +3 -3
  2. package/dist/index.js +56945 -169
  3. package/dist/policy/schema/v0.2.json +1 -1
  4. package/package.json +3 -2
  5. package/dist/api/client.js +0 -235
  6. package/dist/auth.js +0 -20
  7. package/dist/commands/agent-bootstrap.js +0 -182
  8. package/dist/commands/auth.js +0 -354
  9. package/dist/commands/batch.js +0 -413
  10. package/dist/commands/cache.js +0 -126
  11. package/dist/commands/capabilities.js +0 -385
  12. package/dist/commands/catalog.js +0 -359
  13. package/dist/commands/completion.js +0 -385
  14. package/dist/commands/config.js +0 -376
  15. package/dist/commands/daemon.js +0 -410
  16. package/dist/commands/device-meta.js +0 -159
  17. package/dist/commands/devices.js +0 -948
  18. package/dist/commands/doctor.js +0 -1015
  19. package/dist/commands/events.js +0 -563
  20. package/dist/commands/expand.js +0 -130
  21. package/dist/commands/explain.js +0 -139
  22. package/dist/commands/health.js +0 -113
  23. package/dist/commands/history.js +0 -320
  24. package/dist/commands/identity.js +0 -59
  25. package/dist/commands/install.js +0 -246
  26. package/dist/commands/mcp.js +0 -2017
  27. package/dist/commands/plan.js +0 -653
  28. package/dist/commands/policy.js +0 -586
  29. package/dist/commands/quota.js +0 -78
  30. package/dist/commands/rules.js +0 -875
  31. package/dist/commands/scenes.js +0 -264
  32. package/dist/commands/schema.js +0 -177
  33. package/dist/commands/status-sync.js +0 -131
  34. package/dist/commands/uninstall.js +0 -237
  35. package/dist/commands/upgrade-check.js +0 -107
  36. package/dist/commands/watch.js +0 -194
  37. package/dist/commands/webhook.js +0 -182
  38. package/dist/config.js +0 -258
  39. package/dist/credentials/backends/file.js +0 -101
  40. package/dist/credentials/backends/linux.js +0 -129
  41. package/dist/credentials/backends/macos.js +0 -129
  42. package/dist/credentials/backends/windows.js +0 -215
  43. package/dist/credentials/keychain.js +0 -88
  44. package/dist/credentials/prime.js +0 -52
  45. package/dist/devices/cache.js +0 -293
  46. package/dist/devices/catalog.js +0 -767
  47. package/dist/devices/device-meta.js +0 -56
  48. package/dist/devices/history-agg.js +0 -138
  49. package/dist/devices/history-query.js +0 -181
  50. package/dist/devices/param-validator.js +0 -433
  51. package/dist/devices/resources.js +0 -270
  52. package/dist/install/default-steps.js +0 -257
  53. package/dist/install/preflight.js +0 -212
  54. package/dist/install/steps.js +0 -67
  55. package/dist/lib/command-keywords.js +0 -17
  56. package/dist/lib/daemon-state.js +0 -46
  57. package/dist/lib/destructive-mode.js +0 -12
  58. package/dist/lib/devices.js +0 -382
  59. package/dist/lib/idempotency.js +0 -106
  60. package/dist/lib/plan-store.js +0 -68
  61. package/dist/lib/request-context.js +0 -12
  62. package/dist/lib/scenes.js +0 -10
  63. package/dist/logger.js +0 -16
  64. package/dist/mcp/device-history.js +0 -145
  65. package/dist/mcp/events-subscription.js +0 -213
  66. package/dist/mqtt/client.js +0 -180
  67. package/dist/mqtt/credential.js +0 -30
  68. package/dist/policy/add-rule.js +0 -124
  69. package/dist/policy/diff.js +0 -91
  70. package/dist/policy/format.js +0 -57
  71. package/dist/policy/load.js +0 -61
  72. package/dist/policy/migrate.js +0 -67
  73. package/dist/policy/schema.js +0 -18
  74. package/dist/policy/validate.js +0 -262
  75. package/dist/rules/action.js +0 -216
  76. package/dist/rules/audit-query.js +0 -89
  77. package/dist/rules/conflict-analyzer.js +0 -214
  78. package/dist/rules/cron-scheduler.js +0 -186
  79. package/dist/rules/destructive.js +0 -52
  80. package/dist/rules/engine.js +0 -757
  81. package/dist/rules/matcher.js +0 -230
  82. package/dist/rules/pid-file.js +0 -95
  83. package/dist/rules/quiet-hours.js +0 -45
  84. package/dist/rules/suggest.js +0 -95
  85. package/dist/rules/throttle.js +0 -116
  86. package/dist/rules/types.js +0 -34
  87. package/dist/rules/webhook-listener.js +0 -223
  88. package/dist/rules/webhook-token.js +0 -90
  89. package/dist/schema/field-aliases.js +0 -131
  90. package/dist/sinks/dispatcher.js +0 -12
  91. package/dist/sinks/file.js +0 -19
  92. package/dist/sinks/format.js +0 -56
  93. package/dist/sinks/homeassistant.js +0 -44
  94. package/dist/sinks/openclaw.js +0 -33
  95. package/dist/sinks/stdout.js +0 -5
  96. package/dist/sinks/telegram.js +0 -28
  97. package/dist/sinks/types.js +0 -1
  98. package/dist/sinks/webhook.js +0 -22
  99. package/dist/status-sync/manager.js +0 -268
  100. package/dist/utils/arg-parsers.js +0 -66
  101. package/dist/utils/audit.js +0 -121
  102. package/dist/utils/filter.js +0 -189
  103. package/dist/utils/flags.js +0 -186
  104. package/dist/utils/format.js +0 -117
  105. package/dist/utils/health.js +0 -101
  106. package/dist/utils/help-json.js +0 -54
  107. package/dist/utils/name-resolver.js +0 -137
  108. package/dist/utils/output.js +0 -404
  109. package/dist/utils/quota.js +0 -227
  110. package/dist/utils/redact.js +0 -68
  111. package/dist/utils/retry.js +0 -140
  112. package/dist/utils/string.js +0 -22
  113. package/dist/version.js +0 -4
@@ -1,293 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import { createHash } from 'node:crypto';
5
- import { getConfigPath } from '../utils/flags.js';
6
- import { getActiveProfile } from '../lib/request-context.js';
7
- /**
8
- * Returns the directory where cache files should be stored.
9
- *
10
- * - If a profile is active, scopes into a per-profile sub-directory so that
11
- * rotating credentials or switching profiles never serves stale inventory
12
- * from a prior session (Bug #37).
13
- * - If no profile is active (unnamed / default), returns `baseDir` unchanged
14
- * so the existing legacy path (~/.switchbot/devices.json) is preserved.
15
- *
16
- * Only called when `getConfigPath()` returns undefined — the --config-path
17
- * override takes full precedence and bypasses this helper entirely.
18
- */
19
- function scopedCacheDir(baseDir) {
20
- const profile = getActiveProfile();
21
- if (profile === undefined)
22
- return baseDir;
23
- const hash = createHash('sha256').update(profile).digest('hex').slice(0, 8);
24
- const dir = path.join(baseDir, 'cache', hash);
25
- if (!fs.existsSync(dir))
26
- fs.mkdirSync(dir, { recursive: true });
27
- return dir;
28
- }
29
- /** GC cutoff for status entries: evict anything older than this. */
30
- const DEFAULT_STATUS_GC_TTL_MS = 24 * 60 * 60 * 1000; // 24 h
31
- function cacheFilePath() {
32
- const override = getConfigPath();
33
- const dir = override
34
- ? path.dirname(path.resolve(override))
35
- : scopedCacheDir(path.join(os.homedir(), '.switchbot'));
36
- return path.join(dir, 'devices.json');
37
- }
38
- // In-memory hot-cache keyed by active profile (or '__default__' for no profile).
39
- // Using Maps instead of module-level singletons ensures that mcp serve, which
40
- // rotates profiles per request via withRequestContext, never leaks inventory
41
- // across profiles within the same process (Bug #37).
42
- const _listCacheByProfile = new Map();
43
- const _statusCacheByProfile = new Map();
44
- function cacheKey() {
45
- return getActiveProfile() ?? '__default__';
46
- }
47
- /** Force the next loadCache() call to re-read from disk. Used in tests. */
48
- export function resetListCache() {
49
- _listCacheByProfile.clear();
50
- }
51
- /** Force the next loadStatusCache() call to re-read from disk. Used in tests. */
52
- export function resetStatusCache() {
53
- _statusCacheByProfile.clear();
54
- }
55
- export function loadCache() {
56
- const key = cacheKey();
57
- if (_listCacheByProfile.has(key))
58
- return _listCacheByProfile.get(key);
59
- const file = cacheFilePath();
60
- if (!fs.existsSync(file)) {
61
- _listCacheByProfile.set(key, null);
62
- return null;
63
- }
64
- try {
65
- const raw = fs.readFileSync(file, 'utf-8');
66
- const cache = JSON.parse(raw);
67
- if (!cache || typeof cache.devices !== 'object' || cache.devices === null) {
68
- _listCacheByProfile.set(key, null);
69
- return null;
70
- }
71
- _listCacheByProfile.set(key, cache);
72
- return cache;
73
- }
74
- catch {
75
- _listCacheByProfile.set(key, null);
76
- return null;
77
- }
78
- }
79
- export function getCachedDevice(deviceId) {
80
- const cache = loadCache();
81
- if (!cache)
82
- return null;
83
- return cache.devices[deviceId] ?? null;
84
- }
85
- /** Build a deviceId -> type map from the metadata cache. */
86
- export function getCachedTypeMap(deviceIds) {
87
- const cache = loadCache();
88
- const out = new Map();
89
- if (!cache)
90
- return out;
91
- if (deviceIds) {
92
- for (const id of deviceIds) {
93
- const entry = cache.devices[id];
94
- if (entry?.type)
95
- out.set(id, entry.type);
96
- }
97
- return out;
98
- }
99
- for (const [deviceId, entry] of Object.entries(cache.devices)) {
100
- if (entry.type)
101
- out.set(deviceId, entry.type);
102
- }
103
- return out;
104
- }
105
- export function updateCacheFromDeviceList(body) {
106
- const devices = {};
107
- for (const d of body.deviceList) {
108
- if (!d.deviceId || !d.deviceType)
109
- continue;
110
- devices[d.deviceId] = {
111
- type: d.deviceType,
112
- name: d.deviceName,
113
- category: 'physical',
114
- hubDeviceId: d.hubDeviceId,
115
- enableCloudService: d.enableCloudService,
116
- roomID: d.roomID,
117
- roomName: d.roomName,
118
- familyName: d.familyName,
119
- controlType: d.controlType,
120
- };
121
- }
122
- for (const d of body.infraredRemoteList) {
123
- if (!d.deviceId)
124
- continue;
125
- devices[d.deviceId] = {
126
- type: d.remoteType,
127
- name: d.deviceName,
128
- category: 'ir',
129
- hubDeviceId: d.hubDeviceId,
130
- controlType: d.controlType,
131
- };
132
- }
133
- const cache = {
134
- lastUpdated: new Date().toISOString(),
135
- devices,
136
- };
137
- try {
138
- const file = cacheFilePath();
139
- const dir = path.dirname(file);
140
- if (!fs.existsSync(dir))
141
- fs.mkdirSync(dir, { recursive: true });
142
- fs.writeFileSync(file, JSON.stringify(cache, null, 2), { mode: 0o600 });
143
- _listCacheByProfile.set(cacheKey(), cache);
144
- }
145
- catch {
146
- // Cache write failures must not break the command that triggered them.
147
- }
148
- }
149
- export function clearCache() {
150
- const file = cacheFilePath();
151
- if (fs.existsSync(file))
152
- fs.unlinkSync(file);
153
- _listCacheByProfile.set(cacheKey(), null);
154
- }
155
- // ---- Device list freshness -------------------------------------------------
156
- /** Age of the on-disk list cache in ms, or null if there is no cache. */
157
- export function listCacheAgeMs(now = Date.now()) {
158
- const cache = loadCache();
159
- if (!cache)
160
- return null;
161
- const ts = Date.parse(cache.lastUpdated);
162
- if (!Number.isFinite(ts))
163
- return null;
164
- return Math.max(0, now - ts);
165
- }
166
- /** True when the on-disk list cache is present and younger than `ttlMs`. */
167
- export function isListCacheFresh(ttlMs, now = Date.now()) {
168
- if (!ttlMs || ttlMs <= 0)
169
- return false;
170
- const age = listCacheAgeMs(now);
171
- return age !== null && age < ttlMs;
172
- }
173
- function statusCacheFilePath() {
174
- const override = getConfigPath();
175
- const dir = override
176
- ? path.dirname(path.resolve(override))
177
- : scopedCacheDir(path.join(os.homedir(), '.switchbot'));
178
- return path.join(dir, 'status.json');
179
- }
180
- export function loadStatusCache() {
181
- const key = cacheKey();
182
- if (_statusCacheByProfile.has(key))
183
- return _statusCacheByProfile.get(key);
184
- const file = statusCacheFilePath();
185
- if (!fs.existsSync(file)) {
186
- const empty = { entries: {} };
187
- _statusCacheByProfile.set(key, empty);
188
- return empty;
189
- }
190
- try {
191
- const raw = fs.readFileSync(file, 'utf-8');
192
- const parsed = JSON.parse(raw);
193
- if (!parsed || typeof parsed.entries !== 'object' || parsed.entries === null) {
194
- const empty = { entries: {} };
195
- _statusCacheByProfile.set(key, empty);
196
- return empty;
197
- }
198
- _statusCacheByProfile.set(key, parsed);
199
- return parsed;
200
- }
201
- catch {
202
- const empty = { entries: {} };
203
- _statusCacheByProfile.set(key, empty);
204
- return empty;
205
- }
206
- }
207
- function saveStatusCache(cache) {
208
- _statusCacheByProfile.set(cacheKey(), cache);
209
- try {
210
- const file = statusCacheFilePath();
211
- const dir = path.dirname(file);
212
- if (!fs.existsSync(dir))
213
- fs.mkdirSync(dir, { recursive: true });
214
- fs.writeFileSync(file, JSON.stringify(cache, null, 2), { mode: 0o600 });
215
- }
216
- catch {
217
- /* best-effort */
218
- }
219
- }
220
- /** Read a status entry; returns null when missing or older than `ttlMs`. */
221
- export function getCachedStatus(deviceId, ttlMs, now = Date.now()) {
222
- if (!ttlMs || ttlMs <= 0)
223
- return null;
224
- const cache = loadStatusCache();
225
- const entry = cache.entries[deviceId];
226
- if (!entry)
227
- return null;
228
- const ts = Date.parse(entry.fetchedAt);
229
- if (!Number.isFinite(ts))
230
- return null;
231
- if (now - ts >= ttlMs)
232
- return null;
233
- return entry.body;
234
- }
235
- /** Evict status entries older than max(ttlMs × 10, 24 h) to bound file growth. */
236
- function evictExpiredStatusEntries(cache, ttlMs, now = Date.now()) {
237
- const cutoff = now - Math.max(ttlMs * 10, 24 * 60 * 60 * 1000);
238
- for (const id of Object.keys(cache.entries)) {
239
- const entry = cache.entries[id];
240
- const ts = Date.parse(entry.fetchedAt);
241
- if (!Number.isFinite(ts) || ts < cutoff)
242
- delete cache.entries[id];
243
- }
244
- }
245
- export function setCachedStatus(deviceId, body, now = new Date(), ttlMsForGc = DEFAULT_STATUS_GC_TTL_MS) {
246
- const cache = loadStatusCache();
247
- cache.entries[deviceId] = {
248
- fetchedAt: now.toISOString(),
249
- body,
250
- };
251
- evictExpiredStatusEntries(cache, ttlMsForGc, now.getTime());
252
- saveStatusCache(cache);
253
- }
254
- export function clearStatusCache() {
255
- const file = statusCacheFilePath();
256
- if (fs.existsSync(file))
257
- fs.unlinkSync(file);
258
- _statusCacheByProfile.set(cacheKey(), { entries: {} });
259
- }
260
- export function describeCache(now = Date.now()) {
261
- const listFile = cacheFilePath();
262
- const listCache = loadCache();
263
- const listExists = fs.existsSync(listFile);
264
- const list = {
265
- path: listFile,
266
- exists: listExists,
267
- };
268
- if (listCache) {
269
- list.lastUpdated = listCache.lastUpdated;
270
- const ts = Date.parse(listCache.lastUpdated);
271
- if (Number.isFinite(ts))
272
- list.ageMs = Math.max(0, now - ts);
273
- list.deviceCount = Object.keys(listCache.devices).length;
274
- }
275
- const statusFile = statusCacheFilePath();
276
- const statusExists = fs.existsSync(statusFile);
277
- const statusCache = loadStatusCache();
278
- const entries = Object.values(statusCache.entries);
279
- const status = {
280
- path: statusFile,
281
- exists: statusExists,
282
- entryCount: entries.length,
283
- };
284
- if (entries.length > 0) {
285
- const sorted = entries
286
- .map((e) => e.fetchedAt)
287
- .filter((s) => typeof s === 'string')
288
- .sort();
289
- status.oldestFetchedAt = sorted[0];
290
- status.newestFetchedAt = sorted[sorted.length - 1];
291
- }
292
- return { list, status };
293
- }