@formant/formant-cli 0.4.2 → 0.4.3

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 CHANGED
@@ -33,7 +33,9 @@ This CLI provides programmatic access to all Formant capabilities:
33
33
  - List, create, rename, and delete robots/sensors
34
34
  - Add tags for organization (environment, location, customer)
35
35
  - View real-time device status and configuration
36
- - List available telemetry streams
36
+ - List available telemetry streams with per-stream data presence (datapoint counts, last seen, freshness)
37
+ - Data-based last seen timestamps and datapoint counts on device listings
38
+ - Filter to devices that have ingested data (`--with-data`)
37
39
 
38
40
  ### Telemetry Queries
39
41
  - Query historical sensor data (battery, temperature, position, custom metrics)
@@ -171,8 +173,8 @@ The CLI will automatically load credentials from your `.env` file.
171
173
  # View your organization
172
174
  fcli org get
173
175
 
174
- # List all devices
175
- fcli device list --dev
176
+ # List devices with data
177
+ fcli device list --with-data --dev
176
178
 
177
179
  # Get device details
178
180
  fcli device get <device-id> --dev
@@ -214,15 +216,20 @@ Create, list, and manage robots/sensors in your fleet.
214
216
 
215
217
  ```bash
216
218
  # Listing and filtering
217
- fcli device list # List all devices
218
- fcli device list --online # Only online devices
219
+ fcli device list # Online devices (default)
220
+ fcli device list --include-offline # Include offline devices
221
+ fcli device list --with-data # Only devices with ingested data
222
+ fcli device list --with-data --days 90 # Widen the search window (default: 30d)
219
223
  fcli device list --tag location=warehouse # Filter by tag
220
224
 
221
225
  # Device details
222
226
  fcli device get <device-id> # Get full device details
223
227
  fcli device config <device-id> # Get device configuration
224
- fcli device streams <device-id> # List available data streams
225
- fcli device last-seen <device-id> # Get last activity timestamp
228
+
229
+ # Stream discovery with data presence
230
+ fcli device streams <device-id> # List streams with datapoint counts,
231
+ # last seen (ISO), and freshness
232
+ fcli device streams <device-id> --days 30 # Adjust presence lookback (default: 7d)
226
233
 
227
234
  # Device management
228
235
  fcli device create --name "robot-001" # Create a new device
@@ -454,14 +461,29 @@ These flags work with any command:
454
461
  Human-readable tables optimized for terminal viewing:
455
462
 
456
463
  ```bash
457
- $ fcli device list --dev
464
+ $ fcli device list --with-data --dev
458
465
 
459
- Devices (dev):
466
+ Devices — with data (dev):
460
467
 
461
- NAME ID STATUS TAGS
462
- ────────────────────────────────────────────────────────────────────────
463
- robot-001 a1b2c3d4-... online env=prod, location=warehouse
464
- robot-002 e5f6g7h8-... offline env=dev
468
+ NAME ID ONLINE TYPE LAST SEEN (30D) DATAPOINTS (30D)
469
+ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
470
+ carter.1 a8840212-a0a4-4d0b-8a47-098563894748 false default 2026-02-19T19:05:37.536Z 720156
471
+ DJI Matrice 4E 10f78fd5-c00d-42d9-94c4-b310646f59be false default 2026-02-18T01:17:41.167Z 917051
472
+ ```
473
+
474
+ ```bash
475
+ $ fcli device streams <device-id> --dev
476
+
477
+ Device Streams (dev):
478
+
479
+ Device: Walt (020e59a1-...)
480
+ Configured: 10 | Discovered from data: 34
481
+ Presence (last 7d): 0 active, 32 recent, 0 stale, 0 dormant, 12 no data
482
+
483
+ NAME TYPE SOURCE DATAPOINTS LAST SEEN FRESHNESS
484
+ spot.robot_state.battery numeric data 20195 2026-02-19T23:37:01.281Z recent
485
+ spot.localization text data 16962 2026-02-19T23:37:01.271Z recent
486
+ spot.hand.image custom config — — —
465
487
  ```
466
488
 
467
489
  ### JSON
@@ -469,17 +491,15 @@ robot-002 e5f6g7h8-... offline env=dev
469
491
  Machine-readable JSON for scripting and automation:
470
492
 
471
493
  ```bash
472
- $ fcli device list --dev --json
494
+ $ fcli device list --with-data --dev --json
473
495
  {
474
496
  "items": [
475
497
  {
476
- "id": "a1b2c3d4-...",
477
- "name": "robot-001",
478
- "online": true,
479
- "tags": {
480
- "env": "prod",
481
- "location": "warehouse"
482
- }
498
+ "id": "a8840212-...",
499
+ "name": "carter.1",
500
+ "online": false,
501
+ "last_seen": "2026-02-19T19:05:37.536Z",
502
+ "datapoints": 720156
483
503
  }
484
504
  ]
485
505
  }
@@ -496,17 +516,20 @@ fcli device list --json | jq '.items[] | select(.online==true) | .name'
496
516
  ### Monitor fleet health
497
517
 
498
518
  ```bash
519
+ # Find devices that have data
520
+ fcli device list --with-data
521
+
522
+ # Find devices with data, wider search window
523
+ fcli device list --with-data --days 90
524
+
499
525
  # Check which devices are offline
500
- fcli device list --json | jq '.items[] | select(.online==false) | .name'
526
+ fcli device list --include-offline --json | jq '.items[] | select(.online==false) | .name'
501
527
 
502
528
  # View recent critical events across the fleet
503
529
  fcli event list --severity critical --limit 50
504
530
 
505
- # Check battery levels
506
- for device in $(fcli device list --json | jq -r '.items[].id'); do
507
- echo "Device: $device"
508
- fcli query latest-values --device $device --stream battery_level
509
- done
531
+ # Discover what streams a device has and their data freshness
532
+ fcli device streams <device-id>
510
533
  ```
511
534
 
512
535
  ### Delegate a task to an AI persona
@@ -3,15 +3,21 @@ export default class DevicesList extends BaseCommand<typeof DevicesList> {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
+ 'include-offline': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ days: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
7
8
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
8
9
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'with-data': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
11
  tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
12
  };
11
13
  static summary: string;
12
14
  run(): Promise<{
13
15
  items: unknown[];
14
16
  }>;
17
+ /**
18
+ * Query analytics SQL for per-device last seen and datapoint counts.
19
+ */
20
+ private getLastSeenMap;
15
21
  /**
16
22
  * Try to get online device IDs from the presence service.
17
23
  * Returns null if the endpoint fails or is unavailable.
@@ -1,21 +1,30 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../../base-command.js';
3
3
  import { formatTable } from '../../lib/formatters.js';
4
+ import { buildLastSeenSQL, toIsoDateTime } from '../../lib/presence.js';
4
5
  export default class DevicesList extends BaseCommand {
5
6
  static description = `List robots and sensors in your fleet. Shows only online devices by default.
6
7
 
7
- Use --all to include offline devices.`;
8
+ Use --include-offline to include offline devices. Use --with-data to show only
9
+ devices that have ingested data. Each device is enriched with data-based last
10
+ seen timestamp and datapoint count from analytics.`;
8
11
  static examples = [
9
12
  '<%= config.bin %> device list',
10
- '<%= config.bin %> device list --all',
11
- '<%= config.bin %> device list --all --limit 100',
13
+ '<%= config.bin %> device list --include-offline',
14
+ '<%= config.bin %> device list --with-data',
15
+ '<%= config.bin %> device list --with-data --days 90',
16
+ '<%= config.bin %> device list --include-offline --limit 100',
12
17
  '<%= config.bin %> device list --tag location=warehouse',
13
18
  '<%= config.bin %> device list --name robot --dev --json',
14
19
  ];
15
20
  static flags = {
16
- all: Flags.boolean({
21
+ 'include-offline': Flags.boolean({
17
22
  char: 'a',
18
- description: 'Show all devices (online and offline)',
23
+ description: 'Include offline devices',
24
+ }),
25
+ days: Flags.integer({
26
+ default: 30,
27
+ description: 'How many days back to search for last seen data',
19
28
  }),
20
29
  limit: Flags.integer({
21
30
  char: 'l',
@@ -26,6 +35,10 @@ Use --all to include offline devices.`;
26
35
  char: 'n',
27
36
  description: 'Filter devices by name (search)',
28
37
  }),
38
+ 'with-data': Flags.boolean({
39
+ char: 'w',
40
+ description: 'Only show devices that have data in the last --days window',
41
+ }),
29
42
  tag: Flags.string({
30
43
  char: 't',
31
44
  description: 'Filter by tag (key=value), can be specified multiple times',
@@ -55,7 +68,7 @@ Use --all to include offline devices.`;
55
68
  }
56
69
  const limit = this.flags.limit;
57
70
  let filtered;
58
- if (this.flags.all) {
71
+ if (this.flags['include-offline'] || this.flags['with-data']) {
59
72
  // --all: simple single fetch, enrich with online status
60
73
  const result = await this.api('admin', 'devices/query', { body: { ...baseBody, count: limit } });
61
74
  const onlineIds = await this.getOnlineDeviceIds();
@@ -96,14 +109,29 @@ Use --all to include offline devices.`;
96
109
  }
97
110
  }
98
111
  }
112
+ // ── Enrich with analytics-based last seen + datapoint counts ─────────────
113
+ const deviceIds = filtered.map((d) => d.id).filter(Boolean);
114
+ const lastSeenMap = await this.getLastSeenMap(deviceIds);
115
+ for (const device of filtered) {
116
+ const info = lastSeenMap.get(device.id);
117
+ device.last_seen = info ? toIsoDateTime(info.last_seen) : null;
118
+ device.datapoints = info?.total_points ?? null;
119
+ }
120
+ // ── Filter to devices with data if requested ──────────────────────────────
121
+ if (this.flags['with-data']) {
122
+ filtered = filtered.filter((d) => d.last_seen !== null);
123
+ }
99
124
  const output = { items: filtered };
100
125
  if (!this.jsonEnabled()) {
101
- const mode = this.flags.all ? 'all' : 'online';
126
+ const mode = this.flags['with-data'] ? 'with data' : this.flags['include-offline'] ? 'all' : 'online';
127
+ const days = this.flags.days;
102
128
  const columns = [
103
- { key: 'name', label: 'NAME', width: 30 },
129
+ { key: 'name', label: 'NAME', width: 24 },
104
130
  { key: 'id', label: 'ID', width: 40 },
105
- { key: 'online', label: 'ONLINE', width: 10 },
106
- { key: 'type', label: 'TYPE', width: 14 },
131
+ { key: 'online', label: 'ONLINE', width: 8 },
132
+ { key: 'type', label: 'TYPE', width: 10 },
133
+ { key: 'last_seen', label: `LAST SEEN (${days}D)`, width: 28 },
134
+ { key: 'datapoints', label: `DATAPOINTS (${days}D)`, width: 16 },
107
135
  ];
108
136
  this.log(`\nDevices — ${mode} (${this.env}):\n`);
109
137
  this.log(formatTable(filtered, columns));
@@ -111,6 +139,32 @@ Use --all to include offline devices.`;
111
139
  }
112
140
  return output;
113
141
  }
142
+ /**
143
+ * Query analytics SQL for per-device last seen and datapoint counts.
144
+ */
145
+ async getLastSeenMap(deviceIds) {
146
+ const map = new Map();
147
+ if (deviceIds.length === 0)
148
+ return map;
149
+ try {
150
+ const sql = buildLastSeenSQL({
151
+ days: this.flags.days,
152
+ deviceIds,
153
+ });
154
+ const result = await this.api('query', 'analytics/rows', {
155
+ body: { sqlQuery: sql, type: 'advanced' },
156
+ });
157
+ if (!result.error && result.rows) {
158
+ for (const row of result.rows) {
159
+ map.set(row.device_id, row);
160
+ }
161
+ }
162
+ }
163
+ catch {
164
+ // non-fatal
165
+ }
166
+ return map;
167
+ }
114
168
  /**
115
169
  * Try to get online device IDs from the presence service.
116
170
  * Returns null if the endpoint fails or is unavailable.
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/commands/device/list.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/commands/device/list.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ export default class DevicesStreams extends BaseCommand<typeof DevicesStreams> {
7
7
  static examples: string[];
8
8
  static flags: {
9
9
  days: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'with-data': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  };
11
12
  static summary: string;
12
13
  run(): Promise<{
@@ -1,27 +1,36 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../../base-command.js';
3
3
  import { formatTable } from '../../lib/formatters.js';
4
+ import { buildPresenceSQL, getFreshness, toIsoDateTime, } from '../../lib/presence.js';
4
5
  export default class DevicesStreams extends BaseCommand {
5
6
  static args = {
6
7
  id: Args.string({ description: 'Device ID (UUID)', required: true }),
7
8
  };
8
- static description = `List telemetry streams for a device.
9
+ static description = `List telemetry streams for a device with data presence.
9
10
 
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.`;
11
+ Shows streams from the device configuration and any streams discovered from
12
+ ingested data. Each stream is enriched with presence info from the analytics
13
+ backend: data point count, last seen time, and a freshness indicator
14
+ (active/recent/stale/dormant).
15
+
16
+ Unconfigured streams (discovered from data only) are marked with SOURCE "data".`;
13
17
  static examples = [
14
18
  '<%= config.bin %> device streams <device-id>',
19
+ '<%= config.bin %> device streams <device-id> --with-data',
20
+ '<%= config.bin %> device streams <device-id> --with-data --days 30',
15
21
  '<%= config.bin %> device streams <device-id> --json',
16
- '<%= config.bin %> device streams <device-id> --days 30',
17
22
  ];
18
23
  static flags = {
19
24
  days: Flags.integer({
20
- default: 14,
21
- description: 'How many days back to look for unconfigured streams in ingested data',
25
+ default: 7,
26
+ description: 'How many days back to look for stream data presence',
27
+ }),
28
+ 'with-data': Flags.boolean({
29
+ char: 'w',
30
+ description: 'Only show streams that have data in the --days window',
22
31
  }),
23
32
  };
24
- static summary = 'List device streams';
33
+ static summary = 'List device streams with data presence';
25
34
  async run() {
26
35
  const deviceId = this.args.id;
27
36
  // ── 1. Fetch config-based streams ────────────────────────────────────────
@@ -49,48 +58,94 @@ Unconfigured streams are marked with a SOURCE of "data" in the output.`;
49
58
  start: since.toISOString(),
50
59
  },
51
60
  });
52
- metadataItems = metaResult?.items ?? [];
61
+ // Deduplicate metadata by stream name (the API may return multiple
62
+ // entries for the same stream with different tags)
63
+ const seen = new Set();
64
+ for (const item of metaResult?.items ?? []) {
65
+ if (!seen.has(item.name)) {
66
+ seen.add(item.name);
67
+ metadataItems.push(item);
68
+ }
69
+ }
53
70
  }
54
71
  catch {
55
72
  // metadata endpoint failure is non-fatal; we still return config streams
56
73
  }
57
- // ── 3. Build merged stream list ───────────────────────────────────────────
58
- // Configured streams come first, annotated with source="config"
74
+ // ── 3. Fetch per-stream data presence via analytics SQL ──────────────────
75
+ let presenceMap = new Map();
76
+ try {
77
+ const sql = buildPresenceSQL({
78
+ days: this.flags.days,
79
+ deviceIds: [deviceId],
80
+ });
81
+ const presenceResult = await this.api('query', 'analytics/rows', {
82
+ body: { sqlQuery: sql, type: 'advanced' },
83
+ });
84
+ if (!presenceResult.error && presenceResult.rows) {
85
+ for (const row of presenceResult.rows) {
86
+ presenceMap.set(row.stream_name, row);
87
+ }
88
+ }
89
+ }
90
+ catch {
91
+ // presence failure is non-fatal
92
+ }
93
+ // ── 4. Build merged stream list ───────────────────────────────────────────
59
94
  const rows = configStreams.map((s) => {
60
95
  const conf = s.configuration;
96
+ const name = s.name;
97
+ const presence = presenceMap.get(name);
61
98
  return {
62
- name: s.name,
63
- quality: s.quality || '—',
99
+ datapoints: presence?.data_points ?? null,
100
+ first_seen: presence ? toIsoDateTime(presence.first_seen) : null,
101
+ freshness: presence ? getFreshness(presence.last_seen) : null,
102
+ last_seen: presence ? toIsoDateTime(presence.last_seen) : null,
103
+ name,
64
104
  source: 'config',
65
- topic: conf?.topic || '—',
66
105
  type: conf?.type || '—',
67
106
  };
68
107
  });
69
- // Append streams seen in data but absent from config
70
108
  for (const item of metadataItems) {
71
109
  if (!configuredStreamNames.has(item.name)) {
110
+ const presence = presenceMap.get(item.name);
72
111
  rows.push({
112
+ datapoints: presence?.data_points ?? null,
113
+ first_seen: presence ? toIsoDateTime(presence.first_seen) : null,
114
+ freshness: presence ? getFreshness(presence.last_seen) : null,
115
+ last_seen: presence ? toIsoDateTime(presence.last_seen) : null,
73
116
  name: item.name,
74
- quality: '—',
75
117
  source: 'data',
76
- topic: '—',
77
118
  type: item.type || '—',
78
119
  });
79
120
  }
80
121
  }
122
+ // ── 5. Filter to streams with data if requested ────────────────────────
123
+ if (this.flags['with-data']) {
124
+ rows.splice(0, rows.length, ...rows.filter((r) => r.datapoints !== null));
125
+ }
81
126
  if (!this.jsonEnabled()) {
82
127
  const columns = [
83
- { key: 'name', label: 'NAME', width: 30 },
84
- { key: 'type', label: 'TYPE', width: 20 },
85
- { key: 'topic', label: 'TOPIC', width: 36 },
86
- { key: 'quality', label: 'QUALITY', width: 10 },
128
+ { key: 'name', label: 'NAME', width: 34 },
129
+ { key: 'type', label: 'TYPE', width: 14 },
87
130
  { key: 'source', label: 'SOURCE', width: 8 },
131
+ { key: 'datapoints', label: `DATAPOINTS (${this.flags.days}D)`, width: 18 },
132
+ { key: 'last_seen', label: `LAST SEEN (${this.flags.days}D)`, width: 28 },
133
+ { key: 'freshness', label: 'FRESHNESS', width: 10 },
88
134
  ];
89
135
  const configured = rows.filter((r) => r.source === 'config').length;
90
136
  const discovered = rows.filter((r) => r.source === 'data').length;
91
- this.log(`\nDevice Streams (${this.env}):\n`);
137
+ const active = rows.filter((r) => r.freshness === 'active').length;
138
+ const recent = rows.filter((r) => r.freshness === 'recent').length;
139
+ const stale = rows.filter((r) => r.freshness === 'stale').length;
140
+ const dormant = rows.filter((r) => r.freshness === 'dormant').length;
141
+ const noData = rows.filter((r) => r.freshness === null).length;
142
+ const mode = this.flags['with-data'] ? ' — with data' : '';
143
+ this.log(`\nDevice Streams${mode} (${this.env}):\n`);
92
144
  this.log(` Device: ${device.name} (${deviceId})`);
93
- this.log(` Configured: ${configured} | Discovered from data (last ${this.flags.days}d): ${discovered}\n`);
145
+ this.log(` Configured: ${configured} | Discovered from data: ${discovered}`);
146
+ this.log(` Presence (last ${this.flags.days}d): ${active} active, ${recent} recent, ${stale} stale, ${dormant} dormant` +
147
+ (noData > 0 ? `, ${noData} no data` : ''));
148
+ this.log('');
94
149
  this.log(formatTable(rows, columns));
95
150
  this.log('');
96
151
  }
@@ -1 +1 @@
1
- {"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../src/commands/device/streams.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streams.js","sourceRoot":"","sources":["../../../src/commands/device/streams.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared utilities for querying per-stream per-device data presence
3
+ * via the analytics SQL backend.
4
+ *
5
+ * Uses a UNION ALL across all telemetry tables to discover what data
6
+ * exists, grouped by (device_id, stream_name, stream_type).
7
+ */
8
+ export interface StreamPresenceRow {
9
+ data_points: number;
10
+ device_id: string;
11
+ first_seen: string;
12
+ last_seen: string;
13
+ stream_name: string;
14
+ stream_type: string;
15
+ }
16
+ /**
17
+ * Build a SQL query that returns per-stream per-device data presence.
18
+ *
19
+ * Returns columns: device_id, stream_name, stream_type, data_points, first_seen, last_seen
20
+ */
21
+ export declare function buildPresenceSQL(options: {
22
+ days?: number;
23
+ deviceIds?: string[];
24
+ limit?: number;
25
+ streamNames?: string[];
26
+ streamTypes?: string[];
27
+ }): string;
28
+ /**
29
+ * Build a SQL query that returns the last time any data was seen per device.
30
+ *
31
+ * Returns columns: device_id, last_seen, total_points, stream_count
32
+ */
33
+ export declare function buildLastSeenSQL(options: {
34
+ days?: number;
35
+ deviceIds?: string[];
36
+ }): string;
37
+ /**
38
+ * Normalize a timestamp string from analytics SQL (e.g. "2026-02-19 23:37:04.747")
39
+ * to ISO 8601 format ("2026-02-19T23:37:04.747Z").
40
+ */
41
+ export declare function toIsoDateTime(sqlTimestamp: string): string;
42
+ /**
43
+ * Freshness label based on how recently data was seen.
44
+ */
45
+ export type FreshnessLevel = 'active' | 'dormant' | 'recent' | 'stale';
46
+ export declare function getFreshness(lastSeenIso: string): FreshnessLevel;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared utilities for querying per-stream per-device data presence
3
+ * via the analytics SQL backend.
4
+ *
5
+ * Uses a UNION ALL across all telemetry tables to discover what data
6
+ * exists, grouped by (device_id, stream_name, stream_type).
7
+ */
8
+ /**
9
+ * The telemetry tables that hold stream data.
10
+ * Each has: organization_id, device_id, name, tags, time, and type-specific value columns.
11
+ */
12
+ const TELEMETRY_TABLES = [
13
+ { streamType: 'numeric', table: 'query_numeric' },
14
+ { streamType: 'text', table: 'query_text' },
15
+ { streamType: 'json', table: 'query_json' },
16
+ { streamType: 'location', table: 'query_location' },
17
+ { streamType: 'battery', table: 'query_battery' },
18
+ { streamType: 'health', table: 'query_health' },
19
+ { streamType: 'bitset', table: 'query_bitset' },
20
+ { streamType: 'numeric_set', table: 'query_numeric_set' },
21
+ ];
22
+ /**
23
+ * Build a SQL query that returns per-stream per-device data presence.
24
+ *
25
+ * Returns columns: device_id, stream_name, stream_type, data_points, first_seen, last_seen
26
+ */
27
+ export function buildPresenceSQL(options) {
28
+ const { days = 7, deviceIds, limit = 500, streamNames, streamTypes } = options;
29
+ // Filter to requested stream types or use all
30
+ const tables = streamTypes
31
+ ? TELEMETRY_TABLES.filter((t) => streamTypes.includes(t.streamType))
32
+ : TELEMETRY_TABLES;
33
+ if (tables.length === 0) {
34
+ throw new Error(`No matching stream types. Valid types: ${TELEMETRY_TABLES.map((t) => t.streamType).join(', ')}`);
35
+ }
36
+ const deviceFilter = deviceIds?.length
37
+ ? `AND device_id IN (${deviceIds.map((id) => `'${id}'`).join(', ')})`
38
+ : '';
39
+ const nameFilter = streamNames?.length
40
+ ? `AND name IN (${streamNames.map((n) => `'${n}'`).join(', ')})`
41
+ : '';
42
+ const subqueries = tables.map(({ streamType, table }) => `SELECT device_id, name AS stream_name, '${streamType}' AS stream_type, ` +
43
+ `count(*) AS data_points, min(time) AS first_seen, max(time) AS last_seen ` +
44
+ `FROM ${table} ` +
45
+ `WHERE time > now() - INTERVAL ${days} DAY ${deviceFilter} ${nameFilter} ` +
46
+ `GROUP BY device_id, name`);
47
+ return (`SELECT device_id, stream_name, stream_type, data_points, first_seen, last_seen FROM (\n` +
48
+ subqueries.join('\nUNION ALL\n') +
49
+ `\n) ORDER BY last_seen DESC LIMIT ${limit}`);
50
+ }
51
+ /**
52
+ * Build a SQL query that returns the last time any data was seen per device.
53
+ *
54
+ * Returns columns: device_id, last_seen, total_points, stream_count
55
+ */
56
+ export function buildLastSeenSQL(options) {
57
+ const { days = 30, deviceIds } = options;
58
+ const deviceFilter = deviceIds?.length
59
+ ? `AND device_id IN (${deviceIds.map((id) => `'${id}'`).join(', ')})`
60
+ : '';
61
+ const subqueries = TELEMETRY_TABLES.map(({ table }) => `SELECT device_id, max(time) AS last_seen, count(*) AS data_points, count(DISTINCT name) AS stream_count ` +
62
+ `FROM ${table} ` +
63
+ `WHERE time > now() - INTERVAL ${days} DAY ${deviceFilter} ` +
64
+ `GROUP BY device_id`);
65
+ return (`SELECT device_id, max(last_seen) AS last_seen, sum(data_points) AS total_points, sum(stream_count) AS stream_count FROM (\n` +
66
+ subqueries.join('\nUNION ALL\n') +
67
+ `\n) GROUP BY device_id ORDER BY last_seen DESC`);
68
+ }
69
+ /**
70
+ * Normalize a timestamp string from analytics SQL (e.g. "2026-02-19 23:37:04.747")
71
+ * to ISO 8601 format ("2026-02-19T23:37:04.747Z").
72
+ */
73
+ export function toIsoDateTime(sqlTimestamp) {
74
+ if (!sqlTimestamp)
75
+ return sqlTimestamp;
76
+ // Already ISO
77
+ if (sqlTimestamp.includes('T'))
78
+ return sqlTimestamp;
79
+ // Replace first space with T, append Z if no timezone
80
+ const iso = sqlTimestamp.replace(' ', 'T');
81
+ return iso.endsWith('Z') ? iso : iso + 'Z';
82
+ }
83
+ export function getFreshness(lastSeenIso) {
84
+ const lastSeen = new Date(lastSeenIso).getTime();
85
+ const now = Date.now();
86
+ const hoursAgo = (now - lastSeen) / (1000 * 60 * 60);
87
+ if (hoursAgo < 1)
88
+ return 'active';
89
+ if (hoursAgo < 24)
90
+ return 'recent';
91
+ if (hoursAgo < 7 * 24)
92
+ return 'stale';
93
+ return 'dormant';
94
+ }
95
+ //# sourceMappingURL=presence.js.map