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,162 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { resolve } from "node:path";
3
- import { loadTwinModel } from "@miravo/core";
4
- import { type GeneratorConfig, TwinModelSchema } from "@miravo/shared";
5
- import { stripAnsi } from "consola/utils";
6
- import { configureLogger } from "../logger.ts";
7
- import { formatGeneratorSummary, formatModelInspect } from "./models.ts";
8
-
9
- configureLogger({ quiet: true });
10
-
11
- const modelsDir = resolve(import.meta.dir, "..", "..", "..", "content", "models");
12
-
13
- beforeEach(() => {
14
- process.exitCode = 0;
15
- });
16
-
17
- afterEach(() => {
18
- process.exitCode = 0;
19
- });
20
-
21
- // ---------------------------------------------------------------------------
22
- // formatGeneratorSummary
23
- // ---------------------------------------------------------------------------
24
-
25
- describe("formatGeneratorSummary", () => {
26
- const cases: [string, GeneratorConfig, string][] = [
27
- ["sine", { type: "sine", center: 1500, amplitude: 50, period: 60 }, "sine (center: 1500)"],
28
- ["noise", { type: "noise", center: 3.5, stddev: 0.5 }, "noise (center: 3.5)"],
29
- [
30
- "correlated",
31
- { type: "correlated", source: "rpm", factor: 0.007, offset: 2 },
32
- "correlated (source: rpm)",
33
- ],
34
- ["random-walk", { type: "random-walk", min: 0, max: 360, step: 5 }, "random-walk (0..360)"],
35
- ["step", { type: "step", values: [100, 75, 50, 75, 100], interval: 300 }, "step (5 values)"],
36
- [
37
- "state-machine",
38
- {
39
- type: "state-machine",
40
- states: ["idle", "running", "cooldown"],
41
- initial: "idle",
42
- transitions: [{ from: "idle", to: "running" }],
43
- },
44
- "state-machine (3 states)",
45
- ],
46
- [
47
- "health-dependent",
48
- {
49
- type: "health-dependent",
50
- stages: {
51
- healthy: { type: "noise", center: 1.5, stddev: 0.3 },
52
- degraded: { type: "noise", center: 3.5, stddev: 0.8 },
53
- },
54
- },
55
- "health-dependent (2 stages)",
56
- ],
57
- [
58
- "gate",
59
- {
60
- type: "gate",
61
- enabled_member: "run_command",
62
- input: { member: "speed_command" },
63
- disabled_value: 0,
64
- },
65
- "gate (enabled: run_command)",
66
- ],
67
- ];
68
-
69
- test.each(cases)("%s → %s", (_label, config, expected) => {
70
- expect(formatGeneratorSummary(config)).toBe(expected);
71
- });
72
- });
73
-
74
- // ---------------------------------------------------------------------------
75
- // formatModelInspect
76
- // ---------------------------------------------------------------------------
77
-
78
- describe("formatModelInspect", () => {
79
- test("model with parameters, lifecycle, and faults shows all sections", async () => {
80
- const model = await loadTwinModel(resolve(modelsDir, "centrifugal-pump.twin.yaml"));
81
- const raw = formatModelInspect(
82
- model,
83
- resolve(modelsDir, "centrifugal-pump.twin.yaml"),
84
- "built-in",
85
- );
86
- const output = stripAnsi(raw);
87
-
88
- expect(output).toContain("centrifugal-pump");
89
- expect(output).toContain("Parameters:");
90
- expect(output).toContain("rated_rpm");
91
- expect(output).toContain("Members (24):");
92
- expect(output).toContain("Methods (5):");
93
- expect(output).toContain("availability_state");
94
- expect(output).toContain("Lifecycle: healthy");
95
- expect(output).toContain("\u2192");
96
- expect(output).toContain("Faults:");
97
- expect(output).toContain("Source: built-in");
98
- });
99
-
100
- test("model without lifecycle shows 'none'", () => {
101
- const model = TwinModelSchema.parse({
102
- schema_version: "2",
103
- model: { name: "test-vessel", description: "Minimal vessel" },
104
- members: {
105
- level: {
106
- kind: "variable",
107
- data_type: "Double",
108
- access: "read",
109
- unit: "%",
110
- generator: { type: "noise", center: 65, stddev: 1.5 },
111
- },
112
- },
113
- });
114
- const raw = formatModelInspect(model, "/tmp/test-vessel.twin.yaml", "test");
115
- const output = stripAnsi(raw);
116
-
117
- expect(output).toContain("test-vessel");
118
- expect(output).toContain("Methods: none");
119
- expect(output).toContain("Lifecycle: none");
120
- });
121
- });
122
-
123
- // ---------------------------------------------------------------------------
124
- // CLI integration
125
- // ---------------------------------------------------------------------------
126
-
127
- describe("miravo models", () => {
128
- test("models inspect nonexistent model sets exit code 1", async () => {
129
- const { createProgram } = await import("../index.ts");
130
- const program = createProgram();
131
- program.exitOverride();
132
-
133
- try {
134
- await program.parseAsync(["node", "miravo", "models", "inspect", "nonexistent-model"]);
135
- } catch {
136
- // Commander may throw on exitOverride
137
- }
138
-
139
- expect(process.exitCode).toBe(1);
140
- });
141
-
142
- test("models list --dir with nonexistent path sets exit code 1", async () => {
143
- const { createProgram } = await import("../index.ts");
144
- const program = createProgram();
145
- program.exitOverride();
146
-
147
- try {
148
- await program.parseAsync([
149
- "node",
150
- "miravo",
151
- "models",
152
- "list",
153
- "--dir",
154
- "/definitely-nonexistent-miravo-test-path",
155
- ]);
156
- } catch {
157
- // Commander may throw on exitOverride
158
- }
159
-
160
- expect(process.exitCode).toBe(1);
161
- });
162
- });
@@ -1,338 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { relative, resolve } from "node:path";
3
- import { type DiscoveredModelWithSource, loadTwinModel } from "@miravo/core";
4
- import type { GeneratorConfig, TwinModel } from "@miravo/shared";
5
- import { TWIN_FILE_EXTENSION } from "@miravo/shared";
6
- import type { Command } from "commander";
7
- import { colors } from "consola/utils";
8
- import { createCatalog } from "../lib/template-locator.ts";
9
- import { configLog, handleCommandError, writeOutput } from "../logger.ts";
10
- import { formatTable } from "../output.ts";
11
-
12
- // ---------------------------------------------------------------------------
13
- // Command registration
14
- // ---------------------------------------------------------------------------
15
-
16
- export function registerModelsCommand(program: Command): void {
17
- const models = program.command("models").description("List and inspect asset models");
18
-
19
- models
20
- .command("list")
21
- .description("Discover and list available models")
22
- .option("--dir <path>", "Additional directory to search for .twin.yaml models")
23
- .addHelpText(
24
- "after",
25
- `
26
- Examples:
27
- $ miravo models list List all discoverable models`,
28
- )
29
- .action(async (options: { readonly dir?: string }) => {
30
- await modelsListAction(options);
31
- });
32
-
33
- models
34
- .command("inspect <name>")
35
- .description("Show detailed information about a model")
36
- .option("--raw", "Dump the raw YAML file to stdout")
37
- .addHelpText(
38
- "after",
39
- `
40
- Examples:
41
- $ miravo models inspect centrifugal-pump Show model details
42
- $ miravo models inspect centrifugal-pump --raw Dump raw YAML`,
43
- )
44
- .action(async (name: string, options: { readonly raw?: boolean }) => {
45
- await modelsInspectAction(name, options);
46
- });
47
- }
48
-
49
- // ---------------------------------------------------------------------------
50
- // models list
51
- // ---------------------------------------------------------------------------
52
-
53
- async function modelsListAction(options: { readonly dir?: string }): Promise<void> {
54
- try {
55
- const catalog = createCatalog();
56
- const allModels = await catalog.listModels();
57
-
58
- const builtinModels: DiscoveredModelWithSource[] = [];
59
- const localRegModels: DiscoveredModelWithSource[] = [];
60
- const cwdModels: DiscoveredModelWithSource[] = [];
61
-
62
- for (const model of allModels) {
63
- switch (model.source.kind) {
64
- case "builtin":
65
- builtinModels.push(model);
66
- break;
67
- case "local":
68
- localRegModels.push(model);
69
- break;
70
- case "cwd":
71
- cwdModels.push(model);
72
- break;
73
- default: {
74
- const _exhaustive: never = model.source.kind;
75
- throw new Error(`Unexpected content source kind: ${String(_exhaustive)}`);
76
- }
77
- }
78
- }
79
-
80
- if (options.dir) {
81
- const resolvedDir = resolve(options.dir);
82
- if (!existsSync(resolvedDir)) {
83
- configLog.error(`Directory not found: ${resolvedDir}`);
84
- process.exitCode = 1;
85
- return;
86
- }
87
- const { discoverModels } = await import("@miravo/core");
88
- const extraModels = await discoverModels(resolvedDir);
89
- const existingPaths = new Set(cwdModels.map((m) => m.path));
90
- for (const extra of extraModels) {
91
- if (!existingPaths.has(extra.path)) {
92
- existingPaths.add(extra.path);
93
- cwdModels.push({ ...extra, source: { kind: "cwd", path: resolvedDir } });
94
- }
95
- }
96
- }
97
-
98
- if (builtinModels.length === 0 && cwdModels.length === 0 && localRegModels.length === 0) {
99
- configLog.info("No models found.");
100
- return;
101
- }
102
-
103
- const headers = {
104
- name: colors.dim("Name"),
105
- members: colors.dim("Members"),
106
- methods: colors.dim("Methods"),
107
- faults: colors.dim("Faults"),
108
- lifecycle: colors.dim("Lifecycle"),
109
- source: colors.dim("Source"),
110
- };
111
-
112
- const renderSection = (label: string, models: readonly DiscoveredModelWithSource[]): void => {
113
- if (models.length === 0) return;
114
- writeOutput();
115
- writeOutput(` ${colors.bold(`${label}:`)}`);
116
- writeOutput(
117
- formatTable(
118
- headers,
119
- models.map((m) => ({
120
- name: colors.cyan(m.name),
121
- members: String(m.memberCount),
122
- methods: String(m.methodCount),
123
- faults: String(m.faultCount),
124
- lifecycle:
125
- m.lifecycleStageCount > 0 ? `${m.lifecycleStageCount} stages` : colors.dim("none"),
126
- source: colors.dim(m.source.kind),
127
- })),
128
- ),
129
- );
130
- };
131
-
132
- renderSection("Built-in", builtinModels);
133
- renderSection("Local", localRegModels);
134
- renderSection("Project", cwdModels);
135
- writeOutput();
136
- } catch (error) {
137
- handleCommandError(error);
138
- }
139
- }
140
-
141
- // ---------------------------------------------------------------------------
142
- // models inspect
143
- // ---------------------------------------------------------------------------
144
-
145
- async function modelsInspectAction(
146
- name: string,
147
- options: { readonly raw?: boolean },
148
- ): Promise<void> {
149
- try {
150
- const catalog = createCatalog();
151
- const isPath = name.includes("/") || name.endsWith(TWIN_FILE_EXTENSION);
152
- const resolvedPath = isPath ? resolve(name) : await catalog.resolveModel(name);
153
-
154
- const sourceLabel = catalog.getSourceLabel(resolvedPath);
155
-
156
- if (options.raw) {
157
- const rawText = await Bun.file(resolvedPath).text();
158
- process.stdout.write(rawText);
159
- return;
160
- }
161
-
162
- const model = await loadTwinModel(resolvedPath);
163
- const output = formatModelInspect(model, resolvedPath, sourceLabel);
164
- writeOutput(output);
165
- } catch (error) {
166
- handleCommandError(error);
167
- }
168
- }
169
-
170
- // ---------------------------------------------------------------------------
171
- // Formatting helpers (exported for testing)
172
- // ---------------------------------------------------------------------------
173
-
174
- export function formatGeneratorSummary(generator: GeneratorConfig): string {
175
- switch (generator.type) {
176
- case "sine":
177
- return `sine (center: ${generator.center})`;
178
- case "noise":
179
- return `noise (center: ${generator.center})`;
180
- case "correlated":
181
- return `correlated (source: ${generator.source})`;
182
- case "random-walk":
183
- return `random-walk (${generator.min}..${generator.max})`;
184
- case "step":
185
- return `step (${generator.values.length} values)`;
186
- case "state-machine":
187
- return `state-machine (${generator.states.length} states)`;
188
- case "health-dependent":
189
- return `health-dependent (${Object.keys(generator.stages).length} stages)`;
190
- case "ramp":
191
- return `ramp (rate: ${generator.rate}, duration: ${generator.duration}s)`;
192
- case "mean-reverting-noise":
193
- return `mean-reverting-noise (mean: ${generator.mean}, tau: ${generator.tau}s)`;
194
- case "timetable":
195
- return `timetable (${generator.points.length} points)`;
196
- case "first-order-lag":
197
- return `first-order-lag (tau: ${generator.tau}s)`;
198
- case "integrator":
199
- return `integrator (gain: ${generator.gain})`;
200
- case "transport-delay":
201
- return `transport-delay (delay: ${generator.delay}s)`;
202
- case "slew-rate-limiter":
203
- return `slew-rate-limiter (rate_up: ${generator.rate_up})`;
204
- case "second-order":
205
- return `second-order (freq: ${generator.natural_freq_hz}Hz, zeta: ${generator.damping_ratio})`;
206
- case "hysteresis-relay":
207
- return `hysteresis-relay (on: ${generator.on_threshold}, off: ${generator.off_threshold})`;
208
- case "weighted-sum":
209
- return `weighted-sum (${generator.terms.length} terms)`;
210
- case "gate":
211
- return `gate (enabled: ${generator.enabled_member})`;
212
- case "waveform":
213
- return `waveform (shape: ${generator.shape}, period: ${generator.period}s)`;
214
- case "chirp":
215
- return `chirp (${generator.start_freq_hz}–${generator.end_freq_hz}Hz, ${generator.sweep_duration}s)`;
216
- case "impulse":
217
- return `impulse (amplitude: ${generator.amplitude}${generator.probability !== undefined ? `, p=${generator.probability}` : ``})`;
218
- default: {
219
- const _exhaustive: never = generator;
220
- return String(_exhaustive);
221
- }
222
- }
223
- }
224
-
225
- export function formatModelInspect(
226
- model: TwinModel,
227
- sourcePath: string,
228
- sourceLabel: string,
229
- ): string {
230
- const lines: string[] = [];
231
-
232
- // Header
233
- if (model.model.description) {
234
- lines.push(
235
- ` ${colors.cyan(colors.bold(model.model.name))} ${colors.dim("\u2014")} ${model.model.description}`,
236
- );
237
- } else {
238
- lines.push(` ${colors.cyan(colors.bold(model.model.name))}`);
239
- }
240
- lines.push("");
241
-
242
- // Parameters
243
- if (model.parameters && Object.keys(model.parameters).length > 0) {
244
- lines.push(` ${colors.bold("Parameters:")}`);
245
- lines.push(
246
- formatTable(
247
- {
248
- name: colors.dim("Name"),
249
- type: colors.dim("Type"),
250
- defaultVal: colors.dim("Default"),
251
- unit: colors.dim("Unit"),
252
- },
253
- Object.entries(model.parameters).map(([name, param]) => ({
254
- name,
255
- type: param.type,
256
- defaultVal: String(param.default),
257
- unit: param.unit ?? "",
258
- })),
259
- ),
260
- );
261
- lines.push("");
262
- }
263
-
264
- // Members
265
- const memberEntries = Object.entries(model.members) as Array<
266
- [string, TwinModel["members"][string]]
267
- >;
268
- lines.push(` ${colors.bold(`Members (${memberEntries.length}):`)}`);
269
- lines.push(
270
- formatTable(
271
- {
272
- name: colors.dim("Name"),
273
- kind: colors.dim("Kind"),
274
- dataType: colors.dim("Data Type"),
275
- unit: colors.dim("Unit"),
276
- generator: colors.dim("Generator"),
277
- },
278
- memberEntries.map(([name, member]) => ({
279
- name,
280
- kind: member.kind,
281
- dataType: member.data_type,
282
- unit: member.unit ?? "",
283
- generator:
284
- member.kind === "variable" && member.generator
285
- ? formatGeneratorSummary(member.generator)
286
- : colors.dim("none"),
287
- })),
288
- ),
289
- );
290
- lines.push("");
291
-
292
- const methodEntries = Object.entries(model.methods ?? {});
293
- if (methodEntries.length > 0) {
294
- lines.push(` ${colors.bold(`Methods (${methodEntries.length}):`)}`);
295
- lines.push(
296
- formatTable(
297
- {
298
- name: colors.dim("Name"),
299
- inputs: colors.dim("Inputs"),
300
- outputs: colors.dim("Outputs"),
301
- description: colors.dim("Description"),
302
- },
303
- methodEntries.map(([name, method]) => ({
304
- name,
305
- inputs: String(Object.keys(method.input_arguments ?? {}).length),
306
- outputs: String(Object.keys(method.output_arguments ?? {}).length),
307
- description: method.description ?? "",
308
- })),
309
- ),
310
- );
311
- lines.push("");
312
- } else {
313
- lines.push(` ${colors.bold("Methods:")} ${colors.dim("none")}`);
314
- lines.push("");
315
- }
316
-
317
- // Lifecycle
318
- if (model.lifecycle && model.lifecycle.stages.length > 0) {
319
- const stageNames = model.lifecycle.stages.map((s) => s.name).join(` ${colors.dim("\u2192")} `);
320
- lines.push(` ${colors.bold("Lifecycle:")} ${stageNames}`);
321
- } else {
322
- lines.push(` ${colors.bold("Lifecycle:")} ${colors.dim("none")}`);
323
- }
324
-
325
- // Faults
326
- if (model.faults && model.faults.length > 0) {
327
- const faultNames = model.faults.map((f) => f.name).join(", ");
328
- lines.push(` ${colors.bold("Faults:")} ${faultNames}`);
329
- } else {
330
- lines.push(` ${colors.bold("Faults:")} ${colors.dim("none")}`);
331
- }
332
-
333
- // Source
334
- const relPath = relative(process.cwd(), sourcePath);
335
- lines.push(` ${colors.bold("Source:")} ${sourceLabel} ${colors.gray(`(${relPath})`)}`);
336
-
337
- return lines.join("\n");
338
- }
@@ -1,67 +0,0 @@
1
- import type { EngineCommand } from "@miravo/shared";
2
- import { CONTROL_CMD_TIMEOUT_MS } from "@miravo/shared";
3
- import type { Command } from "commander";
4
- import { executeAdminCommand, resolveAdminUrl } from "../lib/admin-client.ts";
5
- import { parseTimeoutSeconds } from "../lib/parse.ts";
6
- import { handleCommandError, handleCommandResult } from "../logger.ts";
7
-
8
- interface PauseResumeOptions {
9
- readonly json?: boolean;
10
- readonly adminUrl?: string;
11
- readonly timeout?: number;
12
- }
13
-
14
- export function registerPauseCommand(program: Command): void {
15
- program
16
- .command("pause")
17
- .description("Pause the running simulation (state is preserved)")
18
- .option("--json", "Machine-readable JSON output")
19
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
20
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
21
- .addHelpText(
22
- "after",
23
- `
24
- Examples:
25
- $ miravo pause Pause the simulation`,
26
- )
27
- .action(async (options: PauseResumeOptions) => {
28
- try {
29
- await sendSimpleCommand("pause", options);
30
- } catch (error) {
31
- handleCommandError(error);
32
- }
33
- });
34
- }
35
-
36
- export function registerResumeCommand(program: Command): void {
37
- program
38
- .command("resume")
39
- .description("Resume a paused simulation")
40
- .option("--json", "Machine-readable JSON output")
41
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
42
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
43
- .addHelpText(
44
- "after",
45
- `
46
- Examples:
47
- $ miravo resume Resume a paused simulation`,
48
- )
49
- .action(async (options: PauseResumeOptions) => {
50
- try {
51
- await sendSimpleCommand("resume", options);
52
- } catch (error) {
53
- handleCommandError(error);
54
- }
55
- });
56
- }
57
-
58
- async function sendSimpleCommand(
59
- type: "pause" | "resume",
60
- options: PauseResumeOptions,
61
- ): Promise<void> {
62
- const adminUrl = resolveAdminUrl(options.adminUrl);
63
- const timeoutMs = options.timeout ?? CONTROL_CMD_TIMEOUT_MS;
64
- const command: EngineCommand = { type };
65
- const result = await executeAdminCommand(adminUrl, command, timeoutMs);
66
- handleCommandResult(result, options.json);
67
- }