@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.
- package/README.md +380 -116
- package/dist/commands/devices/streams.d.ts +3 -0
- package/dist/commands/devices/streams.js +71 -27
- package/dist/commands/devices/streams.js.map +1 -1
- package/dist/commands/ingest/batch.d.ts +21 -0
- package/dist/commands/ingest/batch.js +170 -0
- package/dist/commands/ingest/batch.js.map +1 -0
- package/dist/commands/ingest/bitset.d.ts +15 -0
- package/dist/commands/ingest/bitset.js +128 -0
- package/dist/commands/ingest/bitset.js.map +1 -0
- package/dist/commands/ingest/health.d.ts +15 -0
- package/dist/commands/ingest/health.js +94 -0
- package/dist/commands/ingest/health.js.map +1 -0
- package/dist/commands/ingest/image.d.ts +15 -0
- package/dist/commands/ingest/image.js +99 -0
- package/dist/commands/ingest/image.js.map +1 -0
- package/dist/commands/ingest/json.d.ts +16 -0
- package/dist/commands/ingest/json.js +92 -0
- package/dist/commands/ingest/json.js.map +1 -0
- package/dist/commands/ingest/numeric.d.ts +16 -0
- package/dist/commands/ingest/numeric.js +83 -0
- package/dist/commands/ingest/numeric.js.map +1 -0
- package/dist/commands/ingest/text.d.ts +16 -0
- package/dist/commands/ingest/text.js +80 -0
- package/dist/commands/ingest/text.js.map +1 -0
- package/dist/commands/ingest/video.d.ts +16 -0
- package/dist/commands/ingest/video.js +120 -0
- package/dist/commands/ingest/video.js.map +1 -0
- package/dist/commands/query/latest-values.d.ts +1 -0
- package/dist/commands/query/latest-values.js +41 -15
- package/dist/commands/query/latest-values.js.map +1 -1
- package/dist/help.js +103 -53
- package/dist/help.js.map +1 -1
- package/dist/lib/api.d.ts +1 -1
- package/dist/lib/api.js +3 -0
- package/dist/lib/api.js.map +1 -1
- package/oclif.manifest.json +1396 -590
- package/package.json +4 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base-command.js';
|
|
3
|
+
export default class IngestImage extends BaseCommand {
|
|
4
|
+
static description = `Ingest an image URL to a device stream.
|
|
5
|
+
|
|
6
|
+
Sends an image reference (by URL) to the specified device stream. The image must be accessible
|
|
7
|
+
via HTTP/HTTPS. Formant will fetch and store the image from the provided URL.
|
|
8
|
+
|
|
9
|
+
Image streams are used for camera feeds, snapshots, diagnostic images, etc.`;
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> ingest image --device <device-id> --stream camera_front --url https://example.com/image.jpg',
|
|
12
|
+
'<%= config.bin %> ingest image --device <device-id> --stream snapshot --url https://example.com/snap.png --size 1024000',
|
|
13
|
+
'<%= config.bin %> ingest image --device <device-id> --stream detection --url https://example.com/detect.jpg --tag frame=123',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
device: Flags.string({
|
|
17
|
+
char: 'd',
|
|
18
|
+
description: 'Device ID (UUID)',
|
|
19
|
+
required: true,
|
|
20
|
+
}),
|
|
21
|
+
stream: Flags.string({
|
|
22
|
+
char: 's',
|
|
23
|
+
description: 'Stream name',
|
|
24
|
+
required: true,
|
|
25
|
+
}),
|
|
26
|
+
url: Flags.string({
|
|
27
|
+
char: 'u',
|
|
28
|
+
description: 'Image URL (must be http:// or https://)',
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
size: Flags.integer({
|
|
32
|
+
description: 'Image size in bytes (optional)',
|
|
33
|
+
}),
|
|
34
|
+
tag: Flags.string({
|
|
35
|
+
char: 't',
|
|
36
|
+
description: 'Tag as key=value where both are strings (can be specified multiple times)',
|
|
37
|
+
multiple: true,
|
|
38
|
+
}),
|
|
39
|
+
timestamp: Flags.string({
|
|
40
|
+
description: 'Unix timestamp in milliseconds (defaults to now)',
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
static summary = 'Ingest image data';
|
|
44
|
+
async run() {
|
|
45
|
+
// Validate URL format
|
|
46
|
+
if (!this.flags.url.match(/^https?:\/\//i)) {
|
|
47
|
+
this.error('Image URL must start with http:// or https://');
|
|
48
|
+
}
|
|
49
|
+
// Build image value object
|
|
50
|
+
const imageValue = {
|
|
51
|
+
url: this.flags.url,
|
|
52
|
+
};
|
|
53
|
+
if (this.flags.size !== undefined) {
|
|
54
|
+
if (this.flags.size <= 0) {
|
|
55
|
+
this.error('Image size must be a positive number');
|
|
56
|
+
}
|
|
57
|
+
imageValue.size = this.flags.size;
|
|
58
|
+
}
|
|
59
|
+
// Parse tags
|
|
60
|
+
const tags = {};
|
|
61
|
+
if (this.flags.tag) {
|
|
62
|
+
for (const tag of this.flags.tag) {
|
|
63
|
+
const [key, ...valueParts] = tag.split('=');
|
|
64
|
+
if (!key || valueParts.length === 0) {
|
|
65
|
+
this.error(`Invalid tag format: "${tag}". Must be key=value.`);
|
|
66
|
+
}
|
|
67
|
+
tags[key] = valueParts.join('=');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Parse timestamp
|
|
71
|
+
let timestamp = Date.now();
|
|
72
|
+
if (this.flags.timestamp) {
|
|
73
|
+
timestamp = Number.parseInt(this.flags.timestamp, 10);
|
|
74
|
+
if (Number.isNaN(timestamp) || timestamp <= 0) {
|
|
75
|
+
this.error(`Invalid timestamp: "${this.flags.timestamp}". Must be a positive integer.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Build request body
|
|
79
|
+
const body = {
|
|
80
|
+
items: [
|
|
81
|
+
{
|
|
82
|
+
deviceId: this.flags.device,
|
|
83
|
+
name: this.flags.stream,
|
|
84
|
+
type: 'image',
|
|
85
|
+
tags,
|
|
86
|
+
points: [[timestamp, imageValue]],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
// Send request (expect 204 No Content)
|
|
91
|
+
await this.api('ingest', 'batch', { body, method: 'POST' });
|
|
92
|
+
if (!this.jsonEnabled()) {
|
|
93
|
+
this.log(`\n✓ Ingested image ${this.flags.url} to stream "${this.flags.stream}" on device ${this.flags.device} (${this.env})\n`);
|
|
94
|
+
}
|
|
95
|
+
// Return the request body for --json output
|
|
96
|
+
return body;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/commands/ingest/image.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class IngestJson extends BaseCommand<typeof IngestJson> {
|
|
3
|
+
static args: {
|
|
4
|
+
value: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
device: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
stream: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
timestamp: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static summary: string;
|
|
15
|
+
run(): Promise<Record<string, unknown>>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base-command.js';
|
|
3
|
+
export default class IngestJson extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
value: Args.string({ description: 'JSON value to ingest (as a JSON string)', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = `Ingest a JSON data point to a device stream.
|
|
8
|
+
|
|
9
|
+
Sends structured JSON data to the specified device stream. The value must be valid JSON.
|
|
10
|
+
JSON streams are used for complex structured data like configuration objects, state machines,
|
|
11
|
+
nested telemetry, etc.
|
|
12
|
+
|
|
13
|
+
Note: The JSON is sent as a JSON-encoded string to Formant.`;
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> ingest json \'{"x":1,"y":2}\' --device <device-id> --stream position',
|
|
16
|
+
'<%= config.bin %> ingest json \'{"status":"active","mode":"auto"}\' --device <device-id> --stream config',
|
|
17
|
+
'<%= config.bin %> ingest json \'[1,2,3,4,5]\' --device <device-id> --stream array_data',
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
device: Flags.string({
|
|
21
|
+
char: 'd',
|
|
22
|
+
description: 'Device ID (UUID)',
|
|
23
|
+
required: true,
|
|
24
|
+
}),
|
|
25
|
+
stream: Flags.string({
|
|
26
|
+
char: 's',
|
|
27
|
+
description: 'Stream name',
|
|
28
|
+
required: true,
|
|
29
|
+
}),
|
|
30
|
+
tag: Flags.string({
|
|
31
|
+
char: 't',
|
|
32
|
+
description: 'Tag as key=value where both are strings (can be specified multiple times)',
|
|
33
|
+
multiple: true,
|
|
34
|
+
}),
|
|
35
|
+
timestamp: Flags.string({
|
|
36
|
+
description: 'Unix timestamp in milliseconds (defaults to now)',
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
static summary = 'Ingest JSON data';
|
|
40
|
+
async run() {
|
|
41
|
+
// Validate JSON
|
|
42
|
+
let jsonValue;
|
|
43
|
+
try {
|
|
44
|
+
// Parse to validate, but send as string
|
|
45
|
+
JSON.parse(this.args.value);
|
|
46
|
+
jsonValue = this.args.value;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.error(`Invalid JSON value: ${error instanceof Error ? error.message : 'Unknown error'}.\nValue must be valid JSON.`);
|
|
50
|
+
}
|
|
51
|
+
// Parse tags
|
|
52
|
+
const tags = {};
|
|
53
|
+
if (this.flags.tag) {
|
|
54
|
+
for (const tag of this.flags.tag) {
|
|
55
|
+
const [key, ...valueParts] = tag.split('=');
|
|
56
|
+
if (!key || valueParts.length === 0) {
|
|
57
|
+
this.error(`Invalid tag format: "${tag}". Must be key=value.`);
|
|
58
|
+
}
|
|
59
|
+
tags[key] = valueParts.join('=');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Parse timestamp
|
|
63
|
+
let timestamp = Date.now();
|
|
64
|
+
if (this.flags.timestamp) {
|
|
65
|
+
timestamp = Number.parseInt(this.flags.timestamp, 10);
|
|
66
|
+
if (Number.isNaN(timestamp) || timestamp <= 0) {
|
|
67
|
+
this.error(`Invalid timestamp: "${this.flags.timestamp}". Must be a positive integer.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Build request body (json type sends the value as a JSON string)
|
|
71
|
+
const body = {
|
|
72
|
+
items: [
|
|
73
|
+
{
|
|
74
|
+
deviceId: this.flags.device,
|
|
75
|
+
name: this.flags.stream,
|
|
76
|
+
type: 'json',
|
|
77
|
+
tags,
|
|
78
|
+
points: [[timestamp, jsonValue]],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
// Send request (expect 204 No Content)
|
|
83
|
+
await this.api('ingest', 'batch', { body, method: 'POST' });
|
|
84
|
+
if (!this.jsonEnabled()) {
|
|
85
|
+
const preview = jsonValue.length > 50 ? `${jsonValue.slice(0, 47)}...` : jsonValue;
|
|
86
|
+
this.log(`\n✓ Ingested JSON ${preview} to stream "${this.flags.stream}" on device ${this.flags.device} (${this.env})\n`);
|
|
87
|
+
}
|
|
88
|
+
// Return the request body for --json output
|
|
89
|
+
return body;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/commands/ingest/json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class IngestNumeric extends BaseCommand<typeof IngestNumeric> {
|
|
3
|
+
static args: {
|
|
4
|
+
value: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
device: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
stream: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
timestamp: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static summary: string;
|
|
15
|
+
run(): Promise<Record<string, unknown>>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base-command.js';
|
|
3
|
+
export default class IngestNumeric extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
value: Args.string({ description: 'Numeric value to ingest', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = `Ingest a numeric data point to a device stream.
|
|
8
|
+
|
|
9
|
+
Sends a single numeric value to the specified device stream. The value must be a valid number.
|
|
10
|
+
Numeric streams are commonly used for sensor readings like battery level, temperature, speed, etc.`;
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> ingest numeric 42.5 --device <device-id> --stream battery_level',
|
|
13
|
+
'<%= config.bin %> ingest numeric 98.6 --device <device-id> --stream temperature --tag unit=fahrenheit',
|
|
14
|
+
'<%= config.bin %> ingest numeric 1500 --device <device-id> --stream rpm --timestamp 1700000000000',
|
|
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
|
+
tag: Flags.string({
|
|
28
|
+
char: 't',
|
|
29
|
+
description: 'Tag as key=value where both are strings (can be specified multiple times)',
|
|
30
|
+
multiple: true,
|
|
31
|
+
}),
|
|
32
|
+
timestamp: Flags.string({
|
|
33
|
+
description: 'Unix timestamp in milliseconds (defaults to now)',
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
static summary = 'Ingest numeric data';
|
|
37
|
+
async run() {
|
|
38
|
+
// Parse and validate numeric value
|
|
39
|
+
const numericValue = Number.parseFloat(this.args.value);
|
|
40
|
+
if (Number.isNaN(numericValue)) {
|
|
41
|
+
this.error(`Invalid numeric value: "${this.args.value}". Must be a valid number.`);
|
|
42
|
+
}
|
|
43
|
+
// Parse tags
|
|
44
|
+
const tags = {};
|
|
45
|
+
if (this.flags.tag) {
|
|
46
|
+
for (const tag of this.flags.tag) {
|
|
47
|
+
const [key, ...valueParts] = tag.split('=');
|
|
48
|
+
if (!key || valueParts.length === 0) {
|
|
49
|
+
this.error(`Invalid tag format: "${tag}". Must be key=value.`);
|
|
50
|
+
}
|
|
51
|
+
tags[key] = valueParts.join('=');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Parse timestamp
|
|
55
|
+
let timestamp = Date.now();
|
|
56
|
+
if (this.flags.timestamp) {
|
|
57
|
+
timestamp = Number.parseInt(this.flags.timestamp, 10);
|
|
58
|
+
if (Number.isNaN(timestamp) || timestamp <= 0) {
|
|
59
|
+
this.error(`Invalid timestamp: "${this.flags.timestamp}". Must be a positive integer.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Build request body
|
|
63
|
+
const body = {
|
|
64
|
+
items: [
|
|
65
|
+
{
|
|
66
|
+
deviceId: this.flags.device,
|
|
67
|
+
name: this.flags.stream,
|
|
68
|
+
type: 'numeric',
|
|
69
|
+
tags,
|
|
70
|
+
points: [[timestamp, numericValue]],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
// Send request (expect 204 No Content)
|
|
75
|
+
await this.api('ingest', 'batch', { body, method: 'POST' });
|
|
76
|
+
if (!this.jsonEnabled()) {
|
|
77
|
+
this.log(`\n✓ Ingested numeric value ${numericValue} to stream "${this.flags.stream}" on device ${this.flags.device} (${this.env})\n`);
|
|
78
|
+
}
|
|
79
|
+
// Return the request body for --json output
|
|
80
|
+
return body;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=numeric.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numeric.js","sourceRoot":"","sources":["../../../src/commands/ingest/numeric.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class IngestText extends BaseCommand<typeof IngestText> {
|
|
3
|
+
static args: {
|
|
4
|
+
value: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
device: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
stream: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
timestamp: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static summary: string;
|
|
15
|
+
run(): Promise<Record<string, unknown>>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base-command.js';
|
|
3
|
+
export default class IngestText extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
value: Args.string({ description: 'Text value to ingest', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = `Ingest a text data point to a device stream.
|
|
8
|
+
|
|
9
|
+
Sends a single text/string value to the specified device stream. Text streams are commonly
|
|
10
|
+
used for logs, status messages, error messages, and other string data.`;
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> ingest text "Robot started successfully" --device <device-id> --stream status',
|
|
13
|
+
'<%= config.bin %> ingest text "Error: sensor offline" --device <device-id> --stream errors --tag severity=high',
|
|
14
|
+
'<%= config.bin %> ingest text "Checkpoint A reached" --device <device-id> --stream waypoints',
|
|
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
|
+
tag: Flags.string({
|
|
28
|
+
char: 't',
|
|
29
|
+
description: 'Tag as key=value where both are strings (can be specified multiple times)',
|
|
30
|
+
multiple: true,
|
|
31
|
+
}),
|
|
32
|
+
timestamp: Flags.string({
|
|
33
|
+
description: 'Unix timestamp in milliseconds (defaults to now)',
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
static summary = 'Ingest text data';
|
|
37
|
+
async run() {
|
|
38
|
+
const textValue = this.args.value;
|
|
39
|
+
// Parse tags
|
|
40
|
+
const tags = {};
|
|
41
|
+
if (this.flags.tag) {
|
|
42
|
+
for (const tag of this.flags.tag) {
|
|
43
|
+
const [key, ...valueParts] = tag.split('=');
|
|
44
|
+
if (!key || valueParts.length === 0) {
|
|
45
|
+
this.error(`Invalid tag format: "${tag}". Must be key=value.`);
|
|
46
|
+
}
|
|
47
|
+
tags[key] = valueParts.join('=');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Parse timestamp
|
|
51
|
+
let timestamp = Date.now();
|
|
52
|
+
if (this.flags.timestamp) {
|
|
53
|
+
timestamp = Number.parseInt(this.flags.timestamp, 10);
|
|
54
|
+
if (Number.isNaN(timestamp) || timestamp <= 0) {
|
|
55
|
+
this.error(`Invalid timestamp: "${this.flags.timestamp}". Must be a positive integer.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Build request body
|
|
59
|
+
const body = {
|
|
60
|
+
items: [
|
|
61
|
+
{
|
|
62
|
+
deviceId: this.flags.device,
|
|
63
|
+
name: this.flags.stream,
|
|
64
|
+
type: 'text',
|
|
65
|
+
tags,
|
|
66
|
+
points: [[timestamp, textValue]],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
// Send request (expect 204 No Content)
|
|
71
|
+
await this.api('ingest', 'batch', { body, method: 'POST' });
|
|
72
|
+
if (!this.jsonEnabled()) {
|
|
73
|
+
const preview = textValue.length > 50 ? `${textValue.slice(0, 47)}...` : textValue;
|
|
74
|
+
this.log(`\n✓ Ingested text "${preview}" to stream "${this.flags.stream}" on device ${this.flags.device} (${this.env})\n`);
|
|
75
|
+
}
|
|
76
|
+
// Return the request body for --json output
|
|
77
|
+
return body;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/commands/ingest/text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
export default class IngestVideo extends BaseCommand<typeof IngestVideo> {
|
|
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
|
+
duration: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
size: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
timestamp: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
static summary: string;
|
|
15
|
+
run(): Promise<Record<string, unknown>>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base-command.js';
|
|
3
|
+
export default class IngestVideo extends BaseCommand {
|
|
4
|
+
static description = `Ingest a video URL to a device stream.
|
|
5
|
+
|
|
6
|
+
Sends a video reference (by URL) to the specified device stream. The video must be accessible
|
|
7
|
+
via HTTP/HTTPS. You must provide the video duration in milliseconds.
|
|
8
|
+
|
|
9
|
+
Video streams are used for recorded video clips, timelapse videos, event recordings, etc.
|
|
10
|
+
|
|
11
|
+
The MIME type is auto-detected from the file extension (.mp4, .webm, .mov).`;
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> ingest video --device <device-id> --stream recordings --url https://example.com/video.mp4 --duration 5000',
|
|
14
|
+
'<%= config.bin %> ingest video --device <device-id> --stream clips --url https://example.com/clip.webm --duration 3500 --size 2048000',
|
|
15
|
+
'<%= config.bin %> ingest video --device <device-id> --stream event_video --url https://example.com/event.mov --duration 10000 --tag event=collision',
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
device: Flags.string({
|
|
19
|
+
char: 'd',
|
|
20
|
+
description: 'Device ID (UUID)',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
stream: Flags.string({
|
|
24
|
+
char: 's',
|
|
25
|
+
description: 'Stream name',
|
|
26
|
+
required: true,
|
|
27
|
+
}),
|
|
28
|
+
url: Flags.string({
|
|
29
|
+
char: 'u',
|
|
30
|
+
description: 'Video URL (must be http:// or https://)',
|
|
31
|
+
required: true,
|
|
32
|
+
}),
|
|
33
|
+
duration: Flags.integer({
|
|
34
|
+
description: 'Video duration in milliseconds (required)',
|
|
35
|
+
required: true,
|
|
36
|
+
}),
|
|
37
|
+
size: Flags.integer({
|
|
38
|
+
description: 'Video size in bytes (optional)',
|
|
39
|
+
}),
|
|
40
|
+
tag: Flags.string({
|
|
41
|
+
char: 't',
|
|
42
|
+
description: 'Tag as key=value where both are strings (can be specified multiple times)',
|
|
43
|
+
multiple: true,
|
|
44
|
+
}),
|
|
45
|
+
timestamp: Flags.string({
|
|
46
|
+
description: 'Unix timestamp in milliseconds (defaults to now)',
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
static summary = 'Ingest video data';
|
|
50
|
+
async run() {
|
|
51
|
+
// Validate URL format
|
|
52
|
+
if (!this.flags.url.match(/^https?:\/\//i)) {
|
|
53
|
+
this.error('Video URL must start with http:// or https://');
|
|
54
|
+
}
|
|
55
|
+
// Validate duration
|
|
56
|
+
if (this.flags.duration <= 0) {
|
|
57
|
+
this.error('Video duration must be a positive number (milliseconds)');
|
|
58
|
+
}
|
|
59
|
+
// Auto-detect MIME type from extension
|
|
60
|
+
const urlLower = this.flags.url.toLowerCase();
|
|
61
|
+
let mimeType = 'video/mp4'; // default
|
|
62
|
+
if (urlLower.endsWith('.webm')) {
|
|
63
|
+
mimeType = 'video/webm';
|
|
64
|
+
}
|
|
65
|
+
else if (urlLower.endsWith('.mov')) {
|
|
66
|
+
mimeType = 'video/quicktime';
|
|
67
|
+
}
|
|
68
|
+
// Build video value object
|
|
69
|
+
const videoValue = {
|
|
70
|
+
duration: this.flags.duration,
|
|
71
|
+
mimeType,
|
|
72
|
+
url: this.flags.url,
|
|
73
|
+
};
|
|
74
|
+
if (this.flags.size !== undefined) {
|
|
75
|
+
if (this.flags.size <= 0) {
|
|
76
|
+
this.error('Video size must be a positive number');
|
|
77
|
+
}
|
|
78
|
+
videoValue.size = this.flags.size;
|
|
79
|
+
}
|
|
80
|
+
// Parse tags
|
|
81
|
+
const tags = {};
|
|
82
|
+
if (this.flags.tag) {
|
|
83
|
+
for (const tag of this.flags.tag) {
|
|
84
|
+
const [key, ...valueParts] = tag.split('=');
|
|
85
|
+
if (!key || valueParts.length === 0) {
|
|
86
|
+
this.error(`Invalid tag format: "${tag}". Must be key=value.`);
|
|
87
|
+
}
|
|
88
|
+
tags[key] = valueParts.join('=');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Parse timestamp
|
|
92
|
+
let timestamp = Date.now();
|
|
93
|
+
if (this.flags.timestamp) {
|
|
94
|
+
timestamp = Number.parseInt(this.flags.timestamp, 10);
|
|
95
|
+
if (Number.isNaN(timestamp) || timestamp <= 0) {
|
|
96
|
+
this.error(`Invalid timestamp: "${this.flags.timestamp}". Must be a positive integer.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Build request body
|
|
100
|
+
const body = {
|
|
101
|
+
items: [
|
|
102
|
+
{
|
|
103
|
+
deviceId: this.flags.device,
|
|
104
|
+
name: this.flags.stream,
|
|
105
|
+
type: 'video',
|
|
106
|
+
tags,
|
|
107
|
+
points: [[timestamp, videoValue]],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
// Send request (expect 204 No Content)
|
|
112
|
+
await this.api('ingest', 'batch', { body, method: 'POST' });
|
|
113
|
+
if (!this.jsonEnabled()) {
|
|
114
|
+
this.log(`\n✓ Ingested video ${this.flags.url} (${this.flags.duration}ms, ${mimeType}) to stream "${this.flags.stream}" on device ${this.flags.device} (${this.env})\n`);
|
|
115
|
+
}
|
|
116
|
+
// Return the request body for --json output
|
|
117
|
+
return body;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=video.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video.js","sourceRoot":"","sources":["../../../src/commands/ingest/video.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ export default class QueryLatestValues extends BaseCommand<typeof QueryLatestVal
|
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
'all-streams': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
days: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
8
|
device: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
stream: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
};
|