@switchbot/openapi-cli 1.0.1 → 1.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 (90) hide show
  1. package/README.md +174 -18
  2. package/dist/api/client.d.ts +7 -1
  3. package/dist/api/client.js +44 -8
  4. package/dist/api/client.js.map +1 -1
  5. package/dist/commands/batch.d.ts +2 -0
  6. package/dist/commands/batch.js +252 -0
  7. package/dist/commands/batch.js.map +1 -0
  8. package/dist/commands/cache.d.ts +2 -0
  9. package/dist/commands/cache.js +108 -0
  10. package/dist/commands/cache.js.map +1 -0
  11. package/dist/commands/capabilities.d.ts +2 -0
  12. package/dist/commands/capabilities.js +91 -0
  13. package/dist/commands/capabilities.js.map +1 -0
  14. package/dist/commands/catalog.d.ts +2 -0
  15. package/dist/commands/catalog.js +291 -0
  16. package/dist/commands/catalog.js.map +1 -0
  17. package/dist/commands/config.js +123 -10
  18. package/dist/commands/config.js.map +1 -1
  19. package/dist/commands/devices.js +234 -112
  20. package/dist/commands/devices.js.map +1 -1
  21. package/dist/commands/doctor.d.ts +2 -0
  22. package/dist/commands/doctor.js +147 -0
  23. package/dist/commands/doctor.js.map +1 -0
  24. package/dist/commands/events.d.ts +15 -0
  25. package/dist/commands/events.js +188 -0
  26. package/dist/commands/events.js.map +1 -0
  27. package/dist/commands/explain.d.ts +2 -0
  28. package/dist/commands/explain.js +137 -0
  29. package/dist/commands/explain.js.map +1 -0
  30. package/dist/commands/history.d.ts +2 -0
  31. package/dist/commands/history.js +104 -0
  32. package/dist/commands/history.js.map +1 -0
  33. package/dist/commands/mcp.d.ts +4 -0
  34. package/dist/commands/mcp.js +386 -0
  35. package/dist/commands/mcp.js.map +1 -0
  36. package/dist/commands/plan.d.ts +37 -0
  37. package/dist/commands/plan.js +344 -0
  38. package/dist/commands/plan.js.map +1 -0
  39. package/dist/commands/quota.d.ts +2 -0
  40. package/dist/commands/quota.js +77 -0
  41. package/dist/commands/quota.js.map +1 -0
  42. package/dist/commands/scenes.js +19 -13
  43. package/dist/commands/scenes.js.map +1 -1
  44. package/dist/commands/schema.d.ts +2 -0
  45. package/dist/commands/schema.js +77 -0
  46. package/dist/commands/schema.js.map +1 -0
  47. package/dist/commands/watch.d.ts +2 -0
  48. package/dist/commands/watch.js +161 -0
  49. package/dist/commands/watch.js.map +1 -0
  50. package/dist/commands/webhook.js +37 -22
  51. package/dist/commands/webhook.js.map +1 -1
  52. package/dist/config.d.ts +11 -0
  53. package/dist/config.js +32 -6
  54. package/dist/config.js.map +1 -1
  55. package/dist/devices/cache.d.ts +75 -0
  56. package/dist/devices/cache.js +225 -0
  57. package/dist/devices/cache.js.map +1 -0
  58. package/dist/devices/catalog.d.ts +49 -0
  59. package/dist/devices/catalog.js +362 -92
  60. package/dist/devices/catalog.js.map +1 -1
  61. package/dist/index.js +31 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/lib/devices.d.ts +144 -0
  64. package/dist/lib/devices.js +329 -0
  65. package/dist/lib/devices.js.map +1 -0
  66. package/dist/lib/scenes.d.ts +7 -0
  67. package/dist/lib/scenes.js +11 -0
  68. package/dist/lib/scenes.js.map +1 -0
  69. package/dist/utils/audit.d.ts +13 -0
  70. package/dist/utils/audit.js +43 -0
  71. package/dist/utils/audit.js.map +1 -0
  72. package/dist/utils/filter.d.ts +45 -0
  73. package/dist/utils/filter.js +96 -0
  74. package/dist/utils/filter.js.map +1 -0
  75. package/dist/utils/flags.d.ts +42 -0
  76. package/dist/utils/flags.js +108 -0
  77. package/dist/utils/flags.js.map +1 -1
  78. package/dist/utils/format.d.ts +9 -0
  79. package/dist/utils/format.js +109 -0
  80. package/dist/utils/format.js.map +1 -0
  81. package/dist/utils/output.d.ts +11 -0
  82. package/dist/utils/output.js +37 -6
  83. package/dist/utils/output.js.map +1 -1
  84. package/dist/utils/quota.d.ts +48 -0
  85. package/dist/utils/quota.js +144 -0
  86. package/dist/utils/quota.js.map +1 -0
  87. package/dist/utils/retry.d.ts +23 -0
  88. package/dist/utils/retry.js +60 -0
  89. package/dist/utils/retry.js.map +1 -0
  90. package/package.json +4 -1
@@ -0,0 +1,225 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { getConfigPath } from '../utils/flags.js';
5
+ /** GC cutoff for status entries: evict anything older than this. */
6
+ const DEFAULT_STATUS_GC_TTL_MS = 24 * 60 * 60 * 1000; // 24 h
7
+ function cacheFilePath() {
8
+ const override = getConfigPath();
9
+ const dir = override
10
+ ? path.dirname(path.resolve(override))
11
+ : path.join(os.homedir(), '.switchbot');
12
+ return path.join(dir, 'devices.json');
13
+ }
14
+ // In-memory hot-cache: undefined = not yet loaded, null = loaded but empty.
15
+ let _listCache = undefined;
16
+ /** Force the next loadCache() call to re-read from disk. Used in tests. */
17
+ export function resetListCache() {
18
+ _listCache = undefined;
19
+ }
20
+ export function loadCache() {
21
+ if (_listCache !== undefined)
22
+ return _listCache;
23
+ const file = cacheFilePath();
24
+ if (!fs.existsSync(file)) {
25
+ _listCache = null;
26
+ return null;
27
+ }
28
+ try {
29
+ const raw = fs.readFileSync(file, 'utf-8');
30
+ const cache = JSON.parse(raw);
31
+ if (!cache || typeof cache.devices !== 'object' || cache.devices === null) {
32
+ _listCache = null;
33
+ return null;
34
+ }
35
+ _listCache = cache;
36
+ return cache;
37
+ }
38
+ catch {
39
+ _listCache = null;
40
+ return null;
41
+ }
42
+ }
43
+ export function getCachedDevice(deviceId) {
44
+ const cache = loadCache();
45
+ if (!cache)
46
+ return null;
47
+ return cache.devices[deviceId] ?? null;
48
+ }
49
+ export function updateCacheFromDeviceList(body) {
50
+ const devices = {};
51
+ for (const d of body.deviceList) {
52
+ if (!d.deviceId || !d.deviceType)
53
+ continue;
54
+ devices[d.deviceId] = {
55
+ type: d.deviceType,
56
+ name: d.deviceName,
57
+ category: 'physical',
58
+ hubDeviceId: d.hubDeviceId,
59
+ enableCloudService: d.enableCloudService,
60
+ roomID: d.roomID,
61
+ roomName: d.roomName,
62
+ familyName: d.familyName,
63
+ controlType: d.controlType,
64
+ };
65
+ }
66
+ for (const d of body.infraredRemoteList) {
67
+ if (!d.deviceId)
68
+ continue;
69
+ devices[d.deviceId] = {
70
+ type: d.remoteType,
71
+ name: d.deviceName,
72
+ category: 'ir',
73
+ hubDeviceId: d.hubDeviceId,
74
+ controlType: d.controlType,
75
+ };
76
+ }
77
+ const cache = {
78
+ lastUpdated: new Date().toISOString(),
79
+ devices,
80
+ };
81
+ try {
82
+ const file = cacheFilePath();
83
+ const dir = path.dirname(file);
84
+ if (!fs.existsSync(dir))
85
+ fs.mkdirSync(dir, { recursive: true });
86
+ fs.writeFileSync(file, JSON.stringify(cache, null, 2), { mode: 0o600 });
87
+ _listCache = cache;
88
+ }
89
+ catch {
90
+ // Cache write failures must not break the command that triggered them.
91
+ }
92
+ }
93
+ export function clearCache() {
94
+ const file = cacheFilePath();
95
+ if (fs.existsSync(file))
96
+ fs.unlinkSync(file);
97
+ _listCache = null;
98
+ }
99
+ // ---- Device list freshness -------------------------------------------------
100
+ /** Age of the on-disk list cache in ms, or null if there is no cache. */
101
+ export function listCacheAgeMs(now = Date.now()) {
102
+ const cache = loadCache();
103
+ if (!cache)
104
+ return null;
105
+ const ts = Date.parse(cache.lastUpdated);
106
+ if (!Number.isFinite(ts))
107
+ return null;
108
+ return Math.max(0, now - ts);
109
+ }
110
+ /** True when the on-disk list cache is present and younger than `ttlMs`. */
111
+ export function isListCacheFresh(ttlMs, now = Date.now()) {
112
+ if (!ttlMs || ttlMs <= 0)
113
+ return false;
114
+ const age = listCacheAgeMs(now);
115
+ return age !== null && age < ttlMs;
116
+ }
117
+ function statusCacheFilePath() {
118
+ const override = getConfigPath();
119
+ const dir = override
120
+ ? path.dirname(path.resolve(override))
121
+ : path.join(os.homedir(), '.switchbot');
122
+ return path.join(dir, 'status.json');
123
+ }
124
+ export function loadStatusCache() {
125
+ const file = statusCacheFilePath();
126
+ if (!fs.existsSync(file))
127
+ return { entries: {} };
128
+ try {
129
+ const raw = fs.readFileSync(file, 'utf-8');
130
+ const parsed = JSON.parse(raw);
131
+ if (!parsed || typeof parsed.entries !== 'object' || parsed.entries === null) {
132
+ return { entries: {} };
133
+ }
134
+ return parsed;
135
+ }
136
+ catch {
137
+ return { entries: {} };
138
+ }
139
+ }
140
+ function saveStatusCache(cache) {
141
+ try {
142
+ const file = statusCacheFilePath();
143
+ const dir = path.dirname(file);
144
+ if (!fs.existsSync(dir))
145
+ fs.mkdirSync(dir, { recursive: true });
146
+ fs.writeFileSync(file, JSON.stringify(cache, null, 2), { mode: 0o600 });
147
+ }
148
+ catch {
149
+ /* best-effort */
150
+ }
151
+ }
152
+ /** Read a status entry; returns null when missing or older than `ttlMs`. */
153
+ export function getCachedStatus(deviceId, ttlMs, now = Date.now()) {
154
+ if (!ttlMs || ttlMs <= 0)
155
+ return null;
156
+ const cache = loadStatusCache();
157
+ const entry = cache.entries[deviceId];
158
+ if (!entry)
159
+ return null;
160
+ const ts = Date.parse(entry.fetchedAt);
161
+ if (!Number.isFinite(ts))
162
+ return null;
163
+ if (now - ts >= ttlMs)
164
+ return null;
165
+ return entry.body;
166
+ }
167
+ /** Evict status entries older than max(ttlMs × 10, 24 h) to bound file growth. */
168
+ function evictExpiredStatusEntries(cache, ttlMs, now = Date.now()) {
169
+ const cutoff = now - Math.max(ttlMs * 10, 24 * 60 * 60 * 1000);
170
+ for (const id of Object.keys(cache.entries)) {
171
+ const entry = cache.entries[id];
172
+ const ts = Date.parse(entry.fetchedAt);
173
+ if (!Number.isFinite(ts) || ts < cutoff)
174
+ delete cache.entries[id];
175
+ }
176
+ }
177
+ export function setCachedStatus(deviceId, body, now = new Date()) {
178
+ const cache = loadStatusCache();
179
+ cache.entries[deviceId] = {
180
+ fetchedAt: now.toISOString(),
181
+ body,
182
+ };
183
+ evictExpiredStatusEntries(cache, DEFAULT_STATUS_GC_TTL_MS, now.getTime());
184
+ saveStatusCache(cache);
185
+ }
186
+ export function clearStatusCache() {
187
+ const file = statusCacheFilePath();
188
+ if (fs.existsSync(file))
189
+ fs.unlinkSync(file);
190
+ }
191
+ export function describeCache(now = Date.now()) {
192
+ const listFile = cacheFilePath();
193
+ const listCache = loadCache();
194
+ const listExists = fs.existsSync(listFile);
195
+ const list = {
196
+ path: listFile,
197
+ exists: listExists,
198
+ };
199
+ if (listCache) {
200
+ list.lastUpdated = listCache.lastUpdated;
201
+ const ts = Date.parse(listCache.lastUpdated);
202
+ if (Number.isFinite(ts))
203
+ list.ageMs = Math.max(0, now - ts);
204
+ list.deviceCount = Object.keys(listCache.devices).length;
205
+ }
206
+ const statusFile = statusCacheFilePath();
207
+ const statusExists = fs.existsSync(statusFile);
208
+ const statusCache = loadStatusCache();
209
+ const entries = Object.values(statusCache.entries);
210
+ const status = {
211
+ path: statusFile,
212
+ exists: statusExists,
213
+ entryCount: entries.length,
214
+ };
215
+ if (entries.length > 0) {
216
+ const sorted = entries
217
+ .map((e) => e.fetchedAt)
218
+ .filter((s) => typeof s === 'string')
219
+ .sort();
220
+ status.oldestFetchedAt = sorted[0];
221
+ status.newestFetchedAt = sorted[sorted.length - 1];
222
+ }
223
+ return { list, status };
224
+ }
225
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/devices/cache.ts"],"names":[],"mappings":"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"}
@@ -2,20 +2,69 @@
2
2
  * Static catalog of SwitchBot device types, control commands and status fields.
3
3
  * Sourced from https://github.com/OpenWonderLabs/SwitchBotAPI — keep in sync
4
4
  * when the upstream API adds new device types.
5
+ *
6
+ * Field conventions:
7
+ * - CommandSpec.idempotent: repeat-safe — calling it N times ends in the
8
+ * same state as calling it once (turnOn, setBrightness 50). Agents can
9
+ * retry these freely. Counter-examples: toggle, press, volumeAdd.
10
+ * - CommandSpec.destructive: causes a real-world effect that is hard or
11
+ * unsafe to reverse (unlock, garage open, deleteKey). UIs and agents
12
+ * should require explicit confirmation before issuing these.
13
+ * - DeviceCatalogEntry.role: functional grouping for filter/search
14
+ * ("all lighting", "all security"). Does not affect API behavior.
15
+ * - DeviceCatalogEntry.readOnly: the device has no control commands; it
16
+ * can only be queried via 'devices status'.
5
17
  */
6
18
  export interface CommandSpec {
7
19
  command: string;
8
20
  parameter: string;
9
21
  description: string;
10
22
  commandType?: 'command' | 'customize';
23
+ idempotent?: boolean;
24
+ destructive?: boolean;
25
+ /** One sentence explaining *why* this command is destructive — used in guard errors so agents/users can decide whether to confirm. */
26
+ destructiveReason?: string;
27
+ exampleParams?: string[];
11
28
  }
29
+ /** Coarse functional role — helpful for cross-type selection in agents. */
30
+ export type DeviceRole = 'lighting' | 'climate' | 'security' | 'media' | 'sensor' | 'cleaning' | 'curtain' | 'fan' | 'power' | 'hub' | 'other';
12
31
  export interface DeviceCatalogEntry {
13
32
  type: string;
14
33
  category: 'physical' | 'ir';
34
+ description?: string;
15
35
  aliases?: string[];
16
36
  commands: CommandSpec[];
17
37
  statusFields?: string[];
38
+ role?: DeviceRole;
39
+ readOnly?: boolean;
18
40
  }
19
41
  export declare const DEVICE_CATALOG: DeviceCatalogEntry[];
20
42
  /** Find a catalog entry by exact name, alias, or case-insensitive substring. */
21
43
  export declare function findCatalogEntry(query: string): DeviceCatalogEntry | DeviceCatalogEntry[] | null;
44
+ /**
45
+ * Pick up to 3 non-destructive, idempotent commands an agent can safely invoke
46
+ * to explore or exercise a device. Used by `devices describe --json` to hint
47
+ * at concrete next steps.
48
+ */
49
+ export declare function suggestedActions(entry: DeviceCatalogEntry): Array<{
50
+ command: string;
51
+ parameter?: string;
52
+ description: string;
53
+ }>;
54
+ export interface CatalogOverlayEntry extends Partial<DeviceCatalogEntry> {
55
+ type: string;
56
+ remove?: boolean;
57
+ }
58
+ export interface OverlayLoadResult {
59
+ path: string;
60
+ exists: boolean;
61
+ entries: CatalogOverlayEntry[];
62
+ error?: string;
63
+ }
64
+ export declare function getCatalogOverlayPath(): string;
65
+ /** Read the overlay file. Never throws — returns `error` on bad files. */
66
+ export declare function loadCatalogOverlay(): OverlayLoadResult;
67
+ /** Clear the overlay cache (test helper; also useful for `catalog refresh`). */
68
+ export declare function resetCatalogOverlayCache(): void;
69
+ /** Merge built-in catalog with the on-disk overlay. */
70
+ export declare function getEffectiveCatalog(): DeviceCatalogEntry[];