@formant/formant-cli 0.2.0 → 0.3.1

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 (38) hide show
  1. package/README.md +380 -116
  2. package/dist/commands/devices/streams.d.ts +3 -0
  3. package/dist/commands/devices/streams.js +71 -27
  4. package/dist/commands/devices/streams.js.map +1 -1
  5. package/dist/commands/ingest/batch.d.ts +21 -0
  6. package/dist/commands/ingest/batch.js +170 -0
  7. package/dist/commands/ingest/batch.js.map +1 -0
  8. package/dist/commands/ingest/bitset.d.ts +15 -0
  9. package/dist/commands/ingest/bitset.js +128 -0
  10. package/dist/commands/ingest/bitset.js.map +1 -0
  11. package/dist/commands/ingest/health.d.ts +15 -0
  12. package/dist/commands/ingest/health.js +94 -0
  13. package/dist/commands/ingest/health.js.map +1 -0
  14. package/dist/commands/ingest/image.d.ts +15 -0
  15. package/dist/commands/ingest/image.js +99 -0
  16. package/dist/commands/ingest/image.js.map +1 -0
  17. package/dist/commands/ingest/json.d.ts +16 -0
  18. package/dist/commands/ingest/json.js +92 -0
  19. package/dist/commands/ingest/json.js.map +1 -0
  20. package/dist/commands/ingest/numeric.d.ts +16 -0
  21. package/dist/commands/ingest/numeric.js +83 -0
  22. package/dist/commands/ingest/numeric.js.map +1 -0
  23. package/dist/commands/ingest/text.d.ts +16 -0
  24. package/dist/commands/ingest/text.js +80 -0
  25. package/dist/commands/ingest/text.js.map +1 -0
  26. package/dist/commands/ingest/video.d.ts +16 -0
  27. package/dist/commands/ingest/video.js +120 -0
  28. package/dist/commands/ingest/video.js.map +1 -0
  29. package/dist/commands/query/latest-values.d.ts +1 -0
  30. package/dist/commands/query/latest-values.js +41 -15
  31. package/dist/commands/query/latest-values.js.map +1 -1
  32. package/dist/help.js +103 -53
  33. package/dist/help.js.map +1 -1
  34. package/dist/lib/api.d.ts +1 -1
  35. package/dist/lib/api.js +3 -0
  36. package/dist/lib/api.js.map +1 -1
  37. package/oclif.manifest.json +1396 -590
  38. package/package.json +4 -1
@@ -1,56 +1,100 @@
1
- import { Args } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../../base-command.js';
3
3
  import { formatTable } from '../../lib/formatters.js';
4
4
  export default class DevicesStreams extends BaseCommand {
5
5
  static args = {
6
6
  id: Args.string({ description: 'Device ID (UUID)', required: true }),
7
7
  };
8
- static description = `List telemetry streams configured on a device.
8
+ static description = `List telemetry streams for a device.
9
9
 
10
- Shows a clean view of stream definitions parsed from the device configuration,
11
- including stream names, types, topics, and quality settings.`;
10
+ Shows streams parsed from the device configuration as well as any streams
11
+ discovered from actual ingested data (even if not explicitly configured).
12
+ Unconfigured streams are marked with a SOURCE of "data" in the output.`;
12
13
  static examples = [
13
14
  '<%= config.bin %> devices streams <device-id>',
14
15
  '<%= config.bin %> devices streams <device-id> --json',
16
+ '<%= config.bin %> devices streams <device-id> --days 30',
15
17
  ];
18
+ static flags = {
19
+ days: Flags.integer({
20
+ default: 14,
21
+ description: 'How many days back to look for unconfigured streams in ingested data',
22
+ }),
23
+ };
16
24
  static summary = 'List device streams';
17
25
  async run() {
18
- // Fetch device and its configuration
19
- const device = await this.api('admin', `devices/${this.args.id}`, {
26
+ const deviceId = this.args.id;
27
+ // ── 1. Fetch config-based streams ────────────────────────────────────────
28
+ const device = await this.api('admin', `devices/${deviceId}`, {
20
29
  method: 'GET',
21
30
  });
22
31
  const configVersion = device.desiredConfigurationVersion;
23
- if (!configVersion) {
24
- this.error('Device has no configuration version set');
32
+ let configuredStreamNames = new Set();
33
+ let configStreams = [];
34
+ if (configVersion) {
35
+ const config = await this.api('admin', `devices/${deviceId}/configurations/${configVersion}`, { method: 'GET' });
36
+ const doc = config.document;
37
+ const telemetry = doc?.telemetry;
38
+ configStreams = telemetry?.streams || [];
39
+ configuredStreamNames = new Set(configStreams.map((s) => s.name).filter(Boolean));
40
+ }
41
+ // ── 2. Discover streams from actual ingested data ─────────────────────────
42
+ const since = new Date();
43
+ since.setDate(since.getDate() - this.flags.days);
44
+ let metadataItems = [];
45
+ try {
46
+ const metaResult = await this.api('query', 'metadata', {
47
+ body: {
48
+ deviceIds: [deviceId],
49
+ start: since.toISOString(),
50
+ },
51
+ });
52
+ metadataItems = metaResult?.items ?? [];
53
+ }
54
+ catch {
55
+ // metadata endpoint failure is non-fatal; we still return config streams
56
+ }
57
+ // ── 3. Build merged stream list ───────────────────────────────────────────
58
+ // Configured streams come first, annotated with source="config"
59
+ const rows = configStreams.map((s) => {
60
+ const conf = s.configuration;
61
+ return {
62
+ name: s.name,
63
+ quality: s.quality || '—',
64
+ source: 'config',
65
+ topic: conf?.topic || '—',
66
+ type: conf?.type || '—',
67
+ };
68
+ });
69
+ // Append streams seen in data but absent from config
70
+ for (const item of metadataItems) {
71
+ if (!configuredStreamNames.has(item.name)) {
72
+ rows.push({
73
+ name: item.name,
74
+ quality: '—',
75
+ source: 'data',
76
+ topic: '—',
77
+ type: item.type || '—',
78
+ });
79
+ }
25
80
  }
26
- const config = await this.api('admin', `devices/${this.args.id}/configurations/${configVersion}`, { method: 'GET' });
27
- // Parse streams from config
28
- const doc = config.document;
29
- const telemetry = doc?.telemetry;
30
- const streams = telemetry?.streams || [];
31
81
  if (!this.jsonEnabled()) {
32
82
  const columns = [
33
83
  { key: 'name', label: 'NAME', width: 30 },
34
84
  { key: 'type', label: 'TYPE', width: 20 },
35
- { key: 'topic', label: 'TOPIC', width: 40 },
36
- { key: 'quality', label: 'QUALITY', width: 12 },
85
+ { key: 'topic', label: 'TOPIC', width: 36 },
86
+ { key: 'quality', label: 'QUALITY', width: 10 },
87
+ { key: 'source', label: 'SOURCE', width: 8 },
37
88
  ];
38
- // Format streams for display
39
- const rows = streams.map((s) => {
40
- const conf = s.configuration;
41
- return {
42
- name: s.name,
43
- type: conf?.type || '—',
44
- topic: conf?.topic || '—',
45
- quality: s.quality || '—',
46
- };
47
- });
89
+ const configured = rows.filter((r) => r.source === 'config').length;
90
+ const discovered = rows.filter((r) => r.source === 'data').length;
48
91
  this.log(`\nDevice Streams (${this.env}):\n`);
49
- this.log(` Device: ${device.name} (${this.args.id})\n`);
92
+ this.log(` Device: ${device.name} (${deviceId})`);
93
+ this.log(` Configured: ${configured} | Discovered from data (last ${this.flags.days}d): ${discovered}\n`);
50
94
  this.log(formatTable(rows, columns));
51
95
  this.log('');
52
96
  }
53
- return { streams };
97
+ return { streams: rows };
54
98
  }
55
99
  }
56
100
  //# sourceMappingURL=streams.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../src/commands/devices/streams.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../src/commands/devices/streams.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ interface IngestBatchRequest {
3
+ items: Array<{
4
+ deviceId: string;
5
+ name: string;
6
+ type: 'bitset' | 'health' | 'image' | 'json' | 'numeric' | 'text' | 'video';
7
+ tags: Record<string, string>;
8
+ points: Array<[number, unknown]>;
9
+ }>;
10
+ }
11
+ export default class IngestBatch extends BaseCommand<typeof IngestBatch> {
12
+ static description: string;
13
+ static examples: string[];
14
+ static flags: {
15
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ };
18
+ static summary: string;
19
+ run(): Promise<IngestBatchRequest>;
20
+ }
21
+ export {};
@@ -0,0 +1,170 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { Flags } from '@oclif/core';
3
+ import { BaseCommand } from '../../base-command.js';
4
+ export default class IngestBatch extends BaseCommand {
5
+ static description = `Ingest multiple data points in a single batch request.
6
+
7
+ Reads a JSON file (or stdin) containing a batch ingestion payload and sends all data points
8
+ in a single API request. This is more efficient than sending individual data points when you
9
+ have multiple values to ingest.
10
+
11
+ INPUT FORMAT:
12
+ The input JSON must have the following structure:
13
+ {
14
+ "items": [
15
+ {
16
+ "deviceId": "device-uuid", // Required: Device ID (UUID)
17
+ "name": "stream-name", // Required: Stream name
18
+ "type": "numeric|text|json|...", // Required: Data type (see below)
19
+ "tags": {"key": "value"}, // Optional: Tags object (string key-value pairs)
20
+ "points": [[timestamp_ms, value]] // Required: Array of [timestamp, value] pairs
21
+ }
22
+ ]
23
+ }
24
+
25
+ SUPPORTED TYPES AND VALUE FORMATS:
26
+ - numeric: Number value
27
+ Example: "points": [[1708272000000, 42.5]]
28
+
29
+ - text: String value
30
+ Example: "points": [[1708272000000, "hello world"]]
31
+
32
+ - json: JSON object encoded as string
33
+ Example: "points": [[1708272000000, "{\\"x\\":10,\\"y\\":20}"]]
34
+
35
+ - image: Object with url (required), size and annotations (optional)
36
+ Example: "points": [[1708272000000, {"url": "https://example.com/img.jpg", "size": 102400}]]
37
+
38
+ - video: Object with url, duration, mimeType (required), size (optional)
39
+ Example: "points": [[1708272000000, {"url": "https://example.com/vid.mp4", "duration": 30000, "mimeType": "video/mp4"}]]
40
+
41
+ - bitset: Object with keys array and values array (1-1000 pairs)
42
+ Example: "points": [[1708272000000, {"keys": ["door", "window"], "values": [true, false]}]]
43
+
44
+ - health: Object with status (required) and clockSkewMs (optional)
45
+ Example: "points": [[1708272000000, {"status": "operational", "clockSkewMs": 150}]]
46
+ Valid status values: "unknown", "operational", "offline", "error"
47
+
48
+ Each item can have multiple points (timestamp/value pairs) for the same stream.
49
+ Timestamps are Unix milliseconds (use Date.now() for current time).`;
50
+ static examples = [
51
+ `<%= config.bin %> ingest batch --file data.json`,
52
+ `<%= config.bin %> ingest batch --stdin < data.json`,
53
+ `cat payload.json | <%= config.bin %> ingest batch --stdin`,
54
+ `# Example payload.json with multiple types:
55
+ {
56
+ "items": [
57
+ {
58
+ "deviceId": "abc-123",
59
+ "name": "battery_level",
60
+ "type": "numeric",
61
+ "tags": {"env": "prod"},
62
+ "points": [[1708272000000, 42.5], [1708272060000, 41.8]]
63
+ },
64
+ {
65
+ "deviceId": "abc-123",
66
+ "name": "status",
67
+ "type": "text",
68
+ "tags": {"env": "prod"},
69
+ "points": [[1708272000000, "operational"]]
70
+ },
71
+ {
72
+ "deviceId": "abc-123",
73
+ "name": "health",
74
+ "type": "health",
75
+ "tags": {},
76
+ "points": [[1708272000000, {"status": "operational"}]]
77
+ }
78
+ ]
79
+ }`,
80
+ ];
81
+ static flags = {
82
+ file: Flags.string({
83
+ char: 'f',
84
+ description: 'Path to JSON file containing batch payload',
85
+ exclusive: ['stdin'],
86
+ }),
87
+ stdin: Flags.boolean({
88
+ description: 'Read JSON payload from stdin',
89
+ exclusive: ['file'],
90
+ }),
91
+ };
92
+ static summary = 'Ingest batch data from file or stdin';
93
+ async run() {
94
+ // Ensure either --file or --stdin is specified
95
+ if (!this.flags.file && !this.flags.stdin) {
96
+ this.error('Must specify either --file <path> or --stdin');
97
+ }
98
+ // Read input
99
+ let inputJson;
100
+ if (this.flags.file) {
101
+ try {
102
+ inputJson = readFileSync(this.flags.file, 'utf8');
103
+ }
104
+ catch (error) {
105
+ this.error(`Failed to read file "${this.flags.file}": ${error instanceof Error ? error.message : 'Unknown error'}`);
106
+ }
107
+ }
108
+ else {
109
+ // Read from stdin
110
+ const chunks = [];
111
+ for await (const chunk of process.stdin) {
112
+ chunks.push(chunk);
113
+ }
114
+ inputJson = Buffer.concat(chunks).toString('utf8');
115
+ if (!inputJson.trim()) {
116
+ this.error('No data received from stdin');
117
+ }
118
+ }
119
+ // Parse JSON
120
+ let body;
121
+ try {
122
+ body = JSON.parse(inputJson);
123
+ }
124
+ catch (error) {
125
+ this.error(`Invalid JSON: ${error instanceof Error ? error.message : 'Unknown error'}. Input must be valid JSON.`);
126
+ }
127
+ // Validate structure
128
+ if (!body || typeof body !== 'object') {
129
+ this.error('Input must be a JSON object');
130
+ }
131
+ if (!Array.isArray(body.items)) {
132
+ this.error('Input must have an "items" array');
133
+ }
134
+ if (body.items.length === 0) {
135
+ this.error('Items array cannot be empty');
136
+ }
137
+ // Basic validation of items
138
+ for (const [index, item] of body.items.entries()) {
139
+ if (!item.deviceId) {
140
+ this.error(`Item ${index}: Missing required field "deviceId"`);
141
+ }
142
+ if (!item.name) {
143
+ this.error(`Item ${index}: Missing required field "name"`);
144
+ }
145
+ if (!item.type) {
146
+ this.error(`Item ${index}: Missing required field "type"`);
147
+ }
148
+ const validTypes = ['numeric', 'text', 'json', 'image', 'video', 'bitset', 'health'];
149
+ if (!validTypes.includes(item.type)) {
150
+ this.error(`Item ${index}: Invalid type "${item.type}". Must be one of: ${validTypes.join(', ')}`);
151
+ }
152
+ if (!Array.isArray(item.points)) {
153
+ this.error(`Item ${index}: "points" must be an array`);
154
+ }
155
+ if (item.points.length === 0) {
156
+ this.error(`Item ${index}: "points" array cannot be empty`);
157
+ }
158
+ }
159
+ // Count total points
160
+ const totalPoints = body.items.reduce((sum, item) => sum + item.points.length, 0);
161
+ // Send request (expect 204 No Content)
162
+ await this.api('ingest', 'batch', { body, method: 'POST' });
163
+ if (!this.jsonEnabled()) {
164
+ this.log(`\n✓ Ingested batch: ${body.items.length} stream(s), ${totalPoints} data point(s) (${this.env})\n`);
165
+ }
166
+ // Return the request body for --json output
167
+ return body;
168
+ }
169
+ }
170
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../../src/commands/ingest/batch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class IngestBitset extends BaseCommand<typeof IngestBitset> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ device: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ stream: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ keys: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ values: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ timestamp: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ static summary: string;
14
+ run(): Promise<Record<string, unknown>>;
15
+ }
@@ -0,0 +1,128 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ export default class IngestBitset extends BaseCommand {
4
+ static description = `Ingest a bitset data point to a device stream.
5
+
6
+ Sends a bitset (array of named boolean values) to the specified device stream. A bitset
7
+ consists of parallel arrays of keys (strings) and values (booleans).
8
+
9
+ Bitsets are useful for representing sets of binary states, flags, or boolean sensor readings
10
+ where you have multiple named on/off states.
11
+
12
+ Constraints:
13
+ - Must have 1-1000 key/value pairs
14
+ - Keys must be max 255 characters each
15
+ - Keys and values arrays must have the same length`;
16
+ static examples = [
17
+ '<%= config.bin %> ingest bitset --device <device-id> --stream sensors --keys "door,window,motion" --values "true,false,true"',
18
+ '<%= config.bin %> ingest bitset --device <device-id> --stream flags --keys "active,paused,error" --values "true,false,false" --tag system=main',
19
+ '<%= config.bin %> ingest bitset --device <device-id> --stream states --keys "a,b" --values "false,true"',
20
+ ];
21
+ static flags = {
22
+ device: Flags.string({
23
+ char: 'd',
24
+ description: 'Device ID (UUID)',
25
+ required: true,
26
+ }),
27
+ stream: Flags.string({
28
+ char: 's',
29
+ description: 'Stream name',
30
+ required: true,
31
+ }),
32
+ keys: Flags.string({
33
+ char: 'k',
34
+ description: 'Comma-separated list of key names (1-1000 keys, max 255 chars each)',
35
+ required: true,
36
+ }),
37
+ values: Flags.string({
38
+ char: 'v',
39
+ description: 'Comma-separated list of boolean values (true/false, must match key count)',
40
+ required: true,
41
+ }),
42
+ tag: Flags.string({
43
+ char: 't',
44
+ description: 'Tag as key=value where both are strings (can be specified multiple times)',
45
+ multiple: true,
46
+ }),
47
+ timestamp: Flags.string({
48
+ description: 'Unix timestamp in milliseconds (defaults to now)',
49
+ }),
50
+ };
51
+ static summary = 'Ingest bitset data';
52
+ async run() {
53
+ // Parse keys
54
+ const keys = this.flags.keys.split(',').map((k) => k.trim());
55
+ // Parse values
56
+ const valueStrings = this.flags.values.split(',').map((v) => v.trim().toLowerCase());
57
+ const values = [];
58
+ for (const v of valueStrings) {
59
+ if (v === 'true') {
60
+ values.push(true);
61
+ }
62
+ else if (v === 'false') {
63
+ values.push(false);
64
+ }
65
+ else {
66
+ this.error(`Invalid boolean value: "${v}". Must be "true" or "false".`);
67
+ }
68
+ }
69
+ // Validate arrays have same length
70
+ if (keys.length !== values.length) {
71
+ this.error(`Keys and values must have the same count. Got ${keys.length} keys and ${values.length} values.`);
72
+ }
73
+ // Validate count (1-1000)
74
+ if (keys.length < 1 || keys.length > 1000) {
75
+ this.error('Bitset must have between 1 and 1000 key/value pairs.');
76
+ }
77
+ // Validate key lengths
78
+ for (const key of keys) {
79
+ if (key.length > 255) {
80
+ this.error(`Key "${key}" exceeds maximum length of 255 characters.`);
81
+ }
82
+ }
83
+ // Build bitset value
84
+ const bitsetValue = {
85
+ keys,
86
+ values,
87
+ };
88
+ // Parse tags
89
+ const tags = {};
90
+ if (this.flags.tag) {
91
+ for (const tag of this.flags.tag) {
92
+ const [tagKey, ...valueParts] = tag.split('=');
93
+ if (!tagKey || valueParts.length === 0) {
94
+ this.error(`Invalid tag format: "${tag}". Must be key=value.`);
95
+ }
96
+ tags[tagKey] = valueParts.join('=');
97
+ }
98
+ }
99
+ // Parse timestamp
100
+ let timestamp = Date.now();
101
+ if (this.flags.timestamp) {
102
+ timestamp = Number.parseInt(this.flags.timestamp, 10);
103
+ if (Number.isNaN(timestamp) || timestamp <= 0) {
104
+ this.error(`Invalid timestamp: "${this.flags.timestamp}". Must be a positive integer.`);
105
+ }
106
+ }
107
+ // Build request body
108
+ const body = {
109
+ items: [
110
+ {
111
+ deviceId: this.flags.device,
112
+ name: this.flags.stream,
113
+ type: 'bitset',
114
+ tags,
115
+ points: [[timestamp, bitsetValue]],
116
+ },
117
+ ],
118
+ };
119
+ // Send request (expect 204 No Content)
120
+ await this.api('ingest', 'batch', { body, method: 'POST' });
121
+ if (!this.jsonEnabled()) {
122
+ this.log(`\n✓ Ingested bitset with ${keys.length} entries to stream "${this.flags.stream}" on device ${this.flags.device} (${this.env})\n`);
123
+ }
124
+ // Return the request body for --json output
125
+ return body;
126
+ }
127
+ }
128
+ //# sourceMappingURL=bitset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitset.js","sourceRoot":"","sources":["../../../src/commands/ingest/bitset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class IngestHealth extends BaseCommand<typeof IngestHealth> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ device: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ stream: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ status: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'clock-skew': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ timestamp: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ static summary: string;
14
+ run(): Promise<Record<string, unknown>>;
15
+ }
@@ -0,0 +1,94 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ export default class IngestHealth extends BaseCommand {
4
+ static description = `Ingest a health status data point to a device stream.
5
+
6
+ Sends device health status information to the specified stream. Health data consists of:
7
+ - status: One of "unknown", "operational", "offline", or "error"
8
+ - clockSkewMs (optional): Clock skew in milliseconds between device and server
9
+
10
+ Health streams are used to track device operational state, system health, and time synchronization.`;
11
+ static examples = [
12
+ '<%= config.bin %> ingest health --device <device-id> --stream system_health --status operational',
13
+ '<%= config.bin %> ingest health --device <device-id> --stream health --status error --tag component=motor',
14
+ '<%= config.bin %> ingest health --device <device-id> --stream sync --status operational --clock-skew 150',
15
+ ];
16
+ static flags = {
17
+ device: Flags.string({
18
+ char: 'd',
19
+ description: 'Device ID (UUID)',
20
+ required: true,
21
+ }),
22
+ stream: Flags.string({
23
+ char: 's',
24
+ description: 'Stream name',
25
+ required: true,
26
+ }),
27
+ status: Flags.string({
28
+ description: 'Health status (unknown, operational, offline, or error)',
29
+ options: ['unknown', 'operational', 'offline', 'error'],
30
+ required: true,
31
+ }),
32
+ 'clock-skew': Flags.integer({
33
+ description: 'Clock skew in milliseconds (optional)',
34
+ }),
35
+ tag: Flags.string({
36
+ char: 't',
37
+ description: 'Tag as key=value where both are strings (can be specified multiple times)',
38
+ multiple: true,
39
+ }),
40
+ timestamp: Flags.string({
41
+ description: 'Unix timestamp in milliseconds (defaults to now)',
42
+ }),
43
+ };
44
+ static summary = 'Ingest health status data';
45
+ async run() {
46
+ // Build health value
47
+ const healthValue = {
48
+ status: this.flags.status,
49
+ };
50
+ if (this.flags['clock-skew'] !== undefined) {
51
+ healthValue.clockSkewMs = this.flags['clock-skew'];
52
+ }
53
+ // Parse tags
54
+ const tags = {};
55
+ if (this.flags.tag) {
56
+ for (const tag of this.flags.tag) {
57
+ const [key, ...valueParts] = tag.split('=');
58
+ if (!key || valueParts.length === 0) {
59
+ this.error(`Invalid tag format: "${tag}". Must be key=value.`);
60
+ }
61
+ tags[key] = valueParts.join('=');
62
+ }
63
+ }
64
+ // Parse timestamp
65
+ let timestamp = Date.now();
66
+ if (this.flags.timestamp) {
67
+ timestamp = Number.parseInt(this.flags.timestamp, 10);
68
+ if (Number.isNaN(timestamp) || timestamp <= 0) {
69
+ this.error(`Invalid timestamp: "${this.flags.timestamp}". Must be a positive integer.`);
70
+ }
71
+ }
72
+ // Build request body
73
+ const body = {
74
+ items: [
75
+ {
76
+ deviceId: this.flags.device,
77
+ name: this.flags.stream,
78
+ type: 'health',
79
+ tags,
80
+ points: [[timestamp, healthValue]],
81
+ },
82
+ ],
83
+ };
84
+ // Send request (expect 204 No Content)
85
+ await this.api('ingest', 'batch', { body, method: 'POST' });
86
+ if (!this.jsonEnabled()) {
87
+ const skewInfo = this.flags['clock-skew'] ? `, clock skew: ${this.flags['clock-skew']}ms` : '';
88
+ this.log(`\n✓ Ingested health status "${this.flags.status}"${skewInfo} to stream "${this.flags.stream}" on device ${this.flags.device} (${this.env})\n`);
89
+ }
90
+ // Return the request body for --json output
91
+ return body;
92
+ }
93
+ }
94
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../../src/commands/ingest/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,aAAa,CAAA;AAEjC,OAAO,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAA;AAEjD,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,WAAgC;IACxE,MAAM,CAAU,WAAW,GAAG;;;;;;oGAMoE,CAAA;IAElG,MAAM,CAAU,QAAQ,GAAG;QACzB,kGAAkG;QAClG,2GAA2G;QAC3G,0GAA0G;KAC3G,CAAA;IAED,MAAM,CAAU,KAAK,GAAG;QACtB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,kBAAkB;YAC/B,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,aAAa;YAC1B,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,WAAW,EAAE,yDAAyD;YACtE,OAAO,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC;YACvD,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC;YAC1B,WAAW,EAAE,uCAAuC;SACrD,CAAC;QACF,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC;YAChB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,2EAA2E;YACxF,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC;YACtB,WAAW,EAAE,kDAAkD;SAChE,CAAC;KACH,CAAA;IAED,MAAM,CAAU,OAAO,GAAG,2BAA2B,CAAA;IAE9C,KAAK,CAAC,GAAG;QACd,qBAAqB;QACrB,MAAM,WAAW,GAA4B;YAC3C,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;SAC1B,CAAA;QAED,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,SAAS,EAAE,CAAC;YAC3C,WAAW,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;QACpD,CAAC;QAED,aAAa;QACb,MAAM,IAAI,GAA2B,EAAE,CAAA;QACvC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YACnB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;gBACjC,MAAM,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;gBAC3C,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACpC,IAAI,CAAC,KAAK,CAAC,wBAAwB,GAAG,uBAAuB,CAAC,CAAA;gBAChE,CAAC;gBAED,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAClC,CAAC;QACH,CAAC;QAED,kBAAkB;QAClB,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC1B,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACzB,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;YACrD,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;gBAC9C,IAAI,CAAC,KAAK,CAAC,uBAAuB,IAAI,CAAC,KAAK,CAAC,SAAS,gCAAgC,CAAC,CAAA;YACzF,CAAC;QACH,CAAC;QAED,qBAAqB;QACrB,MAAM,IAAI,GAAG;YACX,KAAK,EAAE;gBACL;oBACE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;oBAC3B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;oBACvB,IAAI,EAAE,QAAQ;oBACd,IAAI;oBACJ,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;iBACnC;aACF;SACF,CAAA;QAED,uCAAuC;QACvC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAC,CAAC,CAAA;QAEzD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,iBAAiB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;YAC9F,IAAI,CAAC,GAAG,CACN,+BAA+B,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,QAAQ,eAAe,IAAI,CAAC,KAAK,CAAC,MAAM,eAAe,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,GAAG,KAAK,CAC/I,CAAA;QACH,CAAC;QAED,4CAA4C;QAC5C,OAAO,IAAI,CAAA;IACb,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class IngestImage extends BaseCommand<typeof IngestImage> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ device: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ stream: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ url: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ size: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ timestamp: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ static summary: string;
14
+ run(): Promise<Record<string, unknown>>;
15
+ }