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.
- package/LICENSE +199 -0
- package/README.md +29 -59
- package/bin/miravo.js +84 -0
- package/package.json +21 -37
- package/console-dist/assets/activity-DsWeRq4h.js +0 -1
- package/console-dist/assets/api-4AqKvsm9.js +0 -1
- package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
- package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
- package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
- package/console-dist/assets/box-C1WaoOMF.js +0 -1
- package/console-dist/assets/card-BjeP32PN.js +0 -1
- package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
- package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
- package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
- package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
- package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
- package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
- package/console-dist/assets/control-CPhvwx9o.js +0 -1
- package/console-dist/assets/control-Cg2up9NA.js +0 -1
- package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
- package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
- package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
- package/console-dist/assets/dist-BhbDML0a.js +0 -1
- package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
- package/console-dist/assets/download-DQ6NH16U.js +0 -1
- package/console-dist/assets/engine-store-CrLnz692.js +0 -1
- package/console-dist/assets/events-CXm29Ok4.js +0 -1
- package/console-dist/assets/events-D01IxhnP.js +0 -1
- package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
- package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
- package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
- package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
- package/console-dist/assets/fleet-lotMGVqe.js +0 -1
- package/console-dist/assets/format-kXTonpDJ.js +0 -1
- package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
- package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
- package/console-dist/assets/index-CVPKSDvJ.js +0 -12
- package/console-dist/assets/index-DJkgDeun.css +0 -2
- package/console-dist/assets/input-BWEQFptx.js +0 -1
- package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
- package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
- package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
- package/console-dist/assets/label-uephX5ZG.js +0 -1
- package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
- package/console-dist/assets/login-C3RJli0m.js +0 -126
- package/console-dist/assets/models-B_UioWQ5.js +0 -1
- package/console-dist/assets/models-_IPlPiZ7.js +0 -1
- package/console-dist/assets/not-found-B8gAm08S.js +0 -1
- package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
- package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
- package/console-dist/assets/platforms-DStNbtVc.js +0 -1
- package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
- package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
- package/console-dist/assets/radio-CoHegOv5.js +0 -1
- package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
- package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/console-dist/assets/select-CeO4vP0G.js +0 -1
- package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
- package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
- package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
- package/console-dist/assets/tags-CiXOIHtY.js +0 -4
- package/console-dist/assets/tags-FQCXIxrP.js +0 -1
- package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
- package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
- package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
- package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
- package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
- package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
- package/console-dist/assets/validation-Blvud9_O.js +0 -1
- package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
- package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
- package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
- package/console-dist/favicon.svg +0 -37
- package/console-dist/index.html +0 -82
- package/console-dist/miravo-logo.png +0 -0
- package/src/__tests__/integration/executable-methods-runner.ts +0 -262
- package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
- package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
- package/src/commands/adapter.ts +0 -249
- package/src/commands/apply.test.ts +0 -62
- package/src/commands/apply.ts +0 -216
- package/src/commands/cleanup.test.ts +0 -34
- package/src/commands/cleanup.ts +0 -201
- package/src/commands/cli-test-setup.ts +0 -91
- package/src/commands/export.test.ts +0 -51
- package/src/commands/export.ts +0 -404
- package/src/commands/fork.ts +0 -219
- package/src/commands/inject.test.ts +0 -57
- package/src/commands/inject.ts +0 -212
- package/src/commands/load.ts +0 -167
- package/src/commands/logs.ts +0 -173
- package/src/commands/models.test.ts +0 -162
- package/src/commands/models.ts +0 -338
- package/src/commands/pause.ts +0 -67
- package/src/commands/platforms.test.ts +0 -186
- package/src/commands/platforms.ts +0 -371
- package/src/commands/ps.ts +0 -116
- package/src/commands/remove.ts +0 -38
- package/src/commands/reset.ts +0 -44
- package/src/commands/rm.ts +0 -97
- package/src/commands/spawn.ts +0 -45
- package/src/commands/speed.ts +0 -40
- package/src/commands/start/adapter-config.ts +0 -157
- package/src/commands/start/catalog-le-dt.test.ts +0 -50
- package/src/commands/start/catalog-le-dt.ts +0 -187
- package/src/commands/start/catalog-model-schema.ts +0 -89
- package/src/commands/start/catalog-template-detail.ts +0 -54
- package/src/commands/start/daemon.ts +0 -255
- package/src/commands/start/display.ts +0 -150
- package/src/commands/start/engine-setup.ts +0 -247
- package/src/commands/start/events.ts +0 -126
- package/src/commands/start/index.ts +0 -897
- package/src/commands/start/protocol-plans.ts +0 -239
- package/src/commands/start/template-loader.ts +0 -197
- package/src/commands/start/template-resolver.ts +0 -185
- package/src/commands/start/types.ts +0 -73
- package/src/commands/start.test.ts +0 -480
- package/src/commands/status.test.ts +0 -20
- package/src/commands/status.ts +0 -470
- package/src/commands/stop.test.ts +0 -84
- package/src/commands/stop.ts +0 -169
- package/src/commands/templates.test.ts +0 -58
- package/src/commands/templates.ts +0 -277
- package/src/index.ts +0 -166
- package/src/lib/admin-auth.ts +0 -157
- package/src/lib/admin-client.test.ts +0 -51
- package/src/lib/admin-client.ts +0 -326
- package/src/lib/admin-platform-routes.ts +0 -1017
- package/src/lib/admin-platform-stream.test.ts +0 -434
- package/src/lib/admin-platform-stream.ts +0 -788
- package/src/lib/admin-responses.ts +0 -211
- package/src/lib/admin-server.test.ts +0 -781
- package/src/lib/admin-server.ts +0 -797
- package/src/lib/auth-store.ts +0 -87
- package/src/lib/daemon-registry.test.ts +0 -429
- package/src/lib/daemon-registry.ts +0 -343
- package/src/lib/le/le-apply.test.ts +0 -591
- package/src/lib/le/le-apply.ts +0 -832
- package/src/lib/le/le-auth.ts +0 -89
- package/src/lib/le/le-cleanup.test.ts +0 -168
- package/src/lib/le/le-cleanup.ts +0 -399
- package/src/lib/le/le-cli-helpers.ts +0 -77
- package/src/lib/le/le-client.ts +0 -184
- package/src/lib/le/le-concurrency.test.ts +0 -157
- package/src/lib/le/le-concurrency.ts +0 -78
- package/src/lib/le/le-connection-test.ts +0 -42
- package/src/lib/le/le-devicehub-properties.test.ts +0 -53
- package/src/lib/le/le-devicehub-properties.ts +0 -33
- package/src/lib/le/le-devicehub.ts +0 -213
- package/src/lib/le/le-digital-twins.ts +0 -285
- package/src/lib/le/le-drivers.ts +0 -23
- package/src/lib/le/le-endpoint.ts +0 -40
- package/src/lib/le/le-environments.test.ts +0 -73
- package/src/lib/le/le-environments.ts +0 -156
- package/src/lib/le/le-markers.ts +0 -87
- package/src/lib/le/le-plan.test.ts +0 -26
- package/src/lib/le/le-plan.ts +0 -109
- package/src/lib/le/le-platform-operations.test.ts +0 -198
- package/src/lib/le/le-platform-operations.ts +0 -340
- package/src/lib/le/le-static-model-schema.ts +0 -78
- package/src/lib/le/le-static-planner.test.ts +0 -255
- package/src/lib/le/le-static-planner.ts +0 -442
- package/src/lib/le/le-template-exporter.test.ts +0 -527
- package/src/lib/le/le-template-exporter.ts +0 -459
- package/src/lib/le/le-value-types.ts +0 -23
- package/src/lib/mqtt-client.ts +0 -115
- package/src/lib/parse.test.ts +0 -123
- package/src/lib/parse.ts +0 -87
- package/src/lib/resolve-simulation.test.ts +0 -118
- package/src/lib/resolve-simulation.ts +0 -181
- package/src/lib/session-store.ts +0 -110
- package/src/lib/shutdown.test.ts +0 -562
- package/src/lib/shutdown.ts +0 -621
- package/src/lib/template-category.test.ts +0 -71
- package/src/lib/template-category.ts +0 -72
- package/src/lib/template-locator.ts +0 -55
- package/src/lib/template-utils.ts +0 -45
- package/src/lib/ws-hub.ts +0 -281
- package/src/logger.test.ts +0 -172
- package/src/logger.ts +0 -402
- package/src/output.test.ts +0 -64
- 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
|
-
});
|
package/src/commands/models.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/pause.ts
DELETED
|
@@ -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
|
-
}
|