@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,56 +0,0 @@
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
- function metaFilePath() {
6
- const override = getConfigPath();
7
- const dir = override
8
- ? path.dirname(path.resolve(override))
9
- : path.join(os.homedir(), '.switchbot');
10
- return path.join(dir, 'device-meta.json');
11
- }
12
- export function getMetaFilePath() {
13
- return metaFilePath();
14
- }
15
- export function loadDeviceMeta() {
16
- const file = metaFilePath();
17
- if (!fs.existsSync(file))
18
- return { version: '1', devices: {} };
19
- try {
20
- const raw = fs.readFileSync(file, 'utf-8');
21
- const parsed = JSON.parse(raw);
22
- if (!parsed ||
23
- parsed.version !== '1' ||
24
- typeof parsed.devices !== 'object' ||
25
- parsed.devices === null) {
26
- return { version: '1', devices: {} };
27
- }
28
- return parsed;
29
- }
30
- catch {
31
- return { version: '1', devices: {} };
32
- }
33
- }
34
- export function saveDeviceMeta(meta) {
35
- const file = metaFilePath();
36
- const dir = path.dirname(file);
37
- if (!fs.existsSync(dir))
38
- fs.mkdirSync(dir, { recursive: true });
39
- const tmp = `${file}.tmp-${process.pid}`;
40
- fs.writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 0o600 });
41
- fs.renameSync(tmp, file);
42
- }
43
- export function getDeviceMeta(deviceId) {
44
- const meta = loadDeviceMeta();
45
- return meta.devices[deviceId] ?? null;
46
- }
47
- export function setDeviceMeta(deviceId, patch) {
48
- const meta = loadDeviceMeta();
49
- meta.devices[deviceId] = { ...meta.devices[deviceId], ...patch };
50
- saveDeviceMeta(meta);
51
- }
52
- export function clearDeviceMeta(deviceId) {
53
- const meta = loadDeviceMeta();
54
- delete meta.devices[deviceId];
55
- saveDeviceMeta(meta);
56
- }
@@ -1,138 +0,0 @@
1
- import fs from 'node:fs';
2
- import readline from 'node:readline';
3
- import { jsonlFilesForDevice, parseDurationToMs, resolveRange } from './history-query.js';
4
- export const ALL_AGG_FNS = ['count', 'min', 'max', 'avg', 'sum', 'p50', 'p95'];
5
- export const DEFAULT_AGGS = ['count', 'avg'];
6
- export const DEFAULT_SAMPLE_CAP = 10_000;
7
- export const MAX_SAMPLE_CAP = 100_000;
8
- export async function aggregateDeviceHistory(deviceId, opts) {
9
- const { fromMs, toMs } = resolveRange(opts);
10
- const aggs = (opts.aggs && opts.aggs.length > 0) ? opts.aggs : [...DEFAULT_AGGS];
11
- const needQuantile = aggs.includes('p50') || aggs.includes('p95');
12
- let bucketMs = null;
13
- if (opts.bucket !== undefined) {
14
- bucketMs = parseDurationToMs(opts.bucket);
15
- if (bucketMs === null) {
16
- throw new Error(`Invalid --bucket "${opts.bucket}". Expected e.g. "15m", "1h", "1d".`);
17
- }
18
- }
19
- const sampleCap = Math.max(1, Math.min(opts.maxBucketSamples ?? DEFAULT_SAMPLE_CAP, MAX_SAMPLE_CAP));
20
- let partial = false;
21
- const notes = [];
22
- // bucketKey (epoch ms; 0 when no --bucket) → metric name → Acc
23
- const buckets = new Map();
24
- for (const file of jsonlFilesForDevice(deviceId)) {
25
- try {
26
- const st = fs.statSync(file);
27
- if (st.mtimeMs < fromMs)
28
- continue;
29
- }
30
- catch {
31
- continue;
32
- }
33
- const stream = fs.createReadStream(file, { encoding: 'utf-8' });
34
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
35
- for await (const line of rl) {
36
- if (!line)
37
- continue;
38
- let rec;
39
- try {
40
- rec = JSON.parse(line);
41
- }
42
- catch {
43
- continue;
44
- }
45
- const tMs = Date.parse(rec.t);
46
- if (!Number.isFinite(tMs) || tMs < fromMs || tMs > toMs)
47
- continue;
48
- const key = bucketMs !== null ? Math.floor(tMs / bucketMs) * bucketMs : 0;
49
- let bkt = buckets.get(key);
50
- if (!bkt) {
51
- bkt = new Map();
52
- buckets.set(key, bkt);
53
- }
54
- for (const metric of opts.metrics) {
55
- const v = rec.payload?.[metric];
56
- if (typeof v !== 'number' || !Number.isFinite(v))
57
- continue;
58
- let acc = bkt.get(metric);
59
- if (!acc) {
60
- acc = {
61
- min: v,
62
- max: v,
63
- sum: 0,
64
- count: 0,
65
- samples: needQuantile ? [] : null,
66
- sampleCapHit: false,
67
- };
68
- bkt.set(metric, acc);
69
- }
70
- acc.min = Math.min(acc.min, v);
71
- acc.max = Math.max(acc.max, v);
72
- acc.sum += v;
73
- acc.count += 1;
74
- if (acc.samples) {
75
- if (acc.samples.length < sampleCap) {
76
- acc.samples.push(v);
77
- }
78
- else if (!acc.sampleCapHit) {
79
- acc.sampleCapHit = true;
80
- partial = true;
81
- notes.push(`bucket ${new Date(key).toISOString()} metric ${metric}: sample cap ${sampleCap} reached, quantiles approximate`);
82
- }
83
- }
84
- }
85
- }
86
- }
87
- return finalize(deviceId, opts, aggs, buckets, partial, notes, fromMs, toMs);
88
- }
89
- function finalize(deviceId, opts, aggs, buckets, partial, notes, fromMs, toMs) {
90
- const fromIso = Number.isFinite(fromMs) ? new Date(fromMs).toISOString() : new Date(0).toISOString();
91
- const toIso = Number.isFinite(toMs) ? new Date(toMs).toISOString() : new Date(Date.now()).toISOString();
92
- const keys = [...buckets.keys()].sort((a, b) => a - b);
93
- const outBuckets = [];
94
- for (const key of keys) {
95
- const perMetric = buckets.get(key);
96
- const metricsOut = {};
97
- for (const [metric, acc] of perMetric.entries()) {
98
- if (acc.count === 0)
99
- continue;
100
- const r = {};
101
- if (aggs.includes('count'))
102
- r.count = acc.count;
103
- if (aggs.includes('min'))
104
- r.min = acc.min;
105
- if (aggs.includes('max'))
106
- r.max = acc.max;
107
- if (aggs.includes('avg'))
108
- r.avg = acc.sum / acc.count;
109
- if (aggs.includes('sum'))
110
- r.sum = acc.sum;
111
- if ((aggs.includes('p50') || aggs.includes('p95')) && acc.samples) {
112
- const sorted = [...acc.samples].sort((a, b) => a - b);
113
- if (aggs.includes('p50'))
114
- r.p50 = sorted[Math.floor(0.5 * (sorted.length - 1))];
115
- if (aggs.includes('p95'))
116
- r.p95 = sorted[Math.floor(0.95 * (sorted.length - 1))];
117
- }
118
- metricsOut[metric] = r;
119
- }
120
- if (Object.keys(metricsOut).length === 0)
121
- continue;
122
- outBuckets.push({
123
- t: new Date(key).toISOString(),
124
- metrics: metricsOut,
125
- });
126
- }
127
- return {
128
- deviceId,
129
- bucket: opts.bucket,
130
- from: fromIso,
131
- to: toIso,
132
- metrics: [...opts.metrics],
133
- aggs: [...aggs],
134
- buckets: outBuckets,
135
- partial,
136
- notes,
137
- };
138
- }
@@ -1,181 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import readline from 'node:readline';
5
- const DEFAULT_LIMIT = 1000;
6
- function historyDir() {
7
- return path.join(os.homedir(), '.switchbot', 'device-history');
8
- }
9
- /**
10
- * Parse a duration shortcut like "7d", "12h", "30m", "45s" into milliseconds.
11
- * Returns null on malformed input (caller throws UsageError).
12
- */
13
- export function parseDurationToMs(spec) {
14
- const m = spec.trim().match(/^(\d+)(ms|s|m|h|d)$/i);
15
- if (!m)
16
- return null;
17
- const n = Number(m[1]);
18
- const unit = m[2].toLowerCase();
19
- const factor = unit === 'ms' ? 1 : unit === 's' ? 1_000 : unit === 'm' ? 60_000 : unit === 'h' ? 3_600_000 : 86_400_000;
20
- return n * factor;
21
- }
22
- /** Parse ISO-8601 (with Z or offset) or Date-parseable string → ms, else null. */
23
- export function parseInstantToMs(spec) {
24
- const ms = Date.parse(spec);
25
- return Number.isFinite(ms) ? ms : null;
26
- }
27
- export function resolveRange(opts) {
28
- let fromMs = Number.NEGATIVE_INFINITY;
29
- let toMs = Number.POSITIVE_INFINITY;
30
- if (opts.since && (opts.from || opts.to)) {
31
- throw new Error('--since is mutually exclusive with --from/--to.');
32
- }
33
- if (opts.since) {
34
- const durMs = parseDurationToMs(opts.since);
35
- if (durMs === null) {
36
- throw new Error(`Invalid --since value "${opts.since}". Expected e.g. "30s", "15m", "1h", "7d".`);
37
- }
38
- fromMs = Date.now() - durMs;
39
- }
40
- else {
41
- if (opts.from) {
42
- const parsed = parseInstantToMs(opts.from);
43
- if (parsed === null)
44
- throw new Error(`Invalid --from value "${opts.from}". Expected ISO-8601 timestamp.`);
45
- fromMs = parsed;
46
- }
47
- if (opts.to) {
48
- const parsed = parseInstantToMs(opts.to);
49
- if (parsed === null)
50
- throw new Error(`Invalid --to value "${opts.to}". Expected ISO-8601 timestamp.`);
51
- toMs = parsed;
52
- }
53
- if (fromMs > toMs)
54
- throw new Error('--from must be <= --to.');
55
- }
56
- return { fromMs, toMs };
57
- }
58
- /** Return jsonl candidate files for a device, oldest-first. */
59
- export function jsonlFilesForDevice(deviceId, baseDir = historyDir()) {
60
- const out = [];
61
- if (!fs.existsSync(baseDir))
62
- return out;
63
- // Oldest-first so range walks can bail early once a line overshoots `toMs`.
64
- for (let i = 3; i >= 1; i--) {
65
- const p = path.join(baseDir, `${deviceId}.jsonl.${i}`);
66
- if (fs.existsSync(p))
67
- out.push(p);
68
- }
69
- const current = path.join(baseDir, `${deviceId}.jsonl`);
70
- if (fs.existsSync(current))
71
- out.push(current);
72
- return out;
73
- }
74
- function projectFields(record, fields) {
75
- if (fields.length === 0)
76
- return record;
77
- const projected = {};
78
- const payload = (record.payload ?? {});
79
- for (const f of fields) {
80
- if (f in payload)
81
- projected[f] = payload[f];
82
- }
83
- return { t: record.t, topic: record.topic, deviceType: record.deviceType, payload: projected };
84
- }
85
- /**
86
- * Stream-read the JSONL rotation files for `deviceId` and return records
87
- * within [fromMs, toMs]. Parse failures are silently dropped (best-effort).
88
- *
89
- * Files whose mtime < fromMs are skipped whole (coarse but sound: the newest
90
- * record in the file is <= mtime, so nothing in it can match).
91
- */
92
- export async function queryDeviceHistory(deviceId, opts = {}) {
93
- const { fromMs, toMs } = resolveRange(opts);
94
- const limit = Math.max(0, opts.limit ?? DEFAULT_LIMIT);
95
- const fields = opts.fields ?? [];
96
- const files = jsonlFilesForDevice(deviceId);
97
- const out = [];
98
- for (const file of files) {
99
- try {
100
- const stat = fs.statSync(file);
101
- if (stat.mtimeMs < fromMs)
102
- continue;
103
- }
104
- catch {
105
- continue;
106
- }
107
- const stream = fs.createReadStream(file, { encoding: 'utf-8' });
108
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
109
- for await (const line of rl) {
110
- if (!line)
111
- continue;
112
- let rec;
113
- try {
114
- rec = JSON.parse(line);
115
- }
116
- catch {
117
- continue;
118
- }
119
- const tMs = Date.parse(rec.t);
120
- if (!Number.isFinite(tMs))
121
- continue;
122
- if (tMs < fromMs || tMs > toMs)
123
- continue;
124
- out.push(projectFields(rec, fields));
125
- if (out.length >= limit) {
126
- rl.close();
127
- stream.destroy();
128
- return out;
129
- }
130
- }
131
- }
132
- return out;
133
- }
134
- export function queryDeviceHistoryStats(deviceId) {
135
- const dir = historyDir();
136
- const files = jsonlFilesForDevice(deviceId);
137
- let totalBytes = 0;
138
- let oldest = null;
139
- let newest = null;
140
- let count = 0;
141
- for (const file of files) {
142
- try {
143
- totalBytes += fs.statSync(file).size;
144
- }
145
- catch { /* */ }
146
- }
147
- // Walk the oldest file's head + current file's tail for oldest/newest + count.
148
- // Counting is O(records) here, acceptable for "stats" which isn't a hot path.
149
- for (const file of files) {
150
- try {
151
- const lines = fs.readFileSync(file, 'utf-8').split('\n');
152
- for (const line of lines) {
153
- if (!line)
154
- continue;
155
- count += 1;
156
- try {
157
- const rec = JSON.parse(line);
158
- const tMs = Date.parse(rec.t);
159
- if (Number.isFinite(tMs)) {
160
- if (oldest === null || tMs < oldest)
161
- oldest = tMs;
162
- if (newest === null || tMs > newest)
163
- newest = tMs;
164
- }
165
- }
166
- catch { /* */ }
167
- }
168
- }
169
- catch { /* */ }
170
- }
171
- return {
172
- deviceId,
173
- fileCount: files.length,
174
- totalBytes,
175
- recordCount: count,
176
- oldest: oldest !== null ? new Date(oldest).toISOString() : undefined,
177
- newest: newest !== null ? new Date(newest).toISOString() : undefined,
178
- jsonlFiles: files.map((f) => path.basename(f)),
179
- historyDir: dir,
180
- };
181
- }