@formant/formant-cli 0.3.1 → 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 +193 -142
- package/dist/commands/{commands → command}/for-device.js +2 -2
- package/dist/commands/command/for-device.js.map +1 -0
- package/dist/commands/{commands → command}/get.js +2 -2
- package/dist/commands/command/get.js.map +1 -0
- package/dist/commands/{commands → command}/history.js +3 -3
- package/dist/commands/command/history.js.map +1 -0
- package/dist/commands/{commands → command}/list.js +2 -2
- package/dist/commands/command/list.js.map +1 -0
- package/dist/commands/{commands → command}/send.js +5 -5
- package/dist/commands/command/send.js.map +1 -0
- package/dist/commands/{devices → device}/config.js +2 -2
- package/dist/commands/device/config.js.map +1 -0
- package/dist/commands/{devices → device}/create.js +3 -3
- package/dist/commands/device/create.js.map +1 -0
- package/dist/commands/{devices → device}/delete.js +2 -2
- package/dist/commands/device/delete.js.map +1 -0
- package/dist/commands/{devices → device}/get.js +2 -2
- package/dist/commands/device/get.js.map +1 -0
- package/dist/commands/{devices → device}/list.d.ts +7 -1
- package/dist/commands/{devices → device}/list.js +67 -13
- package/dist/commands/device/list.js.map +1 -0
- package/dist/commands/{devices → device}/rename.js +2 -2
- package/dist/commands/device/rename.js.map +1 -0
- package/dist/commands/{devices → device}/streams.d.ts +1 -0
- package/dist/commands/device/streams.js +155 -0
- package/dist/commands/device/streams.js.map +1 -0
- package/dist/commands/{devices → device}/tag.js +3 -3
- package/dist/commands/device/tag.js.map +1 -0
- package/dist/commands/{devices → device}/untag.js +3 -3
- package/dist/commands/device/untag.js.map +1 -0
- package/dist/commands/{events → event}/get.js +2 -2
- package/dist/commands/event/get.js.map +1 -0
- package/dist/commands/{events → event}/list.js +4 -4
- package/dist/commands/event/list.js.map +1 -0
- package/dist/commands/{event-triggers → event-trigger}/get.js +2 -2
- package/dist/commands/event-trigger/get.js.map +1 -0
- package/dist/commands/{event-triggers → event-trigger}/list.js +2 -2
- package/dist/commands/event-trigger/list.js.map +1 -0
- package/dist/commands/{fleets → fleet}/get.js +2 -2
- package/dist/commands/fleet/get.js.map +1 -0
- package/dist/commands/{fleets → fleet}/list.js +2 -2
- package/dist/commands/fleet/list.js.map +1 -0
- package/dist/commands/{investigations → investigation}/analytics.js +3 -3
- package/dist/commands/investigation/analytics.js.map +1 -0
- package/dist/commands/{investigations → investigation}/get.js +2 -2
- package/dist/commands/investigation/get.js.map +1 -0
- package/dist/commands/{investigations → investigation}/list.js +3 -3
- package/dist/commands/investigation/list.js.map +1 -0
- package/dist/commands/{investigations → investigation}/run.js +3 -3
- package/dist/commands/investigation/run.js.map +1 -0
- package/dist/commands/{investigations → investigation}/runs-list.js +3 -3
- package/dist/commands/investigation/runs-list.js.map +1 -0
- package/dist/commands/{investigations → investigation}/runs.js +2 -2
- package/dist/commands/investigation/runs.js.map +1 -0
- package/dist/commands/{investigations → investigation}/stats.js +2 -2
- package/dist/commands/investigation/stats.js.map +1 -0
- package/dist/commands/{investigations → investigation}/trigger.js +2 -2
- package/dist/commands/investigation/trigger.js.map +1 -0
- package/dist/commands/persona/delegate-task.d.ts +14 -0
- package/dist/commands/persona/delegate-task.js +50 -0
- package/dist/commands/persona/delegate-task.js.map +1 -0
- package/dist/commands/persona/get.d.ts +10 -0
- package/dist/commands/persona/get.js +98 -0
- package/dist/commands/persona/get.js.map +1 -0
- package/dist/commands/{signals → persona}/list.d.ts +1 -1
- package/dist/commands/persona/list.js +31 -0
- package/dist/commands/persona/list.js.map +1 -0
- package/dist/commands/persona/task-status.d.ts +12 -0
- package/dist/commands/persona/task-status.js +57 -0
- package/dist/commands/persona/task-status.js.map +1 -0
- package/dist/commands/{schedules → schedule}/get.js +2 -2
- package/dist/commands/schedule/get.js.map +1 -0
- package/dist/commands/{schedules → schedule}/list.js +2 -2
- package/dist/commands/schedule/list.js.map +1 -0
- package/dist/commands/{signals → signal}/count.js +3 -3
- package/dist/commands/signal/count.js.map +1 -0
- package/dist/commands/signal/create.d.ts +16 -0
- package/dist/commands/signal/create.js +95 -0
- package/dist/commands/signal/create.js.map +1 -0
- package/dist/commands/{signals → signal}/get.js +3 -3
- package/dist/commands/signal/get.js.map +1 -0
- package/dist/commands/signal/list.d.ts +12 -0
- package/dist/commands/signal/list.js +85 -0
- package/dist/commands/signal/list.js.map +1 -0
- package/dist/commands/{signals → signal}/query.d.ts +1 -0
- package/dist/commands/signal/query.js +100 -0
- package/dist/commands/signal/query.js.map +1 -0
- package/dist/commands/signal/set-ground-truth.d.ts +12 -0
- package/dist/commands/signal/set-ground-truth.js +46 -0
- package/dist/commands/signal/set-ground-truth.js.map +1 -0
- package/dist/commands/{users → user}/get.js +2 -2
- package/dist/commands/user/get.js.map +1 -0
- package/dist/commands/{users → user}/list.js +2 -2
- package/dist/commands/user/list.js.map +1 -0
- package/dist/help.js +53 -47
- 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/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/presence.d.ts +46 -0
- package/dist/lib/presence.js +95 -0
- package/dist/lib/presence.js.map +1 -0
- package/oclif.manifest.json +1192 -790
- package/package.json +28 -25
- package/dist/commands/commands/for-device.js.map +0 -1
- package/dist/commands/commands/get.js.map +0 -1
- package/dist/commands/commands/history.js.map +0 -1
- package/dist/commands/commands/list.js.map +0 -1
- package/dist/commands/commands/send.js.map +0 -1
- package/dist/commands/devices/config.js.map +0 -1
- package/dist/commands/devices/create.js.map +0 -1
- package/dist/commands/devices/delete.js.map +0 -1
- package/dist/commands/devices/get.js.map +0 -1
- package/dist/commands/devices/last-seen.d.ts +0 -10
- package/dist/commands/devices/last-seen.js +0 -25
- package/dist/commands/devices/last-seen.js.map +0 -1
- package/dist/commands/devices/list.js.map +0 -1
- package/dist/commands/devices/rename.js.map +0 -1
- package/dist/commands/devices/streams.js +0 -100
- package/dist/commands/devices/streams.js.map +0 -1
- package/dist/commands/devices/tag.js.map +0 -1
- package/dist/commands/devices/untag.js.map +0 -1
- package/dist/commands/event-triggers/get.js.map +0 -1
- package/dist/commands/event-triggers/list.js.map +0 -1
- package/dist/commands/events/get.js.map +0 -1
- package/dist/commands/events/list.js.map +0 -1
- package/dist/commands/fleets/get.js.map +0 -1
- package/dist/commands/fleets/list.js.map +0 -1
- package/dist/commands/investigations/analytics.js.map +0 -1
- package/dist/commands/investigations/get.js.map +0 -1
- package/dist/commands/investigations/list.js.map +0 -1
- package/dist/commands/investigations/run.js.map +0 -1
- package/dist/commands/investigations/runs-list.js.map +0 -1
- package/dist/commands/investigations/runs.js.map +0 -1
- package/dist/commands/investigations/stats.js.map +0 -1
- package/dist/commands/investigations/trigger.js.map +0 -1
- package/dist/commands/schedules/get.js.map +0 -1
- package/dist/commands/schedules/list.js.map +0 -1
- package/dist/commands/signals/count.js.map +0 -1
- package/dist/commands/signals/get.js.map +0 -1
- package/dist/commands/signals/list.js +0 -30
- package/dist/commands/signals/list.js.map +0 -1
- package/dist/commands/signals/query.js +0 -52
- package/dist/commands/signals/query.js.map +0 -1
- package/dist/commands/users/get.js.map +0 -1
- package/dist/commands/users/list.js.map +0 -1
- /package/dist/commands/{commands → command}/for-device.d.ts +0 -0
- /package/dist/commands/{commands → command}/get.d.ts +0 -0
- /package/dist/commands/{commands → command}/history.d.ts +0 -0
- /package/dist/commands/{commands → command}/list.d.ts +0 -0
- /package/dist/commands/{commands → command}/send.d.ts +0 -0
- /package/dist/commands/{devices → device}/config.d.ts +0 -0
- /package/dist/commands/{devices → device}/create.d.ts +0 -0
- /package/dist/commands/{devices → device}/delete.d.ts +0 -0
- /package/dist/commands/{devices → device}/get.d.ts +0 -0
- /package/dist/commands/{devices → device}/rename.d.ts +0 -0
- /package/dist/commands/{devices → device}/tag.d.ts +0 -0
- /package/dist/commands/{devices → device}/untag.d.ts +0 -0
- /package/dist/commands/{events → event}/get.d.ts +0 -0
- /package/dist/commands/{events → event}/list.d.ts +0 -0
- /package/dist/commands/{event-triggers → event-trigger}/get.d.ts +0 -0
- /package/dist/commands/{event-triggers → event-trigger}/list.d.ts +0 -0
- /package/dist/commands/{fleets → fleet}/get.d.ts +0 -0
- /package/dist/commands/{fleets → fleet}/list.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/analytics.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/get.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/list.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/run.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/runs-list.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/runs.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/stats.d.ts +0 -0
- /package/dist/commands/{investigations → investigation}/trigger.d.ts +0 -0
- /package/dist/commands/{schedules → schedule}/get.d.ts +0 -0
- /package/dist/commands/{schedules → schedule}/list.d.ts +0 -0
- /package/dist/commands/{signals → signal}/count.d.ts +0 -0
- /package/dist/commands/{signals → signal}/get.d.ts +0 -0
- /package/dist/commands/{users → user}/get.d.ts +0 -0
- /package/dist/commands/{users → user}/list.d.ts +0 -0
package/dist/help.js
CHANGED
|
@@ -3,17 +3,17 @@ export default class FcliHelp extends Help {
|
|
|
3
3
|
async showRootHelp() {
|
|
4
4
|
const output = `
|
|
5
5
|
███████╗ ██████╗██╗ ██╗
|
|
6
|
-
██╔════╝██╔════╝██║ ██║
|
|
7
|
-
█████╗ ██║ ██║ ██║
|
|
8
|
-
██╔══╝ ██║ ██║ ██║
|
|
6
|
+
██╔════╝██╔════╝██║ ██║ Formant CLI v${this.config.version}
|
|
7
|
+
█████╗ ██║ ██║ ██║ Manage your robot fleet
|
|
8
|
+
██╔══╝ ██║ ██║ ██║ from the command line
|
|
9
9
|
██║ ╚██████╗███████╗██║
|
|
10
10
|
╚═╝ ╚═════╝╚══════╝╚═╝
|
|
11
11
|
|
|
12
12
|
ABOUT
|
|
13
13
|
Formant is a cloud platform for monitoring, operating, and analyzing robot fleets.
|
|
14
14
|
This CLI lets you interact with your robots, query telemetry data, trigger AI-powered
|
|
15
|
-
investigations,
|
|
16
|
-
your terminal.
|
|
15
|
+
investigations, delegate tasks to AI personas, and automate fleet operations — all
|
|
16
|
+
from your terminal.
|
|
17
17
|
|
|
18
18
|
USAGE
|
|
19
19
|
$ fcli <command> [flags]
|
|
@@ -24,23 +24,25 @@ KEY CONCEPTS
|
|
|
24
24
|
Event An important occurrence (alert, warning, error) emitted by a device
|
|
25
25
|
Signal A point of interest that triggers investigation or analysis
|
|
26
26
|
Investigation AI-powered workflow that analyzes device data and diagnoses issues
|
|
27
|
+
Persona An AI agent with specific tools and instructions
|
|
27
28
|
Stream Real-time telemetry data (battery, temperature, location, sensors)
|
|
28
29
|
Command Remote instruction sent to a device to perform an action
|
|
29
30
|
Fleet A logical group of devices (by location, type, or tags)
|
|
30
31
|
|
|
31
32
|
COMMANDS
|
|
32
33
|
org View and update your Formant organization
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
device Create, list, tag, and manage robots/sensors in your fleet
|
|
35
|
+
event View and filter important events emitted by devices
|
|
35
36
|
query Retrieve historical telemetry data and latest sensor values
|
|
36
37
|
ingest Send telemetry data to devices via the Formant ingestion API
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
signal List, query, and analyze signals that trigger investigations
|
|
39
|
+
investigation Trigger, monitor, and analyze AI-powered diagnostic workflows
|
|
40
|
+
persona Manage AI personas and delegate async tasks
|
|
41
|
+
command Send remote commands to devices and view command history
|
|
42
|
+
event-trigger Configure rules that automatically generate events and signals
|
|
43
|
+
user List and inspect users in your organization
|
|
44
|
+
fleet Manage device groups and organize your fleet
|
|
45
|
+
schedule Set up recurring tasks and one-time scheduled jobs
|
|
44
46
|
analytics Execute custom SQL queries against your analytics database
|
|
45
47
|
kv Store and retrieve metadata in the key-value store
|
|
46
48
|
help Display help for fcli
|
|
@@ -65,16 +67,16 @@ EXAMPLES
|
|
|
65
67
|
$ fcli org update --name "Acme Robotics" # Update org name
|
|
66
68
|
|
|
67
69
|
# Device management
|
|
68
|
-
$ fcli
|
|
69
|
-
$ fcli
|
|
70
|
-
$ fcli
|
|
71
|
-
$ fcli
|
|
72
|
-
$ fcli
|
|
70
|
+
$ fcli device list --online # List all online devices
|
|
71
|
+
$ fcli device list --tag location=warehouse # Filter by tags
|
|
72
|
+
$ fcli device create --name "robot-001" # Create a new device
|
|
73
|
+
$ fcli device tag <device-id> --key env --value prod # Add tags
|
|
74
|
+
$ fcli device rename <device-id> --name "robot-new" # Rename a device
|
|
73
75
|
|
|
74
76
|
# Telemetry queries
|
|
75
77
|
$ fcli query --device <id> --stream battery_level --start 2026-01-01 --end 2026-01-02
|
|
76
78
|
$ fcli query latest-values --device <id> --stream battery_level
|
|
77
|
-
$ fcli
|
|
79
|
+
$ fcli device streams <device-id> # List available streams
|
|
78
80
|
|
|
79
81
|
# Ingest telemetry data
|
|
80
82
|
$ fcli ingest numeric 42.5 --device <id> --stream battery_level
|
|
@@ -83,37 +85,41 @@ EXAMPLES
|
|
|
83
85
|
$ fcli ingest batch --file payload.json # Batch ingest from file
|
|
84
86
|
|
|
85
87
|
# Events and monitoring
|
|
86
|
-
$ fcli
|
|
87
|
-
$ fcli
|
|
88
|
-
$ fcli
|
|
88
|
+
$ fcli event list --severity critical --limit 50 # View critical events
|
|
89
|
+
$ fcli event list --device <id> --start 2026-01-01 # Events for one device
|
|
90
|
+
$ fcli event get <event-id> # Get event details
|
|
89
91
|
|
|
90
|
-
# AI
|
|
91
|
-
$ fcli
|
|
92
|
-
$ fcli
|
|
93
|
-
$ fcli
|
|
94
|
-
$ fcli
|
|
92
|
+
# AI personas
|
|
93
|
+
$ fcli persona list # List all personas
|
|
94
|
+
$ fcli persona get <persona-id> # View persona tools
|
|
95
|
+
$ fcli persona delegate-task <persona-id> "Analyze recent failures"
|
|
96
|
+
$ fcli persona task-status <task-id> # Check task results
|
|
95
97
|
|
|
96
|
-
#
|
|
97
|
-
$ fcli
|
|
98
|
-
$ fcli
|
|
99
|
-
$ fcli
|
|
100
|
-
|
|
98
|
+
# AI investigations
|
|
99
|
+
$ fcli investigation list # List all investigations
|
|
100
|
+
$ fcli investigation trigger <id> -i "Robot stopped" # Trigger an investigation
|
|
101
|
+
$ fcli investigation runs <id> # View investigation runs
|
|
102
|
+
|
|
103
|
+
# Signals and ground truths
|
|
104
|
+
$ fcli signal create "Motor overheated on device X" # Create a manual signal
|
|
105
|
+
$ fcli signal create "Sensor fault" -i <inv-id> -g "Should detect thermal issue"
|
|
106
|
+
$ fcli signal list # List all signals
|
|
107
|
+
$ fcli signal list --investigation <inv-id> # List with ground truths
|
|
108
|
+
$ fcli signal query --start 2026-01-01 # Query by time range
|
|
109
|
+
$ fcli signal set-ground-truth <signal-id> <inv-id> "Expected outcome"
|
|
101
110
|
|
|
102
111
|
# Remote commands
|
|
103
|
-
$ fcli
|
|
104
|
-
$ fcli
|
|
105
|
-
$ fcli
|
|
106
|
-
$ fcli commands history <device-id> # View command history
|
|
112
|
+
$ fcli command list # List command templates
|
|
113
|
+
$ fcli command for-device <device-id> # Commands for one device
|
|
114
|
+
$ fcli command send <device-id> <template-id> --param speed=10
|
|
107
115
|
|
|
108
116
|
# Event triggers (automation)
|
|
109
|
-
$ fcli event-
|
|
110
|
-
$ fcli event-
|
|
117
|
+
$ fcli event-trigger list # List all trigger rules
|
|
118
|
+
$ fcli event-trigger get <trigger-id> # View trigger details
|
|
111
119
|
|
|
112
120
|
# User and fleet management
|
|
113
|
-
$ fcli
|
|
114
|
-
$ fcli
|
|
115
|
-
$ fcli fleets list # List device groups
|
|
116
|
-
$ fcli fleets get <fleet-id> # View fleet details
|
|
121
|
+
$ fcli user list # List all users
|
|
122
|
+
$ fcli fleet list # List device groups
|
|
117
123
|
|
|
118
124
|
# Analytics and data queries
|
|
119
125
|
$ fcli analytics tables # List available tables
|
|
@@ -125,10 +131,10 @@ EXAMPLES
|
|
|
125
131
|
$ fcli kv set <device-id> <key> <value> # Store a value
|
|
126
132
|
|
|
127
133
|
TIPS
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
* Use --json with any command to get machine-readable output for scripting
|
|
135
|
+
* Pipe output to jq for advanced JSON processing: fcli device list --json | jq
|
|
136
|
+
* Set --dev or --stage to target non-production environments
|
|
137
|
+
* Use fcli <command> --help to see detailed help for any command
|
|
132
138
|
|
|
133
139
|
LEARN MORE
|
|
134
140
|
Documentation: https://formant.io/docs
|
package/dist/help.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,aAAa,CAAA;AAEhC,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,IAAI;IACrB,KAAK,CAAC,YAAY;QACnC,MAAM,MAAM,GAAG;;
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,aAAa,CAAA;AAEhC,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,IAAI;IACrB,KAAK,CAAC,YAAY;QACnC,MAAM,MAAM,GAAG;;kDAE+B,IAAI,CAAC,MAAM,CAAC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyIpE,CAAA;QAEG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAClB,CAAC;CACF"}
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Environment } from './config.js';
|
|
2
|
-
export type ApiTarget = 'admin' | 'ingest' | 'query' | 'theopolis';
|
|
2
|
+
export type ApiTarget = 'admin' | 'gateway' | 'ingest' | 'query' | 'theopolis';
|
|
3
3
|
interface RequestOptions {
|
|
4
4
|
body?: unknown;
|
|
5
5
|
method?: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
|
package/dist/lib/api.js
CHANGED
package/dist/lib/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,OAAO,EAAC,MAAM,aAAa,CAAA;AACrD,OAAO,EAAC,KAAK,EAAC,MAAM,WAAW,CAAA;AAU/B,SAAS,YAAY,CAAC,GAAgB,EAAE,MAAiB;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACzB,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,GAAG,IAAI,CAAC,QAAQ,WAAW,CAAA;QACpC,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,OAAO,GAAG,IAAI,CAAC,QAAQ,YAAY,CAAA;QACrC,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,GAAG,IAAI,CAAC,QAAQ,aAAa,CAAA;QACtC,CAAC;QAED,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,GAAG,IAAI,CAAC,SAAS,MAAM,CAAA;QAChC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAgB,EAChB,MAAiB,EACjB,IAAY,EACZ,UAA0B,EAAE;IAE5B,MAAM,EAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,EAAC,GAAG,OAAO,CAAA;IAC7D,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;IAE7B,IAAI,GAAG,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAA;IAChD,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,CAAA;QACzC,GAAG,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAA;IAChC,CAAC;IAED,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;QAC3C,cAAc,EAAE,kBAAkB;KACnC,CAAA;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7C,OAAO;QACP,MAAM;KACP,CAAC,CAAA;IAEF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7B,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,CAAC,MAAM,IAAI,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC,CAAA;IACtE,CAAC;IAED,wBAAwB;IACxB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,SAAc,CAAA;IAE7C,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAA;AAChC,CAAC"}
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,OAAO,EAAC,MAAM,aAAa,CAAA;AACrD,OAAO,EAAC,KAAK,EAAC,MAAM,WAAW,CAAA;AAU/B,SAAS,YAAY,CAAC,GAAgB,EAAE,MAAiB;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACzB,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,GAAG,IAAI,CAAC,QAAQ,WAAW,CAAA;QACpC,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,OAAO,GAAG,IAAI,CAAC,OAAO,MAAM,CAAA;QAC9B,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,OAAO,GAAG,IAAI,CAAC,QAAQ,YAAY,CAAA;QACrC,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,GAAG,IAAI,CAAC,QAAQ,aAAa,CAAA;QACtC,CAAC;QAED,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,GAAG,IAAI,CAAC,SAAS,MAAM,CAAA;QAChC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAgB,EAChB,MAAiB,EACjB,IAAY,EACZ,UAA0B,EAAE;IAE5B,MAAM,EAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,EAAC,GAAG,OAAO,CAAA;IAC7D,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;IAE7B,IAAI,GAAG,GAAG,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAA;IAChD,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,CAAA;QACzC,GAAG,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAA;IAChC,CAAC;IAED,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;QAC3C,cAAc,EAAE,kBAAkB;KACnC,CAAA;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7C,OAAO;QACP,MAAM;KACP,CAAC,CAAA;IAEF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7B,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,CAAC,MAAM,IAAI,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC,CAAA;IACtE,CAAC;IAED,wBAAwB;IACxB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,SAAc,CAAA;IAE7C,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAA;AAChC,CAAC"}
|
package/dist/lib/config.d.ts
CHANGED
package/dist/lib/config.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
const ENVIRONMENTS = {
|
|
2
2
|
prod: {
|
|
3
3
|
adminApi: 'https://api.formant.io',
|
|
4
|
+
gateway: 'https://gateway-service-prod-f814f074944f.herokuapp.com',
|
|
4
5
|
queryApi: 'https://api.formant.io',
|
|
5
6
|
theopolis: 'https://theopolis-prod-ad4d9de77f9a.herokuapp.com',
|
|
6
7
|
},
|
|
7
8
|
dev: {
|
|
8
9
|
adminApi: 'https://api-dev.formant.io',
|
|
10
|
+
gateway: 'https://gateway-service-dev-47d47ae014aa.herokuapp.com',
|
|
9
11
|
queryApi: 'https://api-dev.formant.io',
|
|
10
12
|
theopolis: 'https://theopolis-dev-231bd5d86d0f.herokuapp.com',
|
|
11
13
|
},
|
|
12
14
|
stage: {
|
|
13
15
|
adminApi: 'https://api-stage.formant.io',
|
|
16
|
+
gateway: 'https://gateway-service-stage-515bbe9b1906.herokuapp.com',
|
|
14
17
|
queryApi: 'https://api-stage.formant.io',
|
|
15
18
|
theopolis: 'https://theopolis-stage-80d087320b3d.herokuapp.com',
|
|
16
19
|
},
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AASA,MAAM,YAAY,GAAiC;IACjD,IAAI,EAAE;QACJ,QAAQ,EAAE,wBAAwB;QAClC,OAAO,EAAE,yDAAyD;QAClE,QAAQ,EAAE,wBAAwB;QAClC,SAAS,EAAE,mDAAmD;KAC/D;IACD,GAAG,EAAE;QACH,QAAQ,EAAE,4BAA4B;QACtC,OAAO,EAAE,wDAAwD;QACjE,QAAQ,EAAE,4BAA4B;QACtC,SAAS,EAAE,kDAAkD;KAC9D;IACD,KAAK,EAAE;QACL,QAAQ,EAAE,8BAA8B;QACxC,OAAO,EAAE,0DAA0D;QACnE,QAAQ,EAAE,8BAA8B;QACxC,SAAS,EAAE,oDAAoD;KAChE;CACF,CAAA;AAED,MAAM,UAAU,OAAO,CAAC,GAAgB;IACtC,OAAO,YAAY,CAAC,GAAG,CAAC,CAAA;AAC1B,CAAC"}
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/lib/presence.ts"],"names":[],"mappings":"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"}
|