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,212 +0,0 @@
1
- import { confirm, intro, isCancel, outro, select } from "@clack/prompts";
2
- import type { EngineCommand } from "@miravo/shared";
3
- import { CONTROL_CMD_TIMEOUT_MS, MiravoError } from "@miravo/shared";
4
- import type { Command } from "commander";
5
- import { executeAdminCommand, getAdminState, resolveAdminUrl } from "../lib/admin-client.ts";
6
- import { parseTimeoutSeconds } from "../lib/parse.ts";
7
- import { faultLog, handleCommandError } from "../logger.ts";
8
-
9
- interface InjectOptions {
10
- readonly clear?: boolean;
11
- readonly json?: boolean;
12
- readonly name?: string;
13
- readonly adminUrl?: string;
14
- readonly timeout?: number;
15
- }
16
-
17
- /**
18
- * Register the `miravo inject` command with the root Commander program.
19
- * Triggers or clears a fault on a running twin instance via the admin HTTP server.
20
- */
21
- export function registerInjectCommand(program: Command): void {
22
- program
23
- .command("inject [instanceId] [fault]")
24
- .description("Trigger or clear a fault on a running instance (interactive if no args)")
25
- .option("--clear", "Clear the fault instead of triggering it")
26
- .option("--json", "Machine-readable JSON output")
27
- .option("--name <name>", "Target simulation name")
28
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
29
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
30
- .addHelpText(
31
- "after",
32
- `
33
- Examples:
34
- $ miravo inject Interactive mode (pick instance + fault)
35
- $ miravo inject pump-001 cavitation Trigger fault directly
36
- $ miravo inject pump-001 cavitation --clear Clear an active fault`,
37
- )
38
- .action(
39
- async (instanceId: string | undefined, fault: string | undefined, options: InjectOptions) => {
40
- await injectAction(instanceId, fault, options);
41
- },
42
- );
43
- }
44
-
45
- // ---------------------------------------------------------------------------
46
- // Action
47
- // ---------------------------------------------------------------------------
48
-
49
- async function injectAction(
50
- instanceId: string | undefined,
51
- fault: string | undefined,
52
- options: InjectOptions,
53
- ): Promise<void> {
54
- // Fail fast before broker connect when interactive prompts would be required.
55
- if ((!instanceId || !fault) && (!process.stdout.isTTY || !process.stdin.isTTY)) {
56
- faultLog.error("Interactive mode requires a terminal. Usage: miravo inject <instance> <fault>");
57
- process.exitCode = 1;
58
- return;
59
- }
60
-
61
- try {
62
- const timeoutMs = options.timeout ?? CONTROL_CMD_TIMEOUT_MS;
63
- const adminUrl = resolveAdminUrl(options.adminUrl);
64
- const stateResult = await getTargetState(adminUrl, timeoutMs, options.name);
65
-
66
- let targetInstance = instanceId;
67
- let targetFault = fault;
68
-
69
- // Interactive mode: pick instance and fault via prompts
70
- if (!targetInstance || !targetFault) {
71
- if (!process.stdout.isTTY || !process.stdin.isTTY) {
72
- faultLog.error(
73
- "Interactive mode requires a terminal. Usage: miravo inject <instance> <fault>",
74
- );
75
- process.exitCode = 1;
76
- return;
77
- }
78
-
79
- const picked = await pickInstanceAndFault(stateResult, options.clear ?? false);
80
- if (!picked) return;
81
-
82
- targetInstance = picked.instanceId;
83
- targetFault = picked.fault;
84
- }
85
-
86
- const command: EngineCommand = options.clear
87
- ? { type: "clearFault", instanceId: targetInstance, fault: targetFault }
88
- : { type: "triggerFault", instanceId: targetInstance, fault: targetFault };
89
-
90
- const result = await executeAdminCommand(adminUrl, command, timeoutMs);
91
-
92
- if (options.json) {
93
- console.log(JSON.stringify(result, null, 2));
94
- } else if (result.success) {
95
- faultLog.success(result.message);
96
- } else {
97
- faultLog.error(result.message);
98
- if (result.error) {
99
- faultLog.error(`Fix: ${result.error}`);
100
- }
101
- }
102
- if (!result.success) process.exitCode = 1;
103
- } catch (error) {
104
- handleCommandError(error, faultLog);
105
- }
106
- }
107
-
108
- // ---------------------------------------------------------------------------
109
- // Read state snapshot from admin server
110
- // ---------------------------------------------------------------------------
111
-
112
- interface StateSnapshot {
113
- readonly models: ReadonlyArray<{
114
- readonly name: string;
115
- readonly faultNames: readonly string[];
116
- }>;
117
- readonly instances: ReadonlyArray<{
118
- readonly id: string;
119
- readonly model: string;
120
- readonly lifecycleStage: string;
121
- readonly activeFaults: readonly string[];
122
- }>;
123
- }
124
-
125
- async function getTargetState(
126
- adminUrl: string,
127
- timeoutMs: number,
128
- expectedName?: string,
129
- ): Promise<StateSnapshot> {
130
- const state = await getAdminState(adminUrl, timeoutMs);
131
- if (expectedName && state.simulationName !== expectedName) {
132
- throw new MiravoError(
133
- `Connected to simulation "${state.simulationName}" instead of "${expectedName}".`,
134
- "Pass --admin-url for the correct Miravo process, or omit --name to target the connected simulation.",
135
- );
136
- }
137
- return state as StateSnapshot;
138
- }
139
-
140
- // ---------------------------------------------------------------------------
141
- // Interactive: pick instance and fault
142
- // ---------------------------------------------------------------------------
143
-
144
- async function pickInstanceAndFault(
145
- state: StateSnapshot,
146
- clearing: boolean,
147
- ): Promise<{ instanceId: string; fault: string } | null> {
148
- if (state.instances.length === 0) {
149
- faultLog.error("No instances running. Spawn instances first with 'miravo start'.");
150
- process.exitCode = 1;
151
- return null;
152
- }
153
-
154
- intro(clearing ? "Clear a fault" : "Inject a fault");
155
-
156
- // Pick instance
157
- const instanceChoice = await select({
158
- message: "Which instance?",
159
- options: state.instances.map((inst) => ({
160
- value: inst.id,
161
- label: inst.id,
162
- hint: `${inst.model} · ${inst.lifecycleStage}${inst.activeFaults.length > 0 ? ` · faults: ${inst.activeFaults.join(", ")}` : ""}`,
163
- })),
164
- });
165
-
166
- if (isCancel(instanceChoice)) {
167
- outro("Cancelled.");
168
- return null;
169
- }
170
-
171
- const selectedInstance = state.instances.find((i) => i.id === instanceChoice);
172
- if (!selectedInstance) return null;
173
-
174
- // Get available faults for this instance's model
175
- const model = state.models.find((m) => m.name === selectedInstance.model);
176
- const availableFaults = clearing ? selectedInstance.activeFaults : (model?.faultNames ?? []);
177
-
178
- if (availableFaults.length === 0) {
179
- const reason = clearing
180
- ? `No active faults on ${selectedInstance.id}.`
181
- : `Model "${selectedInstance.model}" has no defined faults.`;
182
- outro(reason);
183
- return null;
184
- }
185
-
186
- // Pick fault
187
- const faultChoice = await select({
188
- message: clearing ? "Which fault to clear?" : "Which fault to trigger?",
189
- options: availableFaults.map((f) => ({
190
- value: f,
191
- label: f,
192
- })),
193
- });
194
-
195
- if (isCancel(faultChoice)) {
196
- outro("Cancelled.");
197
- return null;
198
- }
199
-
200
- // Confirm
201
- const action = clearing ? "Clear" : "Trigger";
202
- const confirmed = await confirm({
203
- message: `${action} ${faultChoice as string} on ${instanceChoice as string}?`,
204
- });
205
-
206
- if (isCancel(confirmed) || !confirmed) {
207
- outro("Cancelled.");
208
- return null;
209
- }
210
-
211
- return { instanceId: instanceChoice as string, fault: faultChoice as string };
212
- }
@@ -1,167 +0,0 @@
1
- import { basename } from "node:path";
2
- import { isCancel, select } from "@clack/prompts";
3
- import type { ContentCatalog } from "@miravo/core";
4
- import { discoverTemplates } from "@miravo/core";
5
- import type { DiscoveredTemplate, EngineCommand } from "@miravo/shared";
6
- import { CONTROL_CMD_TIMEOUT_MS, ConfigNotFoundError, MiravoError } from "@miravo/shared";
7
- import type { Command } from "commander";
8
- import { executeAdminCommand, resolveAdminUrl } from "../lib/admin-client.ts";
9
- import { parsePositiveInt, parseTimeoutSeconds } from "../lib/parse.ts";
10
- import { isStressBenchmarkTemplate } from "../lib/template-category.ts";
11
- import {
12
- createCatalog,
13
- discoverPreferredLocalTemplates,
14
- resolveTemplateIdentifier,
15
- } from "../lib/template-locator.ts";
16
- import { configLog, handleCommandError, handleCommandResult } from "../logger.ts";
17
-
18
- interface LoadOptions {
19
- readonly template?: string;
20
- readonly count?: number;
21
- readonly json?: boolean;
22
- readonly adminUrl?: string;
23
- readonly timeout?: number;
24
- }
25
-
26
- export function registerLoadCommand(program: Command): void {
27
- program
28
- .command("load")
29
- .description("Add instances from a template to the running simulation")
30
- .option(
31
- "-t, --template <id|name|path,...>",
32
- "Template id, name, path, or comma-separated list (env: MIRAVO_TEMPLATE)",
33
- )
34
- .option(
35
- "-c, --count <n>",
36
- "Number of template copies to load per template (default: 1)",
37
- parsePositiveInt,
38
- )
39
- .option("--json", "Machine-readable JSON output")
40
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
41
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
42
- .addHelpText(
43
- "after",
44
- `
45
- Examples:
46
- $ miravo load -t smart-factory Load built-in template by name
47
- $ miravo load -t ./extra.miravo.yaml Load template from file
48
- $ miravo load -t smart-factory,water-treatment Load two templates at once
49
- $ miravo load -t smart-factory -c 3 Load 3 copies of a template
50
- $ miravo load Auto-discover and load`,
51
- )
52
- .action(async (options: LoadOptions) => {
53
- try {
54
- const catalog = createCatalog();
55
- const templateIdentifier = options.template ?? Bun.env["MIRAVO_TEMPLATE"];
56
-
57
- let templatePaths: string[];
58
- if (templateIdentifier !== undefined) {
59
- const identifiers = templateIdentifier
60
- .split(",")
61
- .map((s) => s.trim())
62
- .filter(Boolean);
63
- templatePaths = await Promise.all(
64
- identifiers.map((id) => resolveTemplateIdentifier(id, catalog)),
65
- );
66
- } else {
67
- const path = await resolveLoadTemplatePath(catalog);
68
- templatePaths = [path];
69
- }
70
-
71
- const adminUrl = resolveAdminUrl(options.adminUrl);
72
- const timeoutMs = options.timeout ?? CONTROL_CMD_TIMEOUT_MS;
73
-
74
- for (const templatePath of templatePaths) {
75
- const command: EngineCommand = {
76
- type: "loadTemplate",
77
- templatePath,
78
- count: options.count ?? 1,
79
- };
80
- const result = await executeAdminCommand(adminUrl, command, timeoutMs);
81
- handleCommandResult(result, options.json);
82
- }
83
- } catch (error) {
84
- handleCommandError(error);
85
- }
86
- });
87
- }
88
-
89
- async function resolveLoadTemplatePath(catalog: ContentCatalog): Promise<string> {
90
- // 1. Auto-discover local templates (CWD + local registry)
91
- const discovered = await discoverPreferredLocalTemplates(catalog);
92
- if (discovered.length === 1) {
93
- const first = discovered[0];
94
- if (first !== undefined) {
95
- configLog.info(`Found template: ${first.name} (${first.path})`);
96
- return first.path;
97
- }
98
- }
99
- if (discovered.length > 1) {
100
- if (!process.stdout.isTTY || !process.stdin.isTTY) {
101
- const names = discovered.map((t) => t.name).join(", ");
102
- throw new MiravoError(
103
- `Found ${discovered.length} templates: ${names}.`,
104
- "Use '--template <name|path>' to specify which template to use.",
105
- );
106
- }
107
- const first = discovered[0];
108
- if (first !== undefined) {
109
- configLog.warn(
110
- `Found ${discovered.length} templates: ${discovered.map((t) => t.name).join(", ")}. Using ${first.name}. Use '--template <name|path>' to specify.`,
111
- );
112
- return first.path;
113
- }
114
- }
115
-
116
- // 2. Fall back to built-in templates
117
- const builtinTemplateDir = catalog.getBuiltinTemplatesDir();
118
- let builtinTemplates: readonly DiscoveredTemplate[] = [];
119
- try {
120
- const all = await discoverTemplates(builtinTemplateDir);
121
- builtinTemplates = all.filter((t) => !isStressBenchmarkTemplate(t.id));
122
- } catch {
123
- // Builtin dir may not exist in edge cases
124
- }
125
- if (builtinTemplates.length > 0) {
126
- if (process.stdout.isTTY && process.stdin.isTTY) {
127
- return pickBuiltinTemplate(builtinTemplates);
128
- }
129
- const selected =
130
- builtinTemplates.find((t) => basename(t.path) === "smart-factory.miravo.yaml") ??
131
- builtinTemplates[0];
132
- if (selected !== undefined) {
133
- configLog.info(`No template specified. Using built-in "${selected.name}".`);
134
- configLog.info(
135
- "Run 'miravo templates list' to see all options, or use --template <name|path>.",
136
- );
137
- return selected.path;
138
- }
139
- }
140
-
141
- // 3. Nothing available
142
- throw new ConfigNotFoundError(
143
- "No template found. Use 'miravo templates list' to find built-ins or pass 'miravo load --template <name|path>'.",
144
- );
145
- }
146
-
147
- async function pickBuiltinTemplate(templates: readonly DiscoveredTemplate[]): Promise<string> {
148
- configLog.info("No template found in current directory.\n");
149
-
150
- const choice = await select({
151
- message: "Choose a built-in template to load:",
152
- options: templates.map((t) => ({
153
- value: t.path,
154
- label: t.name,
155
- hint: t.description ?? t.id,
156
- })),
157
- maxItems: 9,
158
- });
159
-
160
- if (isCancel(choice)) {
161
- process.exit(0);
162
- }
163
-
164
- configLog.info("Tip: use --template <id> to skip this prompt next time.");
165
-
166
- return choice as string;
167
- }
@@ -1,173 +0,0 @@
1
- import type { WSMessage } from "@miravo/shared";
2
- import type { Command } from "commander";
3
- import { colors } from "consola/utils";
4
- import { isPidAlive, readEntry } from "../lib/daemon-registry.ts";
5
- import { parseNonNegativeInt } from "../lib/parse.ts";
6
- import { handleCommandError, writeOutput } from "../logger.ts";
7
-
8
- interface LogsOptions {
9
- readonly tail?: number;
10
- readonly channels?: string;
11
- }
12
-
13
- export function registerLogsCommand(program: Command): void {
14
- program
15
- .command("logs <name>")
16
- .description("Stream domain events from a background simulation")
17
- .option("--tail <n>", "Fetch last N events before streaming", parseNonNegativeInt)
18
- .option(
19
- "--channels <list>",
20
- "Comma-separated channels: faults,lifecycle,instances,adapters,engine (default: all except tick)",
21
- )
22
- .addHelpText(
23
- "after",
24
- `
25
- Examples:
26
- $ miravo logs my-sim Stream live events
27
- $ miravo logs my-sim --tail 50 Show last 50 events first
28
- $ miravo logs my-sim --channels faults Only fault events`,
29
- )
30
- .action(async (name: string, options: LogsOptions) => {
31
- try {
32
- await logsAction(name, options);
33
- } catch (error) {
34
- handleCommandError(error);
35
- }
36
- });
37
- }
38
-
39
- async function logsAction(name: string, options: LogsOptions): Promise<void> {
40
- const entry = await readEntry(name);
41
- if (!entry) {
42
- writeOutput(`Simulation "${name}" not found. Use 'miravo ps' to list running simulations.`);
43
- process.exitCode = 1;
44
- return;
45
- }
46
-
47
- if (!isPidAlive(entry.pid)) {
48
- writeOutput(`Simulation "${name}" is not running.`);
49
- process.exitCode = 1;
50
- return;
51
- }
52
-
53
- const adminUrl = `http://${entry.adminHost}:${entry.adminPort}`;
54
-
55
- // Fetch historical events if --tail is set
56
- if (options.tail !== undefined && options.tail > 0) {
57
- try {
58
- const response = await fetch(`${adminUrl}/events?limit=${options.tail}`, {
59
- signal: AbortSignal.timeout(5_000),
60
- });
61
- if (response.ok) {
62
- const result = (await response.json()) as { events: readonly Record<string, unknown>[] };
63
- for (const event of result.events) {
64
- const formatted = formatLoggedEvent(event);
65
- if (formatted) writeOutput(formatted);
66
- }
67
- }
68
- } catch {
69
- writeOutput("Warning: could not fetch historical events.");
70
- }
71
- }
72
-
73
- // Connect WebSocket for live streaming
74
- const wsUrl = `ws://${entry.adminHost}:${entry.adminPort}/ws`;
75
-
76
- const selectedChannels = options.channels
77
- ? options.channels.split(",").map((c) => c.trim())
78
- : ["faults", "lifecycle", "instances", "engine", "adapters"];
79
-
80
- const ws = new WebSocket(wsUrl);
81
- let connected = false;
82
- let userInitiatedClose = false;
83
-
84
- const cleanup = (): void => {
85
- userInitiatedClose = true;
86
- ws.close();
87
- process.exit(0);
88
- };
89
- process.once("SIGINT", cleanup);
90
- process.once("SIGTERM", cleanup);
91
-
92
- ws.onopen = () => {
93
- connected = true;
94
- ws.send(JSON.stringify({ type: "subscribe", channels: selectedChannels }));
95
- writeOutput(
96
- `${colors.dim("Connected to")} ${name} ${colors.dim(`(channels: ${selectedChannels.join(", ")})`)}`,
97
- );
98
- writeOutput(colors.dim("Waiting for events... (Ctrl+C to stop)"));
99
- };
100
-
101
- ws.onmessage = (event) => {
102
- try {
103
- const msg = JSON.parse(String(event.data)) as WSMessage;
104
- const formatted = formatWSEvent(msg);
105
- if (formatted) writeOutput(formatted);
106
- } catch {
107
- // Ignore parse errors
108
- }
109
- };
110
-
111
- ws.onerror = () => {
112
- if (!connected) {
113
- writeOutput(`Failed to connect to simulation "${name}". Is it still running?`);
114
- process.exit(1);
115
- }
116
- writeOutput(`Connection lost to simulation "${name}".`);
117
- };
118
-
119
- // Keep alive until WebSocket closes
120
- await new Promise<void>((resolve) => {
121
- ws.onclose = () => resolve();
122
- });
123
-
124
- if (!connected || !userInitiatedClose) {
125
- process.exit(1);
126
- }
127
- }
128
-
129
- /** Format a WebSocket message for terminal output. Returns null for tick/snapshot events. */
130
- function formatWSEvent(msg: WSMessage): string | null {
131
- if (msg.type !== "runtime:event") return null;
132
- const evt = msg.event;
133
- switch (evt.kind) {
134
- case "fault:triggered":
135
- return `${colors.red("[fault:triggered]")} ${evt.instanceId} · ${evt.fault}`;
136
- case "fault:cleared":
137
- return `${colors.green("[fault:cleared]")} ${evt.instanceId} · ${evt.fault}`;
138
- case "lifecycle:changed":
139
- return `${colors.blue("[lifecycle:changed]")} ${evt.instanceId} · ${evt.from} → ${evt.to}`;
140
- case "instance:created":
141
- return `${colors.cyan("[instance:created]")} ${evt.id} · ${evt.assetType}`;
142
- case "instance:removed":
143
- return `${colors.yellow("[instance:removed]")} ${evt.id}`;
144
- case "engine:state":
145
- return `${colors.magenta("[engine:state]")} ${evt.from} → ${evt.to}`;
146
- case "adapter:enabled":
147
- return `${colors.cyan("[adapter:enabled]")} ${evt.name}`;
148
- case "adapter:disabled":
149
- return `${colors.yellow("[adapter:disabled]")} ${evt.name}`;
150
- default:
151
- return null;
152
- }
153
- }
154
-
155
- function formatLoggedEvent(event: Record<string, unknown>): string | null {
156
- const type = event["type"];
157
- if (typeof type !== "string") return null;
158
-
159
- switch (type) {
160
- case "fault:triggered":
161
- return `${colors.red("[fault:triggered]")} ${event["instanceId"]} · ${event["fault"]}`;
162
- case "fault:cleared":
163
- return `${colors.green("[fault:cleared]")} ${event["instanceId"]} · ${event["fault"]}`;
164
- case "lifecycle:changed":
165
- return `${colors.blue("[lifecycle:changed]")} ${event["instanceId"]} · ${event["from"]} → ${event["to"]}`;
166
- case "instance:created":
167
- return `${colors.cyan("[instance:created]")} ${event["instanceId"]} · ${event["assetType"]}`;
168
- case "instance:removed":
169
- return `${colors.yellow("[instance:removed]")} ${event["instanceId"]}`;
170
- default:
171
- return null;
172
- }
173
- }