@formant/formant-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +322 -0
  3. package/bin/dev.js +5 -0
  4. package/bin/run.js +5 -0
  5. package/dist/base-command.d.ts +38 -0
  6. package/dist/base-command.js +66 -0
  7. package/dist/base-command.js.map +1 -0
  8. package/dist/commands/analytics/query.d.ts +12 -0
  9. package/dist/commands/analytics/query.js +82 -0
  10. package/dist/commands/analytics/query.js.map +1 -0
  11. package/dist/commands/analytics/tables.d.ts +9 -0
  12. package/dist/commands/analytics/tables.js +34 -0
  13. package/dist/commands/analytics/tables.js.map +1 -0
  14. package/dist/commands/commands/for-device.d.ts +12 -0
  15. package/dist/commands/commands/for-device.js +33 -0
  16. package/dist/commands/commands/for-device.js.map +1 -0
  17. package/dist/commands/commands/get.d.ts +11 -0
  18. package/dist/commands/commands/get.js +133 -0
  19. package/dist/commands/commands/get.js.map +1 -0
  20. package/dist/commands/commands/history.d.ts +13 -0
  21. package/dist/commands/commands/history.js +49 -0
  22. package/dist/commands/commands/history.js.map +1 -0
  23. package/dist/commands/commands/list.d.ts +9 -0
  24. package/dist/commands/commands/list.js +31 -0
  25. package/dist/commands/commands/list.js.map +1 -0
  26. package/dist/commands/commands/send.d.ts +37 -0
  27. package/dist/commands/commands/send.js +260 -0
  28. package/dist/commands/commands/send.js.map +1 -0
  29. package/dist/commands/devices/config.d.ts +10 -0
  30. package/dist/commands/devices/config.js +38 -0
  31. package/dist/commands/devices/config.js.map +1 -0
  32. package/dist/commands/devices/get.d.ts +10 -0
  33. package/dist/commands/devices/get.js +35 -0
  34. package/dist/commands/devices/get.js.map +1 -0
  35. package/dist/commands/devices/last-seen.d.ts +10 -0
  36. package/dist/commands/devices/last-seen.js +25 -0
  37. package/dist/commands/devices/last-seen.js.map +1 -0
  38. package/dist/commands/devices/list.d.ts +20 -0
  39. package/dist/commands/devices/list.js +133 -0
  40. package/dist/commands/devices/list.js.map +1 -0
  41. package/dist/commands/devices/streams.d.ts +12 -0
  42. package/dist/commands/devices/streams.js +56 -0
  43. package/dist/commands/devices/streams.js.map +1 -0
  44. package/dist/commands/event-triggers/get.d.ts +10 -0
  45. package/dist/commands/event-triggers/get.js +29 -0
  46. package/dist/commands/event-triggers/get.js.map +1 -0
  47. package/dist/commands/event-triggers/list.d.ts +9 -0
  48. package/dist/commands/event-triggers/list.js +30 -0
  49. package/dist/commands/event-triggers/list.js.map +1 -0
  50. package/dist/commands/events/get.d.ts +10 -0
  51. package/dist/commands/events/get.js +35 -0
  52. package/dist/commands/events/get.js.map +1 -0
  53. package/dist/commands/events/list.d.ts +17 -0
  54. package/dist/commands/events/list.js +81 -0
  55. package/dist/commands/events/list.js.map +1 -0
  56. package/dist/commands/fleets/get.d.ts +10 -0
  57. package/dist/commands/fleets/get.js +28 -0
  58. package/dist/commands/fleets/get.js.map +1 -0
  59. package/dist/commands/fleets/list.d.ts +9 -0
  60. package/dist/commands/fleets/list.js +27 -0
  61. package/dist/commands/fleets/list.js.map +1 -0
  62. package/dist/commands/investigations/analytics.d.ts +15 -0
  63. package/dist/commands/investigations/analytics.js +69 -0
  64. package/dist/commands/investigations/analytics.js.map +1 -0
  65. package/dist/commands/investigations/get.d.ts +10 -0
  66. package/dist/commands/investigations/get.js +38 -0
  67. package/dist/commands/investigations/get.js.map +1 -0
  68. package/dist/commands/investigations/list.d.ts +12 -0
  69. package/dist/commands/investigations/list.js +41 -0
  70. package/dist/commands/investigations/list.js.map +1 -0
  71. package/dist/commands/investigations/run.d.ts +15 -0
  72. package/dist/commands/investigations/run.js +96 -0
  73. package/dist/commands/investigations/run.js.map +1 -0
  74. package/dist/commands/investigations/runs-list.d.ts +18 -0
  75. package/dist/commands/investigations/runs-list.js +57 -0
  76. package/dist/commands/investigations/runs-list.js.map +1 -0
  77. package/dist/commands/investigations/runs.d.ts +12 -0
  78. package/dist/commands/investigations/runs.js +30 -0
  79. package/dist/commands/investigations/runs.js.map +1 -0
  80. package/dist/commands/investigations/stats.d.ts +11 -0
  81. package/dist/commands/investigations/stats.js +40 -0
  82. package/dist/commands/investigations/stats.js.map +1 -0
  83. package/dist/commands/investigations/trigger.d.ts +14 -0
  84. package/dist/commands/investigations/trigger.js +43 -0
  85. package/dist/commands/investigations/trigger.js.map +1 -0
  86. package/dist/commands/kv/get.d.ts +10 -0
  87. package/dist/commands/kv/get.js +28 -0
  88. package/dist/commands/kv/get.js.map +1 -0
  89. package/dist/commands/kv/list.d.ts +13 -0
  90. package/dist/commands/kv/list.js +73 -0
  91. package/dist/commands/kv/list.js.map +1 -0
  92. package/dist/commands/kv/set.d.ts +11 -0
  93. package/dist/commands/kv/set.js +40 -0
  94. package/dist/commands/kv/set.js.map +1 -0
  95. package/dist/commands/query/index.d.ts +19 -0
  96. package/dist/commands/query/index.js +105 -0
  97. package/dist/commands/query/index.js.map +1 -0
  98. package/dist/commands/query/latest-values.d.ts +14 -0
  99. package/dist/commands/query/latest-values.js +102 -0
  100. package/dist/commands/query/latest-values.js.map +1 -0
  101. package/dist/commands/schedules/get.d.ts +10 -0
  102. package/dist/commands/schedules/get.js +29 -0
  103. package/dist/commands/schedules/get.js.map +1 -0
  104. package/dist/commands/schedules/list.d.ts +9 -0
  105. package/dist/commands/schedules/list.js +29 -0
  106. package/dist/commands/schedules/list.js.map +1 -0
  107. package/dist/commands/signals/count.d.ts +13 -0
  108. package/dist/commands/signals/count.js +43 -0
  109. package/dist/commands/signals/count.js.map +1 -0
  110. package/dist/commands/signals/get.d.ts +13 -0
  111. package/dist/commands/signals/get.js +80 -0
  112. package/dist/commands/signals/get.js.map +1 -0
  113. package/dist/commands/signals/list.d.ts +9 -0
  114. package/dist/commands/signals/list.js +30 -0
  115. package/dist/commands/signals/list.js.map +1 -0
  116. package/dist/commands/signals/query.d.ts +14 -0
  117. package/dist/commands/signals/query.js +52 -0
  118. package/dist/commands/signals/query.js.map +1 -0
  119. package/dist/commands/users/get.d.ts +10 -0
  120. package/dist/commands/users/get.js +28 -0
  121. package/dist/commands/users/get.js.map +1 -0
  122. package/dist/commands/users/list.d.ts +9 -0
  123. package/dist/commands/users/list.js +28 -0
  124. package/dist/commands/users/list.js.map +1 -0
  125. package/dist/help.d.ts +4 -0
  126. package/dist/help.js +91 -0
  127. package/dist/help.js.map +1 -0
  128. package/dist/hooks/init.d.ts +3 -0
  129. package/dist/hooks/init.js +7 -0
  130. package/dist/hooks/init.js.map +1 -0
  131. package/dist/index.d.ts +1 -0
  132. package/dist/index.js +2 -0
  133. package/dist/index.js.map +1 -0
  134. package/dist/lib/api.d.ts +12 -0
  135. package/dist/lib/api.js +46 -0
  136. package/dist/lib/api.js.map +1 -0
  137. package/dist/lib/auth.d.ts +17 -0
  138. package/dist/lib/auth.js +41 -0
  139. package/dist/lib/auth.js.map +1 -0
  140. package/dist/lib/config.d.ts +7 -0
  141. package/dist/lib/config.js +21 -0
  142. package/dist/lib/config.js.map +1 -0
  143. package/dist/lib/formatters.d.ts +17 -0
  144. package/dist/lib/formatters.js +53 -0
  145. package/dist/lib/formatters.js.map +1 -0
  146. package/oclif.manifest.json +2683 -0
  147. package/package.json +117 -0
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ PROPRIETARY LICENSE
2
+
3
+ Copyright (c) 2024-2026 Formant Inc.
4
+
5
+ All rights reserved.
6
+
7
+ This software and associated documentation files (the "Software") are proprietary
8
+ to Formant Inc. and are provided solely for use by authorized Formant customers.
9
+
10
+ The Software may not be:
11
+ - Copied, modified, merged, published, or distributed without express written
12
+ permission from Formant Inc.
13
+ - Used for any purpose other than in connection with authorized use of Formant
14
+ services
15
+ - Reverse engineered, decompiled, or disassembled
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20
+ FORMANT INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For licensing inquiries, contact: info@formant.io
package/README.md ADDED
@@ -0,0 +1,322 @@
1
+ # Formant CLI (`fcli`)
2
+
3
+ ```
4
+ ███████╗ ██████╗██╗ ██╗
5
+ ██╔════╝██╔════╝██║ ██║ 🤖 Formant CLI
6
+ █████╗ ██║ ██║ ██║ ⚡ Manage your robot fleet
7
+ ██╔══╝ ██║ ██║ ██║ 🚀 from the command line
8
+ ██║ ╚██████╗███████╗██║
9
+ ╚═╝ ╚═════╝╚══════╝╚═╝
10
+ ```
11
+
12
+ The official command-line interface for [Formant](https://formant.io) — manage your robot fleet, query telemetry, trigger investigations, and more, all from your terminal.
13
+
14
+ ## Features
15
+
16
+ - 📋 **Device Management** — List, inspect, and configure robots and sensors
17
+ - 📊 **Telemetry Queries** — Retrieve battery, temperature, and sensor data
18
+ - 🔍 **Event Monitoring** — View and filter device events by severity
19
+ - 🤖 **AI Investigations** — Trigger and monitor AI-powered analysis workflows
20
+ - 📡 **Signal Management** — Track and trace investigation signals
21
+ - 💬 **Command Execution** — Send commands to devices
22
+ - 🚨 **Event Triggers** — Configure rules that generate events and signals
23
+ - 👥 **User & Fleet Management** — Manage users, teams, and device groups
24
+ - 📅 **Scheduled Tasks** — Set up recurring jobs and one-time schedules
25
+ - 📈 **Analytics** — Execute SQL queries against analytics data
26
+ - 🗂️ **Key-Value Store** — Manage device metadata
27
+
28
+ ## Installation
29
+
30
+ ### npm
31
+
32
+ ```bash
33
+ npm install -g @formant/formant-cli
34
+ ```
35
+
36
+ ### Yarn
37
+
38
+ ```bash
39
+ yarn global add @formant/formant-cli
40
+ ```
41
+
42
+ ### pnpm
43
+
44
+ ```bash
45
+ pnpm add -g @formant/formant-cli
46
+ ```
47
+
48
+ ## Authentication
49
+
50
+ Set your Formant credentials as environment variables:
51
+
52
+ ```bash
53
+ export FORMANT_USER="your-service-account@example.com"
54
+ export FORMANT_PASSWORD="your-password"
55
+ ```
56
+
57
+ Or create a `.env` file in your project:
58
+
59
+ ```env
60
+ FORMANT_USER=your-service-account@example.com
61
+ FORMANT_PASSWORD=your-password
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ ```bash
67
+ # View help
68
+ fcli --help
69
+
70
+ # List all devices in dev environment
71
+ fcli devices list --dev
72
+
73
+ # Get device details
74
+ fcli devices get <device-id> --dev
75
+
76
+ # Query battery telemetry
77
+ fcli query --device <device-id> --stream battery_level --start 2026-01-01 --end 2026-01-02 --dev
78
+
79
+ # View recent events
80
+ fcli events list --device <device-id> --severity critical --limit 20 --dev
81
+
82
+ # List investigations
83
+ fcli investigations list --dev
84
+
85
+ # Send a command to a device
86
+ fcli commands send <device-id> <template-id> --param speed=5 --dev
87
+ ```
88
+
89
+ ## Commands
90
+
91
+ ### Devices
92
+
93
+ Manage robots and sensors in your fleet.
94
+
95
+ ```bash
96
+ fcli devices list [--tag <key>=<value>] [--online] [--dev]
97
+ fcli devices get <device-id> [--dev]
98
+ fcli devices config <device-id> [--dev]
99
+ fcli devices streams <device-id> [--dev]
100
+ fcli devices last-seen <device-id> [--dev]
101
+ ```
102
+
103
+ ### Events
104
+
105
+ View important events emitted by devices.
106
+
107
+ ```bash
108
+ fcli events list [--device <id>] [--severity <level>] [--limit <n>] [--dev]
109
+ fcli events get <event-id> [--dev]
110
+ ```
111
+
112
+ ### Query
113
+
114
+ Retrieve telemetry and sensor data from devices.
115
+
116
+ ```bash
117
+ fcli query --device <id> --stream <name> --start <date> --end <date> [--dev]
118
+ fcli query latest-values --device <id> --stream <name> [--dev]
119
+ ```
120
+
121
+ ### Investigations
122
+
123
+ Run and monitor AI-powered analysis workflows.
124
+
125
+ ```bash
126
+ fcli investigations list [--dev]
127
+ fcli investigations get <investigation-id> [--dev]
128
+ fcli investigations stats [--start <date>] [--end <date>] [--dev]
129
+ fcli investigations runs-list <investigation-id> [--dev]
130
+ fcli investigations run <investigation-id> <run-id> [--trace-signal] [--dev]
131
+ fcli investigations trigger <investigation-id> <device-id> [--dev]
132
+ fcli investigations analytics <investigation-id> [--dev]
133
+ ```
134
+
135
+ ### Signals
136
+
137
+ Manage signals (points of interest that trigger analysis).
138
+
139
+ ```bash
140
+ fcli signals list [--start <date>] [--end <date>] [--dev]
141
+ ```
142
+
143
+ ### Commands
144
+
145
+ Manage command templates and send commands to devices.
146
+
147
+ ```bash
148
+ fcli commands list [--dev]
149
+ fcli commands get <command-id> [--dev]
150
+ fcli commands for-device <device-id> [--dev]
151
+ fcli commands send <device-id> <template-id> [--param <key>=<value>] [--dev]
152
+ fcli commands history <device-id> [--dev]
153
+ ```
154
+
155
+ ### Event Triggers
156
+
157
+ Manage event trigger rules that generate events and signals.
158
+
159
+ ```bash
160
+ fcli event-triggers list [--dev]
161
+ fcli event-triggers get <trigger-id> [--dev]
162
+ ```
163
+
164
+ ### Users
165
+
166
+ Manage users in your organization.
167
+
168
+ ```bash
169
+ fcli users list [--dev]
170
+ fcli users get <user-id> [--dev]
171
+ ```
172
+
173
+ ### Fleets
174
+
175
+ Manage device groups (fleets).
176
+
177
+ ```bash
178
+ fcli fleets list [--dev]
179
+ fcli fleets get <fleet-id> [--dev]
180
+ ```
181
+
182
+ ### Schedules
183
+
184
+ Manage scheduled tasks and recurring jobs.
185
+
186
+ ```bash
187
+ fcli schedules list [--dev]
188
+ fcli schedules get <schedule-id> [--dev]
189
+ ```
190
+
191
+ ### Analytics
192
+
193
+ Execute SQL queries against analytics data.
194
+
195
+ ```bash
196
+ fcli analytics query --sql "<query>" [--dev]
197
+ fcli analytics tables [--dev]
198
+ ```
199
+
200
+ ### Key-Value Store
201
+
202
+ Manage key-value store for device metadata.
203
+
204
+ ```bash
205
+ fcli kv list <device-id> [--dev]
206
+ fcli kv get <device-id> <key> [--dev]
207
+ fcli kv set <device-id> <key> <value> [--dev]
208
+ ```
209
+
210
+ ## Global Flags
211
+
212
+ - `--dev` — Target the dev environment
213
+ - `--stage` — Target the stage environment
214
+ - `--json` — Format output as JSON
215
+ - `-h, --help` — Show help for a command
216
+
217
+ ## Environment Variables
218
+
219
+ | Variable | Description | Required |
220
+ |----------|-------------|----------|
221
+ | `FORMANT_USER` | Service account email | Yes |
222
+ | `FORMANT_PASSWORD` | Service account password | Yes |
223
+
224
+ ## Examples
225
+
226
+ ### List online devices with specific tag
227
+
228
+ ```bash
229
+ fcli devices list --tag location=warehouse --online --dev
230
+ ```
231
+
232
+ ### Query telemetry data over a time range
233
+
234
+ ```bash
235
+ fcli query --device abc123 --stream battery_level \
236
+ --start 2026-01-01 --end 2026-01-02 --dev --json
237
+ ```
238
+
239
+ ### Trace an investigation back to its signal
240
+
241
+ ```bash
242
+ # Get investigation run
243
+ fcli investigations run <investigation-id> <run-id> --trace-signal --dev
244
+
245
+ # Get the signal details
246
+ fcli signals get <signal-id> --dev
247
+ ```
248
+
249
+ ### Monitor critical events
250
+
251
+ ```bash
252
+ fcli events list --severity critical --limit 50 --dev
253
+ ```
254
+
255
+ ### Send a command with parameters
256
+
257
+ ```bash
258
+ fcli commands send device-123 template-456 \
259
+ --param speed=10 \
260
+ --param direction=forward \
261
+ --dev
262
+ ```
263
+
264
+ ## Output Formats
265
+
266
+ ### Table (default)
267
+
268
+ Human-readable tables for terminal viewing:
269
+
270
+ ```bash
271
+ fcli devices list --dev
272
+ ```
273
+
274
+ ### JSON
275
+
276
+ Machine-readable JSON for scripting and automation:
277
+
278
+ ```bash
279
+ fcli devices list --dev --json | jq '.items[] | .name'
280
+ ```
281
+
282
+ ## Development
283
+
284
+ ### Build from source
285
+
286
+ ```bash
287
+ git clone https://github.com/FormantIO/formant-cli.git
288
+ cd formant-cli
289
+ npm install
290
+ npm run build
291
+ ```
292
+
293
+ ### Run in development mode
294
+
295
+ ```bash
296
+ npm run dev -- devices list --dev
297
+ ```
298
+
299
+ ### Build and pack
300
+
301
+ ```bash
302
+ npm run prepack
303
+ ```
304
+
305
+ ## Requirements
306
+
307
+ - Node.js >= 18.0.0
308
+ - A Formant service account with appropriate permissions
309
+
310
+ ## Support
311
+
312
+ - Documentation: [https://formant.io/docs](https://formant.io/docs)
313
+ - Support: [support@formant.io](mailto:support@formant.io)
314
+ - Issues: [https://github.com/FormantIO/formant-cli/issues](https://github.com/FormantIO/formant-cli/issues)
315
+
316
+ ## License
317
+
318
+ UNLICENSED — Proprietary software for Formant customers.
319
+
320
+ ---
321
+
322
+ Made with ⚡ by [Formant](https://formant.io)
package/bin/dev.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({development: true, dir: import.meta.url})
package/bin/run.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({dir: import.meta.url})
@@ -0,0 +1,38 @@
1
+ import { Command, Interfaces } from '@oclif/core';
2
+ import { type ApiTarget } from './lib/api.js';
3
+ import type { Environment } from './lib/config.js';
4
+ export type InferredFlags<T extends typeof Command> = Interfaces.InferredFlags<typeof BaseCommand['baseFlags'] & T['flags']>;
5
+ export type InferredArgs<T extends typeof Command> = Interfaces.InferredArgs<T['args']>;
6
+ /**
7
+ * Abstract base command for all fcli commands.
8
+ * Provides global --env flag, --json support, and authenticated API helpers.
9
+ */
10
+ export declare abstract class BaseCommand<T extends typeof Command> extends Command {
11
+ static enableJsonFlag: boolean;
12
+ static baseFlags: {
13
+ dev: Interfaces.BooleanFlag<boolean>;
14
+ stage: Interfaces.BooleanFlag<boolean>;
15
+ };
16
+ protected flags: InferredFlags<T>;
17
+ protected args: InferredArgs<T>;
18
+ /** Resolved environment based on --dev / --stage flags. */
19
+ protected get env(): Environment;
20
+ init(): Promise<void>;
21
+ /**
22
+ * Make an authenticated GET/POST request to a Formant API.
23
+ * Automatically uses the correct environment from --dev/--stage flags.
24
+ */
25
+ protected api<R = unknown>(target: ApiTarget, path: string, options?: {
26
+ body?: unknown;
27
+ method?: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
28
+ query?: Record<string, string>;
29
+ }): Promise<R>;
30
+ protected catch(err: Error & {
31
+ exitCode?: number;
32
+ }): Promise<void>;
33
+ /**
34
+ * Normalize a date string to ISO 8601 datetime format.
35
+ * Accepts both simple dates (2026-01-01) and full datetime strings.
36
+ */
37
+ protected normalizeDateTime(dateStr: string): string;
38
+ }
@@ -0,0 +1,66 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { apiRequest } from './lib/api.js';
3
+ /**
4
+ * Abstract base command for all fcli commands.
5
+ * Provides global --env flag, --json support, and authenticated API helpers.
6
+ */
7
+ export class BaseCommand extends Command {
8
+ static enableJsonFlag = true;
9
+ static baseFlags = {
10
+ dev: Flags.boolean({
11
+ description: 'Target the dev environment',
12
+ exclusive: ['stage'],
13
+ helpGroup: 'GLOBAL',
14
+ }),
15
+ stage: Flags.boolean({
16
+ description: 'Target the stage environment',
17
+ exclusive: ['dev'],
18
+ helpGroup: 'GLOBAL',
19
+ }),
20
+ };
21
+ flags;
22
+ args;
23
+ /** Resolved environment based on --dev / --stage flags. */
24
+ get env() {
25
+ if (this.flags.dev)
26
+ return 'dev';
27
+ if (this.flags.stage)
28
+ return 'stage';
29
+ return 'prod';
30
+ }
31
+ async init() {
32
+ await super.init();
33
+ const { args, flags } = await this.parse({
34
+ args: this.ctor.args,
35
+ baseFlags: super.ctor.baseFlags,
36
+ enableJsonFlag: this.ctor.enableJsonFlag,
37
+ flags: this.ctor.flags,
38
+ strict: this.ctor.strict,
39
+ });
40
+ this.flags = flags;
41
+ this.args = args;
42
+ }
43
+ /**
44
+ * Make an authenticated GET/POST request to a Formant API.
45
+ * Automatically uses the correct environment from --dev/--stage flags.
46
+ */
47
+ async api(target, path, options) {
48
+ return apiRequest(this.env, target, path, options);
49
+ }
50
+ async catch(err) {
51
+ throw err;
52
+ }
53
+ /**
54
+ * Normalize a date string to ISO 8601 datetime format.
55
+ * Accepts both simple dates (2026-01-01) and full datetime strings.
56
+ */
57
+ normalizeDateTime(dateStr) {
58
+ // If it already looks like a full datetime (has 'T' or time), return as-is
59
+ if (dateStr.includes('T') || dateStr.match(/\d{2}:\d{2}/)) {
60
+ return dateStr;
61
+ }
62
+ // Otherwise, append T00:00:00Z to make it a valid datetime
63
+ return `${dateStr}T00:00:00Z`;
64
+ }
65
+ }
66
+ //# sourceMappingURL=base-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-command.js","sourceRoot":"","sources":["../src/base-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAE,KAAK,EAAa,MAAM,aAAa,CAAA;AAEtD,OAAO,EAAiB,UAAU,EAAC,MAAM,cAAc,CAAA;AAQvD;;;GAGG;AACH,MAAM,OAAgB,WAAsC,SAAQ,OAAO;IACzE,MAAM,CAAC,cAAc,GAAG,IAAI,CAAA;IAE5B,MAAM,CAAC,SAAS,GAAG;QACjB,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC;YACjB,WAAW,EAAE,4BAA4B;YACzC,SAAS,EAAE,CAAC,OAAO,CAAC;YACpB,SAAS,EAAE,QAAQ;SACpB,CAAC;QACF,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;YACnB,WAAW,EAAE,8BAA8B;YAC3C,SAAS,EAAE,CAAC,KAAK,CAAC;YAClB,SAAS,EAAE,QAAQ;SACpB,CAAC;KACH,CAAA;IAES,KAAK,CAAmB;IACxB,IAAI,CAAkB;IAEhC,2DAA2D;IAC3D,IAAc,GAAG;QACf,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG;YAAE,OAAO,KAAK,CAAA;QAChC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO,OAAO,CAAA;QACpC,OAAO,MAAM,CAAA;IACf,CAAC;IAEM,KAAK,CAAC,IAAI;QACf,MAAM,KAAK,CAAC,IAAI,EAAE,CAAA;QAClB,MAAM,EAAC,IAAI,EAAE,KAAK,EAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC;YACrC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;YACpB,SAAS,EAAG,KAAK,CAAC,IAA2B,CAAC,SAAS;YACvD,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,cAAc;YACxC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK;YACtB,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;SACzB,CAAC,CAAA;QAEF,IAAI,CAAC,KAAK,GAAG,KAAyB,CAAA;QACtC,IAAI,CAAC,IAAI,GAAG,IAAuB,CAAA;IACrC,CAAC;IAED;;;OAGG;IACO,KAAK,CAAC,GAAG,CACjB,MAAiB,EACjB,IAAY,EACZ,OAAgH;QAEhH,OAAO,UAAU,CAAI,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IACvD,CAAC;IAEkB,KAAK,CAAC,KAAK,CAAC,GAAgC;QAC7D,MAAM,GAAG,CAAA;IACX,CAAC;IAED;;;OAGG;IACO,iBAAiB,CAAC,OAAe;QACzC,2EAA2E;QAC3E,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;YAC1D,OAAO,OAAO,CAAA;QAChB,CAAC;QAED,2DAA2D;QAC3D,OAAO,GAAG,OAAO,YAAY,CAAA;IAC/B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class AnalyticsQuery extends BaseCommand<typeof AnalyticsQuery> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ sql: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ static summary: string;
11
+ run(): Promise<Record<string, unknown>>;
12
+ }
@@ -0,0 +1,82 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ import { formatTable } from '../../lib/formatters.js';
4
+ export default class AnalyticsQuery extends BaseCommand {
5
+ static description = `Execute a SQL query against analytics data (Snowflake SQL dialect).
6
+
7
+ Query telemetry data, events, and other analytics tables using Snowflake-compatible SQL.
8
+ Use 'fcli analytics tables' to see available tables and their columns.
9
+
10
+ Note: The analytics engine uses the Snowflake SQL dialect. String comparisons are
11
+ case-sensitive, use ILIKE for case-insensitive matching, and timestamps should be
12
+ quoted strings (e.g. WHERE time > '2026-01-01'). Large unbounded queries may be
13
+ rejected — always filter by time range for large tables.`;
14
+ static examples = [
15
+ '<%= config.bin %> analytics query --sql "SELECT * FROM query_event WHERE time > \'2026-01-01\' LIMIT 10"',
16
+ '<%= config.bin %> analytics query --sql "SELECT device_id, avg(value) as avg_val FROM query_numeric WHERE time > \'2026-02-01\' GROUP BY device_id LIMIT 10"',
17
+ '<%= config.bin %> analytics query --sql "SELECT type, count(*) as cnt FROM query_event WHERE time > \'2026-02-01\' GROUP BY type ORDER BY cnt DESC LIMIT 5"',
18
+ '<%= config.bin %> analytics query --sql "SELECT * FROM query_event LIMIT 10" --json',
19
+ ];
20
+ static flags = {
21
+ limit: Flags.integer({
22
+ char: 'l',
23
+ description: 'Maximum number of rows to return',
24
+ }),
25
+ sql: Flags.string({
26
+ char: 's',
27
+ description: 'SQL query to execute',
28
+ required: true,
29
+ }),
30
+ type: Flags.string({
31
+ default: 'advanced',
32
+ description: 'Query type',
33
+ options: ['stream', 'task', 'advanced'],
34
+ }),
35
+ };
36
+ static summary = 'Execute SQL analytics query';
37
+ async run() {
38
+ const body = {
39
+ sqlQuery: this.flags.sql,
40
+ type: this.flags.type,
41
+ };
42
+ if (this.flags.limit) {
43
+ body.limit = this.flags.limit;
44
+ }
45
+ const result = await this.api('query', 'analytics/rows', { body });
46
+ if (result.error) {
47
+ this.error(`Query error: ${result.error}`);
48
+ }
49
+ if (!this.jsonEnabled()) {
50
+ this.log(`\nAnalytics Query Results (${this.env}):\n`);
51
+ this.log(` SQL: ${result.sqlText || this.flags.sql}`);
52
+ this.log(` Rows: ${result.rowCount ?? result.rows?.length ?? 0}\n`);
53
+ const rows = result.rows ?? [];
54
+ if (rows.length > 0) {
55
+ // Build columns from the response metadata
56
+ const columns = (result.columns ?? []).map((col) => ({
57
+ key: col.name,
58
+ label: col.name.toUpperCase(),
59
+ width: Math.max(col.name.length + 2, 20),
60
+ }));
61
+ // Fallback: derive from first row if no column metadata
62
+ if (columns.length === 0) {
63
+ const keys = Object.keys(rows[0]);
64
+ for (const key of keys) {
65
+ columns.push({
66
+ key,
67
+ label: key.toUpperCase(),
68
+ width: Math.max(key.length + 2, 20),
69
+ });
70
+ }
71
+ }
72
+ this.log(formatTable(rows, columns));
73
+ }
74
+ else {
75
+ this.log(' No results.');
76
+ }
77
+ this.log('');
78
+ }
79
+ return result;
80
+ }
81
+ }
82
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../../src/commands/analytics/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class AnalyticsTables extends BaseCommand<typeof AnalyticsTables> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static summary: string;
6
+ run(): Promise<{
7
+ items: unknown[];
8
+ }>;
9
+ }
@@ -0,0 +1,34 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ import { formatTable } from '../../lib/formatters.js';
3
+ export default class AnalyticsTables extends BaseCommand {
4
+ static description = `List available SQL tables for analytics queries.
5
+
6
+ Shows table names, descriptions, and column definitions that can be used
7
+ with 'fcli analytics query'. The analytics engine uses the Snowflake SQL dialect.`;
8
+ static examples = [
9
+ '<%= config.bin %> analytics tables',
10
+ '<%= config.bin %> analytics tables --json',
11
+ ];
12
+ static summary = 'List analytics tables';
13
+ async run() {
14
+ const result = await this.api('query', 'analytics/tables', { method: 'GET' });
15
+ const tables = result.items ?? [];
16
+ if (!this.jsonEnabled()) {
17
+ this.log(`\nAnalytics Tables (${this.env}):\n`);
18
+ for (const table of tables) {
19
+ this.log(` ${table.name}${table.description ? ` — ${table.description}` : ''}`);
20
+ if (table.columns && table.columns.length > 0) {
21
+ const columns = [
22
+ { key: 'name', label: 'COLUMN', width: 30 },
23
+ { key: 'dataType', label: 'TYPE', width: 14 },
24
+ { key: 'description', label: 'DESCRIPTION', width: 40 },
25
+ ];
26
+ this.log(formatTable(table.columns, columns));
27
+ }
28
+ this.log('');
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ }
34
+ //# sourceMappingURL=tables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.js","sourceRoot":"","sources":["../../../src/commands/analytics/tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAc,WAAW,EAAC,MAAM,yBAAyB,CAAA;AAEhE,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,WAAmC;IAC9E,MAAM,CAAU,WAAW,GAAG;;;kFAGkD,CAAA;IAEhF,MAAM,CAAU,QAAQ,GAAG;QACzB,oCAAoC;QACpC,2CAA2C;KAC5C,CAAA;IAED,MAAM,CAAU,OAAO,GAAG,uBAAuB,CAAA;IAE1C,KAAK,CAAC,GAAG;QACd,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAC3B,OAAO,EACP,kBAAkB,EAClB,EAAC,MAAM,EAAE,KAAK,EAAC,CAChB,CAAA;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAA;QAEjC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,GAAG,MAAM,CAAC,CAAA;YAE/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;gBAEhF,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9C,MAAM,OAAO,GAAa;wBACxB,EAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAC;wBACzC,EAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAC;wBAC3C,EAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAC;qBACtD,CAAA;oBAED,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;gBAC/C,CAAC;gBAED,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACd,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class CommandsForDevice extends BaseCommand<typeof CommandsForDevice> {
3
+ static args: {
4
+ deviceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static summary: string;
9
+ run(): Promise<{
10
+ items: unknown[];
11
+ }>;
12
+ }
@@ -0,0 +1,33 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ import { formatTable } from '../../lib/formatters.js';
4
+ export default class CommandsForDevice extends BaseCommand {
5
+ static args = {
6
+ deviceId: Args.string({ description: 'Device ID (UUID)', required: true }),
7
+ };
8
+ static description = `List command templates available for a specific device.
9
+
10
+ Returns commands that match the device's tags and filters. Only enabled commands
11
+ that apply to this device are shown.`;
12
+ static examples = [
13
+ '<%= config.bin %> commands for-device <device-id>',
14
+ '<%= config.bin %> commands for-device <device-id> --json',
15
+ ];
16
+ static summary = 'List commands available for a device';
17
+ async run() {
18
+ const result = await this.api('admin', `devices/${this.args.deviceId}/commands`, { method: 'GET' });
19
+ if (!this.jsonEnabled()) {
20
+ const columns = [
21
+ { key: 'name', label: 'NAME', width: 30 },
22
+ { key: 'command', label: 'COMMAND', width: 30 },
23
+ { key: 'id', label: 'ID', width: 40 },
24
+ { key: 'description', label: 'DESCRIPTION', width: 30 },
25
+ ];
26
+ this.log(`\nAvailable Commands for Device (${this.env}):\n`);
27
+ this.log(formatTable(result.items ?? [], columns));
28
+ this.log('');
29
+ }
30
+ return result;
31
+ }
32
+ }
33
+ //# sourceMappingURL=for-device.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"for-device.js","sourceRoot":"","sources":["../../../src/commands/commands/for-device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,aAAa,CAAA;AAEhC,OAAO,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAc,WAAW,EAAC,MAAM,yBAAyB,CAAA;AAEhE,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,WAAqC;IAClF,MAAM,CAAU,IAAI,GAAG;QACrB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAC,WAAW,EAAE,kBAAkB,EAAE,QAAQ,EAAE,IAAI,EAAC,CAAC;KACzE,CAAA;IAED,MAAM,CAAU,WAAW,GAAG;;;qCAGK,CAAA;IAEnC,MAAM,CAAU,QAAQ,GAAG;QACzB,mDAAmD;QACnD,0DAA0D;KAC3D,CAAA;IAED,MAAM,CAAU,OAAO,GAAG,sCAAsC,CAAA;IAEzD,KAAK,CAAC,GAAG;QACd,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAC3B,OAAO,EACP,WAAW,IAAI,CAAC,IAAI,CAAC,QAAQ,WAAW,EACxC,EAAC,MAAM,EAAE,KAAK,EAAC,CAChB,CAAA;QAED,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,OAAO,GAAa;gBACxB,EAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAC;gBACvC,EAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAC;gBAC7C,EAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAC;gBACnC,EAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAC;aACtD,CAAA;YAED,IAAI,CAAC,GAAG,CAAC,oCAAoC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAA;YAC5D,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAA;YAClD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACd,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC"}