miravo 0.6.0 → 0.6.2

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 (187) hide show
  1. package/LICENSE +199 -0
  2. package/README.md +29 -59
  3. package/bin/miravo.js +84 -0
  4. package/package.json +21 -37
  5. package/console-dist/assets/activity-DsWeRq4h.js +0 -1
  6. package/console-dist/assets/api-4AqKvsm9.js +0 -1
  7. package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
  8. package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
  9. package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
  10. package/console-dist/assets/box-C1WaoOMF.js +0 -1
  11. package/console-dist/assets/card-BjeP32PN.js +0 -1
  12. package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
  13. package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
  14. package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
  15. package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
  16. package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
  17. package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
  18. package/console-dist/assets/control-CPhvwx9o.js +0 -1
  19. package/console-dist/assets/control-Cg2up9NA.js +0 -1
  20. package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
  21. package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
  22. package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
  23. package/console-dist/assets/dist-BhbDML0a.js +0 -1
  24. package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
  25. package/console-dist/assets/download-DQ6NH16U.js +0 -1
  26. package/console-dist/assets/engine-store-CrLnz692.js +0 -1
  27. package/console-dist/assets/events-CXm29Ok4.js +0 -1
  28. package/console-dist/assets/events-D01IxhnP.js +0 -1
  29. package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
  30. package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
  31. package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
  32. package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
  33. package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
  34. package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
  35. package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
  36. package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
  37. package/console-dist/assets/fleet-lotMGVqe.js +0 -1
  38. package/console-dist/assets/format-kXTonpDJ.js +0 -1
  39. package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
  40. package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
  41. package/console-dist/assets/index-CVPKSDvJ.js +0 -12
  42. package/console-dist/assets/index-DJkgDeun.css +0 -2
  43. package/console-dist/assets/input-BWEQFptx.js +0 -1
  44. package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
  45. package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
  46. package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
  47. package/console-dist/assets/label-uephX5ZG.js +0 -1
  48. package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
  49. package/console-dist/assets/login-C3RJli0m.js +0 -126
  50. package/console-dist/assets/models-B_UioWQ5.js +0 -1
  51. package/console-dist/assets/models-_IPlPiZ7.js +0 -1
  52. package/console-dist/assets/not-found-B8gAm08S.js +0 -1
  53. package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
  54. package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
  55. package/console-dist/assets/platforms-DStNbtVc.js +0 -1
  56. package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
  57. package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
  58. package/console-dist/assets/radio-CoHegOv5.js +0 -1
  59. package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
  60. package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  61. package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  62. package/console-dist/assets/select-CeO4vP0G.js +0 -1
  63. package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
  64. package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
  65. package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
  66. package/console-dist/assets/tags-CiXOIHtY.js +0 -4
  67. package/console-dist/assets/tags-FQCXIxrP.js +0 -1
  68. package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
  69. package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
  70. package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
  71. package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
  72. package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
  73. package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
  74. package/console-dist/assets/validation-Blvud9_O.js +0 -1
  75. package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
  76. package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
  77. package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
  78. package/console-dist/favicon.svg +0 -37
  79. package/console-dist/index.html +0 -82
  80. package/console-dist/miravo-logo.png +0 -0
  81. package/src/__tests__/integration/executable-methods-runner.ts +0 -262
  82. package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
  83. package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
  84. package/src/commands/adapter.ts +0 -249
  85. package/src/commands/apply.test.ts +0 -62
  86. package/src/commands/apply.ts +0 -216
  87. package/src/commands/cleanup.test.ts +0 -34
  88. package/src/commands/cleanup.ts +0 -201
  89. package/src/commands/cli-test-setup.ts +0 -91
  90. package/src/commands/export.test.ts +0 -51
  91. package/src/commands/export.ts +0 -404
  92. package/src/commands/fork.ts +0 -219
  93. package/src/commands/inject.test.ts +0 -57
  94. package/src/commands/inject.ts +0 -212
  95. package/src/commands/load.ts +0 -167
  96. package/src/commands/logs.ts +0 -173
  97. package/src/commands/models.test.ts +0 -162
  98. package/src/commands/models.ts +0 -338
  99. package/src/commands/pause.ts +0 -67
  100. package/src/commands/platforms.test.ts +0 -186
  101. package/src/commands/platforms.ts +0 -371
  102. package/src/commands/ps.ts +0 -116
  103. package/src/commands/remove.ts +0 -38
  104. package/src/commands/reset.ts +0 -44
  105. package/src/commands/rm.ts +0 -97
  106. package/src/commands/spawn.ts +0 -45
  107. package/src/commands/speed.ts +0 -40
  108. package/src/commands/start/adapter-config.ts +0 -157
  109. package/src/commands/start/catalog-le-dt.test.ts +0 -50
  110. package/src/commands/start/catalog-le-dt.ts +0 -187
  111. package/src/commands/start/catalog-model-schema.ts +0 -89
  112. package/src/commands/start/catalog-template-detail.ts +0 -54
  113. package/src/commands/start/daemon.ts +0 -255
  114. package/src/commands/start/display.ts +0 -150
  115. package/src/commands/start/engine-setup.ts +0 -247
  116. package/src/commands/start/events.ts +0 -126
  117. package/src/commands/start/index.ts +0 -897
  118. package/src/commands/start/protocol-plans.ts +0 -239
  119. package/src/commands/start/template-loader.ts +0 -197
  120. package/src/commands/start/template-resolver.ts +0 -185
  121. package/src/commands/start/types.ts +0 -73
  122. package/src/commands/start.test.ts +0 -480
  123. package/src/commands/status.test.ts +0 -20
  124. package/src/commands/status.ts +0 -470
  125. package/src/commands/stop.test.ts +0 -84
  126. package/src/commands/stop.ts +0 -169
  127. package/src/commands/templates.test.ts +0 -58
  128. package/src/commands/templates.ts +0 -277
  129. package/src/index.ts +0 -166
  130. package/src/lib/admin-auth.ts +0 -157
  131. package/src/lib/admin-client.test.ts +0 -51
  132. package/src/lib/admin-client.ts +0 -326
  133. package/src/lib/admin-platform-routes.ts +0 -1017
  134. package/src/lib/admin-platform-stream.test.ts +0 -434
  135. package/src/lib/admin-platform-stream.ts +0 -788
  136. package/src/lib/admin-responses.ts +0 -211
  137. package/src/lib/admin-server.test.ts +0 -781
  138. package/src/lib/admin-server.ts +0 -797
  139. package/src/lib/auth-store.ts +0 -87
  140. package/src/lib/daemon-registry.test.ts +0 -429
  141. package/src/lib/daemon-registry.ts +0 -343
  142. package/src/lib/le/le-apply.test.ts +0 -591
  143. package/src/lib/le/le-apply.ts +0 -832
  144. package/src/lib/le/le-auth.ts +0 -89
  145. package/src/lib/le/le-cleanup.test.ts +0 -168
  146. package/src/lib/le/le-cleanup.ts +0 -399
  147. package/src/lib/le/le-cli-helpers.ts +0 -77
  148. package/src/lib/le/le-client.ts +0 -184
  149. package/src/lib/le/le-concurrency.test.ts +0 -157
  150. package/src/lib/le/le-concurrency.ts +0 -78
  151. package/src/lib/le/le-connection-test.ts +0 -42
  152. package/src/lib/le/le-devicehub-properties.test.ts +0 -53
  153. package/src/lib/le/le-devicehub-properties.ts +0 -33
  154. package/src/lib/le/le-devicehub.ts +0 -213
  155. package/src/lib/le/le-digital-twins.ts +0 -285
  156. package/src/lib/le/le-drivers.ts +0 -23
  157. package/src/lib/le/le-endpoint.ts +0 -40
  158. package/src/lib/le/le-environments.test.ts +0 -73
  159. package/src/lib/le/le-environments.ts +0 -156
  160. package/src/lib/le/le-markers.ts +0 -87
  161. package/src/lib/le/le-plan.test.ts +0 -26
  162. package/src/lib/le/le-plan.ts +0 -109
  163. package/src/lib/le/le-platform-operations.test.ts +0 -198
  164. package/src/lib/le/le-platform-operations.ts +0 -340
  165. package/src/lib/le/le-static-model-schema.ts +0 -78
  166. package/src/lib/le/le-static-planner.test.ts +0 -255
  167. package/src/lib/le/le-static-planner.ts +0 -442
  168. package/src/lib/le/le-template-exporter.test.ts +0 -527
  169. package/src/lib/le/le-template-exporter.ts +0 -459
  170. package/src/lib/le/le-value-types.ts +0 -23
  171. package/src/lib/mqtt-client.ts +0 -115
  172. package/src/lib/parse.test.ts +0 -123
  173. package/src/lib/parse.ts +0 -87
  174. package/src/lib/resolve-simulation.test.ts +0 -118
  175. package/src/lib/resolve-simulation.ts +0 -181
  176. package/src/lib/session-store.ts +0 -110
  177. package/src/lib/shutdown.test.ts +0 -562
  178. package/src/lib/shutdown.ts +0 -621
  179. package/src/lib/template-category.test.ts +0 -71
  180. package/src/lib/template-category.ts +0 -72
  181. package/src/lib/template-locator.ts +0 -55
  182. package/src/lib/template-utils.ts +0 -45
  183. package/src/lib/ws-hub.ts +0 -281
  184. package/src/logger.test.ts +0 -172
  185. package/src/logger.ts +0 -402
  186. package/src/output.test.ts +0 -64
  187. package/src/output.ts +0 -160
@@ -1,186 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
- import { mkdtemp, rm } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { addEnvironment, loadEnvironments } from "../lib/le/le-environments.ts";
6
- import { configureLogger, lockLogger, unlockLogger } from "../logger.ts";
7
- import { findCliCommand, resetExitCode, runCliCommand } from "./cli-test-setup.ts";
8
-
9
- let testHome = "";
10
- let prevMiravoHome: string | undefined;
11
- const originalFetch = globalThis.fetch;
12
-
13
- beforeEach(async () => {
14
- resetExitCode();
15
- prevMiravoHome = Bun.env["MIRAVO_HOME"];
16
- testHome = await mkdtemp(join(tmpdir(), "miravo-env-command-test-"));
17
- Bun.env["MIRAVO_HOME"] = testHome;
18
- });
19
-
20
- afterEach(async () => {
21
- globalThis.fetch = originalFetch;
22
- if (prevMiravoHome === undefined) {
23
- delete Bun.env["MIRAVO_HOME"];
24
- } else {
25
- Bun.env["MIRAVO_HOME"] = prevMiravoHome;
26
- }
27
- resetExitCode();
28
- if (testHome) {
29
- await rm(testHome, { recursive: true, force: true });
30
- }
31
- });
32
-
33
- describe("miravo platforms env", () => {
34
- test("platforms command is registered with env subcommand", async () => {
35
- const { cmd } = await findCliCommand("platforms");
36
- const env = cmd?.commands.find((command) => command.name() === "env");
37
- expect(cmd).toBeDefined();
38
- expect(env).toBeDefined();
39
- expect(env?.aliases()).toContain("environments");
40
- });
41
-
42
- test("add stores an environment and inspect masks token by default", async () => {
43
- unlockLogger();
44
- configureLogger({ plain: true });
45
- lockLogger();
46
- using logSpy = spyOn(console, "log").mockImplementation(() => {});
47
-
48
- await runCliCommand([
49
- "platforms",
50
- "env",
51
- "add",
52
- "--label",
53
- "Prod",
54
- "--hostname",
55
- "le.example.local",
56
- "--api-token",
57
- "super-secret-token",
58
- "--source-hostname",
59
- "miravo.local",
60
- ]);
61
-
62
- const envs = await loadEnvironments();
63
- expect(envs).toHaveLength(1);
64
- expect(envs[0]?.label).toBe("Prod");
65
- expect(process.exitCode).toBe(0);
66
-
67
- logSpy.mockClear();
68
- await runCliCommand(["platforms", "env", "inspect", "Prod"]);
69
-
70
- const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
71
- expect(output).toContain("Prod");
72
- expect(output).toContain("••••");
73
- expect(output).not.toContain("super-secret-token");
74
- });
75
-
76
- test("inspect --show-token prints full token", async () => {
77
- const created = await addEnvironment({
78
- platformId: "litmus-edge",
79
- label: "Prod",
80
- hostname: "le.example.local",
81
- apiToken: "super-secret-token",
82
- sourceHostname: "miravo.local",
83
- });
84
- unlockLogger();
85
- configureLogger({ plain: true });
86
- lockLogger();
87
- using logSpy = spyOn(console, "log").mockImplementation(() => {});
88
-
89
- await runCliCommand(["platforms", "env", "inspect", created.id, "--show-token"]);
90
-
91
- const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
92
- expect(output).toContain("super-secret-token");
93
- });
94
-
95
- test("update resolves by label and updates the environment", async () => {
96
- await addEnvironment({
97
- platformId: "litmus-edge",
98
- label: "Prod",
99
- hostname: "le.example.local",
100
- apiToken: "super-secret-token",
101
- sourceHostname: "miravo.local",
102
- });
103
-
104
- await runCliCommand(["platforms", "env", "update", "Prod", "--hostname", "new.example.local"]);
105
-
106
- const envs = await loadEnvironments();
107
- expect(envs[0]?.hostname).toBe("new.example.local");
108
- expect(process.exitCode).toBe(0);
109
- });
110
-
111
- test("remove resolves by id and deletes the environment", async () => {
112
- const created = await addEnvironment({
113
- platformId: "litmus-edge",
114
- label: "Prod",
115
- hostname: "le.example.local",
116
- apiToken: "super-secret-token",
117
- sourceHostname: "miravo.local",
118
- });
119
-
120
- await runCliCommand(["platforms", "env", "remove", created.id, "--yes"]);
121
-
122
- const envs = await loadEnvironments();
123
- expect(envs).toHaveLength(0);
124
- expect(process.exitCode).toBe(0);
125
- });
126
-
127
- test("inspect fails when label is ambiguous", async () => {
128
- await addEnvironment({
129
- platformId: "litmus-edge",
130
- label: "Prod",
131
- hostname: "le-1.example.local",
132
- apiToken: "token-1",
133
- sourceHostname: "miravo.local",
134
- });
135
- await addEnvironment({
136
- platformId: "litmus-edge",
137
- label: "prod",
138
- hostname: "le-2.example.local",
139
- apiToken: "token-2",
140
- sourceHostname: "miravo.local",
141
- });
142
-
143
- await runCliCommand(["platforms", "env", "inspect", "Prod"]);
144
-
145
- expect(process.exitCode).toBe(1);
146
- });
147
-
148
- test("test succeeds for a reachable environment", async () => {
149
- await addEnvironment({
150
- platformId: "litmus-edge",
151
- label: "Prod",
152
- hostname: "le.example.local",
153
- apiToken: "super-secret-token",
154
- sourceHostname: "miravo.local",
155
- });
156
- globalThis.fetch = (async (input) => {
157
- const url = String(input);
158
- expect(url).toContain("/sysinfo/version");
159
- return Response.json({ version: "1.0.0" }, { status: 200 });
160
- }) as typeof fetch;
161
- unlockLogger();
162
- configureLogger({ plain: true });
163
- lockLogger();
164
- using logSpy = spyOn(console, "log").mockImplementation(() => {});
165
-
166
- await runCliCommand(["platforms", "env", "test", "Prod"]);
167
-
168
- const output = logSpy.mock.calls.map((call) => call.join(" ")).join("\n");
169
- expect(output).toContain("connected");
170
- expect(process.exitCode).toBe(0);
171
- });
172
-
173
- test("test fails for unsupported platform ids", async () => {
174
- await addEnvironment({
175
- platformId: "coming-soon-platform",
176
- label: "Future",
177
- hostname: "future.example.local",
178
- apiToken: "super-secret-token",
179
- sourceHostname: "miravo.local",
180
- });
181
-
182
- await runCliCommand(["platforms", "env", "test", "Future"]);
183
-
184
- expect(process.exitCode).toBe(1);
185
- });
186
- });
@@ -1,371 +0,0 @@
1
- import { confirm, isCancel } from "@clack/prompts";
2
- import { MiravoError } from "@miravo/shared";
3
- import type { Command } from "commander";
4
- import { colors } from "consola/utils";
5
- import {
6
- assertSupportedEnvironmentPlatform,
7
- testLeConnection,
8
- } from "../lib/le/le-connection-test.ts";
9
- import {
10
- addEnvironment,
11
- DEFAULT_PLATFORM_ID,
12
- getEnvironment,
13
- getEnvironmentByLabel,
14
- type LEEnvironment,
15
- loadEnvironments,
16
- removeEnvironment,
17
- updateEnvironment,
18
- } from "../lib/le/le-environments.ts";
19
- import { handleCommandError, writeOutput } from "../logger.ts";
20
- import { formatKeyValue, formatTable } from "../output.ts";
21
-
22
- interface SharedOptions {
23
- readonly json?: boolean;
24
- }
25
-
26
- interface AddEnvironmentOptions extends SharedOptions {
27
- readonly label?: string;
28
- readonly hostname?: string;
29
- readonly apiToken?: string;
30
- readonly sourceHostname?: string;
31
- readonly platform?: string;
32
- }
33
-
34
- interface UpdateEnvironmentOptions extends SharedOptions {
35
- readonly label?: string;
36
- readonly hostname?: string;
37
- readonly apiToken?: string;
38
- readonly sourceHostname?: string;
39
- readonly platform?: string;
40
- }
41
-
42
- interface InspectEnvironmentOptions extends SharedOptions {
43
- readonly showToken?: boolean;
44
- }
45
-
46
- interface RemoveEnvironmentOptions {
47
- readonly yes?: boolean;
48
- }
49
-
50
- function maskToken(token: string): string {
51
- if (token.length <= 8) return "••••••••";
52
- return `${"•".repeat(token.length - 4)}${token.slice(-4)}`;
53
- }
54
-
55
- function userError(message: string, fix: string): MiravoError {
56
- return new MiravoError(message, fix);
57
- }
58
-
59
- async function resolveEnvironmentByIdOrLabel(idOrLabel: string): Promise<LEEnvironment> {
60
- const byId = await getEnvironment(idOrLabel);
61
- if (byId) return byId;
62
-
63
- const envs = await loadEnvironments();
64
- const lower = idOrLabel.toLowerCase();
65
- const labelMatches = envs.filter((env) => env.label.toLowerCase() === lower);
66
-
67
- if (labelMatches.length === 1) return labelMatches[0] as LEEnvironment;
68
- if (labelMatches.length > 1) {
69
- const ids = labelMatches.map((env) => env.id).join(", ");
70
- throw userError(
71
- `Multiple environments use label "${idOrLabel}".`,
72
- `Use an id instead (${ids}).`,
73
- );
74
- }
75
-
76
- throw userError(
77
- `Environment "${idOrLabel}" not found.`,
78
- "Use 'miravo platforms env list' to inspect ids and labels.",
79
- );
80
- }
81
-
82
- function printEnvironmentDetails(
83
- env: LEEnvironment,
84
- options: {
85
- readonly json?: boolean | undefined;
86
- readonly showToken?: boolean | undefined;
87
- },
88
- ): void {
89
- const token = options.showToken ? env.apiToken : maskToken(env.apiToken);
90
- if (options.json) {
91
- writeOutput(
92
- JSON.stringify(
93
- {
94
- ...env,
95
- apiToken: token,
96
- },
97
- null,
98
- 2,
99
- ),
100
- );
101
- return;
102
- }
103
-
104
- writeOutput(
105
- formatKeyValue([
106
- ["Id", env.id],
107
- ["Label", env.label],
108
- ["Platform", env.platformId],
109
- ["Hostname", env.hostname],
110
- ["Source Host", env.sourceHostname],
111
- ["API Token", token],
112
- ["Last Used", env.lastUsedAt ? new Date(env.lastUsedAt).toISOString() : "never"],
113
- ]),
114
- );
115
- }
116
-
117
- function printEnvironmentList(
118
- envs: readonly LEEnvironment[],
119
- jsonOutput: boolean | undefined,
120
- ): void {
121
- if (jsonOutput) {
122
- writeOutput(
123
- JSON.stringify(
124
- envs.map((env) => ({
125
- ...env,
126
- apiToken: maskToken(env.apiToken),
127
- })),
128
- null,
129
- 2,
130
- ),
131
- );
132
- return;
133
- }
134
-
135
- if (envs.length === 0) {
136
- writeOutput("No environments configured.");
137
- return;
138
- }
139
-
140
- writeOutput(
141
- formatTable(
142
- {
143
- id: colors.dim("Id"),
144
- label: colors.dim("Label"),
145
- platform: colors.dim("Platform"),
146
- hostname: colors.dim("Hostname"),
147
- sourceHost: colors.dim("Source Host"),
148
- lastUsed: colors.dim("Last Used"),
149
- },
150
- envs.map((env) => ({
151
- id: env.id,
152
- label: env.label,
153
- platform: env.platformId,
154
- hostname: env.hostname,
155
- sourceHost: env.sourceHostname,
156
- lastUsed: env.lastUsedAt ? new Date(env.lastUsedAt).toISOString() : "never",
157
- })),
158
- ),
159
- );
160
- }
161
-
162
- function assertSupportedPlatform(platformId: string): void {
163
- if (platformId !== DEFAULT_PLATFORM_ID) {
164
- throw userError(
165
- `Unsupported platform "${platformId}". Only "${DEFAULT_PLATFORM_ID}" is currently supported.`,
166
- `Use --platform ${DEFAULT_PLATFORM_ID}.`,
167
- );
168
- }
169
- }
170
-
171
- export function registerEnvironmentsCommand(program: Command): void {
172
- const platforms = program
173
- .command("platforms")
174
- .description("Manage platform integrations and saved environments");
175
-
176
- const env = platforms
177
- .command("env")
178
- .alias("environments")
179
- .description("Manage saved platform environments");
180
-
181
- env
182
- .command("list")
183
- .description("List configured environments")
184
- .option("--json", "Machine-readable JSON output")
185
- .action(async (options: SharedOptions) => {
186
- try {
187
- const envs = await loadEnvironments();
188
- printEnvironmentList(envs, options.json);
189
- } catch (error) {
190
- handleCommandError(error);
191
- }
192
- });
193
-
194
- env
195
- .command("add")
196
- .description("Add a new environment")
197
- .requiredOption("--label <label>", "Environment label")
198
- .requiredOption("--hostname <host>", "Litmus Edge hostname or IP")
199
- .requiredOption("--api-token <token>", "Litmus Edge API token")
200
- .requiredOption("--source-hostname <host>", "Hostname LE uses to reach Miravo")
201
- .option("--platform <id>", `Platform id (default: ${DEFAULT_PLATFORM_ID})`, DEFAULT_PLATFORM_ID)
202
- .option("--json", "Machine-readable JSON output")
203
- .action(async (options: AddEnvironmentOptions) => {
204
- try {
205
- const platformId = options.platform ?? DEFAULT_PLATFORM_ID;
206
- assertSupportedPlatform(platformId);
207
-
208
- const existing = await getEnvironmentByLabel(options.label as string);
209
- if (existing) {
210
- throw userError(
211
- `Environment label "${options.label}" already exists. Use 'miravo platforms env update ${existing.id}' instead.`,
212
- `Choose a different --label or update ${existing.id}.`,
213
- );
214
- }
215
-
216
- const created = await addEnvironment({
217
- platformId,
218
- label: options.label as string,
219
- hostname: options.hostname as string,
220
- apiToken: options.apiToken as string,
221
- sourceHostname: options.sourceHostname as string,
222
- });
223
- printEnvironmentDetails(created, { json: options.json });
224
- } catch (error) {
225
- handleCommandError(error);
226
- }
227
- });
228
-
229
- env
230
- .command("inspect <idOrLabel>")
231
- .description("Inspect an environment by id or label")
232
- .option("--show-token", "Print the full API token", false)
233
- .option("--json", "Machine-readable JSON output")
234
- .action(async (idOrLabel: string, options: InspectEnvironmentOptions) => {
235
- try {
236
- const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
237
- printEnvironmentDetails(resolved, options);
238
- } catch (error) {
239
- handleCommandError(error);
240
- }
241
- });
242
-
243
- env
244
- .command("update <idOrLabel>")
245
- .description("Update an environment by id or label")
246
- .option("--label <label>", "New environment label")
247
- .option("--hostname <host>", "New Litmus Edge hostname or IP")
248
- .option("--api-token <token>", "New Litmus Edge API token")
249
- .option("--source-hostname <host>", "New hostname LE uses to reach Miravo")
250
- .option("--platform <id>", "New platform id")
251
- .option("--json", "Machine-readable JSON output")
252
- .action(async (idOrLabel: string, options: UpdateEnvironmentOptions) => {
253
- try {
254
- const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
255
- const hasUpdates =
256
- options.label !== undefined ||
257
- options.hostname !== undefined ||
258
- options.apiToken !== undefined ||
259
- options.sourceHostname !== undefined ||
260
- options.platform !== undefined;
261
- if (!hasUpdates) {
262
- throw userError(
263
- "No updates provided.",
264
- "Pass at least one of --label, --hostname, --api-token, --source-hostname, or --platform.",
265
- );
266
- }
267
- if (options.platform !== undefined) {
268
- assertSupportedPlatform(options.platform);
269
- }
270
- if (options.label !== undefined) {
271
- const byLabel = await getEnvironmentByLabel(options.label);
272
- if (byLabel && byLabel.id !== resolved.id) {
273
- throw userError(
274
- `Environment label "${options.label}" already exists. Choose a different label.`,
275
- "Choose a unique --label value.",
276
- );
277
- }
278
- }
279
-
280
- const updated = await updateEnvironment(resolved.id, {
281
- platformId: options.platform,
282
- label: options.label,
283
- hostname: options.hostname,
284
- apiToken: options.apiToken,
285
- sourceHostname: options.sourceHostname,
286
- });
287
- if (!updated) {
288
- throw userError(
289
- `Environment "${resolved.id}" not found.`,
290
- "Run 'miravo platforms env list' and retry with an existing id.",
291
- );
292
- }
293
- printEnvironmentDetails(updated, { json: options.json });
294
- } catch (error) {
295
- handleCommandError(error);
296
- }
297
- });
298
-
299
- env
300
- .command("remove <idOrLabel>")
301
- .description("Remove an environment by id or label")
302
- .option("--yes", "Skip confirmation prompt", false)
303
- .action(async (idOrLabel: string, options: RemoveEnvironmentOptions) => {
304
- try {
305
- const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
306
- if (!options.yes) {
307
- const confirmed = await confirm({
308
- message: `Remove environment "${resolved.label}" (${resolved.id})?`,
309
- });
310
- if (isCancel(confirmed) || confirmed !== true) {
311
- writeOutput("Canceled.");
312
- return;
313
- }
314
- }
315
-
316
- const removed = await removeEnvironment(resolved.id);
317
- if (!removed) {
318
- throw userError(
319
- `Environment "${resolved.id}" not found.`,
320
- "Run 'miravo platforms env list' and retry with an existing id.",
321
- );
322
- }
323
- writeOutput(`Removed environment "${resolved.label}" (${resolved.id}).`);
324
- } catch (error) {
325
- handleCommandError(error);
326
- }
327
- });
328
-
329
- env
330
- .command("test <idOrLabel>")
331
- .description("Test connectivity for an environment by id or label")
332
- .option("--json", "Machine-readable JSON output")
333
- .action(async (idOrLabel: string, options: SharedOptions) => {
334
- try {
335
- const resolved = await resolveEnvironmentByIdOrLabel(idOrLabel);
336
- assertSupportedEnvironmentPlatform(resolved);
337
- const result = await testLeConnection({
338
- hostname: resolved.hostname,
339
- apiToken: resolved.apiToken,
340
- sourceHostname: resolved.sourceHostname,
341
- });
342
-
343
- if (options.json) {
344
- writeOutput(JSON.stringify(result, null, 2));
345
- if (!result.connected) process.exitCode = 1;
346
- return;
347
- }
348
-
349
- if (result.connected) {
350
- writeOutput(
351
- formatKeyValue([
352
- ["Status", "connected"],
353
- ["Hostname", result.hostname],
354
- ["Source Host", result.sourceHostname],
355
- ]),
356
- );
357
- return;
358
- }
359
-
360
- writeOutput(
361
- formatKeyValue([
362
- ["Status", "failed"],
363
- ["Error", result.error],
364
- ]),
365
- );
366
- process.exitCode = 1;
367
- } catch (error) {
368
- handleCommandError(error);
369
- }
370
- });
371
- }
@@ -1,116 +0,0 @@
1
- import type { EngineMetricsSnapshot } from "@miravo/shared";
2
- import type { Command } from "commander";
3
- import { isPidAlive, listEntries, sweepStaleEntries } from "../lib/daemon-registry.ts";
4
- import { handleCommandError, writeOutput } from "../logger.ts";
5
- import { formatTable } from "../output.ts";
6
-
7
- interface PsOptions {
8
- readonly json?: boolean;
9
- }
10
-
11
- export function registerPsCommand(program: Command): void {
12
- program
13
- .command("ps")
14
- .description("List background simulations with live metrics")
15
- .option("--json", "Machine-readable JSON output")
16
- .addHelpText(
17
- "after",
18
- `
19
- Examples:
20
- $ miravo ps List background simulations
21
- $ miravo ps --json Machine-readable JSON output`,
22
- )
23
- .action(async (options: PsOptions) => {
24
- try {
25
- await psAction(options);
26
- } catch (error) {
27
- handleCommandError(error);
28
- }
29
- });
30
- }
31
-
32
- type PsRow = Record<string, string> & {
33
- readonly name: string;
34
- readonly status: string;
35
- readonly uptime: string;
36
- readonly ticks: string;
37
- readonly instances: string;
38
- readonly admin: string;
39
- readonly mqtt: string;
40
- readonly opcua: string;
41
- };
42
-
43
- async function psAction(options: PsOptions): Promise<void> {
44
- await sweepStaleEntries();
45
- const entries = await listEntries();
46
-
47
- if (entries.length === 0) {
48
- writeOutput("No running simulations.");
49
- return;
50
- }
51
-
52
- const rows: PsRow[] = [];
53
-
54
- for (const entry of entries) {
55
- const alive = isPidAlive(entry.pid);
56
- let status = alive ? "running" : "dead";
57
- let uptime = "-";
58
- let ticks = "-";
59
- let instances = "-";
60
-
61
- if (alive) {
62
- try {
63
- const url = `http://${entry.adminHost}:${entry.adminPort}/metrics`;
64
- const response = await fetch(url, { signal: AbortSignal.timeout(2_000) });
65
- if (response.ok) {
66
- const metrics = (await response.json()) as EngineMetricsSnapshot;
67
- status = metrics.core.state === "running" ? "running" : metrics.core.state;
68
- uptime = formatUptime(metrics.core.uptimeMs);
69
- ticks = String(metrics.core.tickCount);
70
- instances = String(metrics.core.instanceCount);
71
- }
72
- } catch {
73
- status = "starting";
74
- }
75
- }
76
-
77
- const mqttAdapter = entry.adapters["mqtt"];
78
- const opcuaAdapter = entry.adapters["opcua"];
79
-
80
- rows.push({
81
- name: entry.name,
82
- status,
83
- uptime,
84
- ticks,
85
- instances,
86
- admin: `:${entry.adminPort}`,
87
- mqtt: mqttAdapter ? `:${mqttAdapter.port}` : "-",
88
- opcua: opcuaAdapter ? `:${opcuaAdapter.port}` : "-",
89
- });
90
- }
91
-
92
- if (options.json) {
93
- writeOutput(JSON.stringify(rows, null, 2));
94
- return;
95
- }
96
-
97
- const headers: Record<string, string> = {
98
- name: "NAME",
99
- status: "STATUS",
100
- uptime: "UPTIME",
101
- ticks: "TICKS",
102
- instances: "INSTANCES",
103
- admin: "ADMIN",
104
- mqtt: "MQTT",
105
- opcua: "OPC UA",
106
- };
107
-
108
- writeOutput(formatTable(headers, rows));
109
- }
110
-
111
- function formatUptime(ms: number): string {
112
- const totalSeconds = Math.floor(ms / 1_000);
113
- const hours = Math.floor(totalSeconds / 3_600);
114
- const minutes = Math.floor((totalSeconds % 3_600) / 60);
115
- return `${hours}h ${String(minutes).padStart(2, "0")}m`;
116
- }