miravo 0.6.0 → 0.6.1

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 +7 -70
  3. package/bin/miravo.js +84 -0
  4. package/package.json +20 -36
  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,157 +0,0 @@
1
- /**
2
- * Auth route handlers for the Miravo admin server.
3
- *
4
- * Each exported function handles a single auth endpoint and returns a Response.
5
- */
6
-
7
- import { ChangePasswordRequestSchema, LoginRequestSchema } from "@miravo/shared";
8
- import { checkContentType, jsonResponse } from "./admin-responses.ts";
9
- import type { SessionStore } from "./session-store.ts";
10
- import { buildClearCookie, buildSessionCookie, extractSessionId } from "./session-store.ts";
11
-
12
- /** Minimal auth configuration needed by auth handlers. */
13
- interface AuthConfig {
14
- readonly verifyPassword: (password: string) => Promise<boolean>;
15
- readonly isDefaultPassword: boolean;
16
- readonly sessionStore: SessionStore;
17
- readonly changePassword: (oldPassword: string, newPassword: string) => Promise<void>;
18
- readonly anonymousMode?: boolean;
19
- }
20
-
21
- /**
22
- * POST /auth/login
23
- * Validates credentials and sets a session cookie on success.
24
- */
25
- export async function handleLogin(request: Request, auth: AuthConfig): Promise<Response> {
26
- if (auth.anonymousMode) {
27
- const session = auth.sessionStore.create("anonymous");
28
- return new Response(JSON.stringify({ ok: true, changePassword: false }), {
29
- status: 200,
30
- headers: {
31
- "content-type": "application/json; charset=utf-8",
32
- "set-cookie": buildSessionCookie(session.id),
33
- "cache-control": "no-store",
34
- },
35
- });
36
- }
37
-
38
- const ctBlock = checkContentType(request);
39
- if (ctBlock) return ctBlock;
40
-
41
- let payload: unknown;
42
- try {
43
- payload = await request.json();
44
- } catch {
45
- return jsonResponse({ message: "Invalid JSON.", error: "Send valid JSON." }, 400);
46
- }
47
-
48
- const validation = LoginRequestSchema.safeParse(payload);
49
- if (!validation.success) {
50
- return jsonResponse(
51
- { message: "Invalid login request.", error: "Provide username and password." },
52
- 400,
53
- );
54
- }
55
- if (validation.data.username !== "admin") {
56
- return jsonResponse(
57
- { message: "Invalid credentials.", error: "Check username and password." },
58
- 401,
59
- );
60
- }
61
- const passwordValid = await auth.verifyPassword(validation.data.password);
62
- if (!passwordValid) {
63
- return jsonResponse(
64
- { message: "Invalid credentials.", error: "Check username and password." },
65
- 401,
66
- );
67
- }
68
-
69
- const session = auth.sessionStore.create("admin");
70
- return new Response(JSON.stringify({ ok: true, changePassword: auth.isDefaultPassword }), {
71
- status: 200,
72
- headers: {
73
- "content-type": "application/json; charset=utf-8",
74
- "set-cookie": buildSessionCookie(session.id),
75
- "cache-control": "no-store",
76
- },
77
- });
78
- }
79
-
80
- /**
81
- * POST /auth/logout
82
- * Destroys the current session and clears the session cookie.
83
- */
84
- export function handleLogout(request: Request, auth: AuthConfig): Response {
85
- const sessionId = extractSessionId(request);
86
- if (sessionId) auth.sessionStore.destroy(sessionId);
87
- return new Response(JSON.stringify({ ok: true }), {
88
- status: 200,
89
- headers: {
90
- "content-type": "application/json; charset=utf-8",
91
- "set-cookie": buildClearCookie(),
92
- "cache-control": "no-store",
93
- },
94
- });
95
- }
96
-
97
- /**
98
- * PUT /auth/password
99
- * Changes the admin password. Requires an active session.
100
- * The caller is responsible for verifying the session before calling this handler.
101
- */
102
- export async function handlePasswordChange(request: Request, auth: AuthConfig): Promise<Response> {
103
- const ctBlock = checkContentType(request);
104
- if (ctBlock) return ctBlock;
105
-
106
- let payload: unknown;
107
- try {
108
- payload = await request.json();
109
- } catch {
110
- return jsonResponse({ message: "Invalid JSON.", error: "Send valid JSON." }, 400);
111
- }
112
-
113
- const validation = ChangePasswordRequestSchema.safeParse(payload);
114
- if (!validation.success) {
115
- return jsonResponse(
116
- {
117
- message: "Invalid request.",
118
- error: "Provide oldPassword and newPassword (min 6 chars).",
119
- },
120
- 400,
121
- );
122
- }
123
-
124
- try {
125
- await auth.changePassword(validation.data.oldPassword, validation.data.newPassword);
126
- } catch (err) {
127
- const msg = err instanceof Error ? err.message : "Password change failed";
128
- return jsonResponse({ message: msg, error: "Check old password." }, 400);
129
- }
130
-
131
- // Invalidate all existing sessions, then issue a fresh one for the caller
132
- auth.sessionStore.clear();
133
- const session = auth.sessionStore.create("admin");
134
- return new Response(JSON.stringify({ ok: true }), {
135
- status: 200,
136
- headers: {
137
- "content-type": "application/json",
138
- "set-cookie": buildSessionCookie(session.id),
139
- },
140
- });
141
- }
142
-
143
- /**
144
- * GET /auth/status
145
- * Returns authentication state for the current session cookie.
146
- */
147
- export function handleAuthStatus(request: Request, auth: AuthConfig): Response {
148
- if (auth.anonymousMode) {
149
- return jsonResponse({ authenticated: true, username: "anonymous" }, 200);
150
- }
151
- const sessionId = extractSessionId(request);
152
- const session = sessionId ? auth.sessionStore.validate(sessionId) : null;
153
- if (session) {
154
- return jsonResponse({ authenticated: true, username: session.username }, 200);
155
- }
156
- return jsonResponse({ authenticated: false }, 200);
157
- }
@@ -1,51 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import type { EngineCommand } from "@miravo/shared";
3
- import { executeAdminCommand } from "./admin-client.ts";
4
-
5
- const originalFetch = globalThis.fetch;
6
-
7
- afterEach(() => {
8
- globalThis.fetch = originalFetch;
9
- delete Bun.env["MIRAVO_ADMIN_PASSWORD"];
10
- });
11
-
12
- describe("admin-client", () => {
13
- test("authenticates via session login and sends cookie on command requests", async () => {
14
- let loginCalled = false;
15
- let commandCookie = "";
16
-
17
- globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
18
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
19
-
20
- // Login request
21
- if (url.endsWith("/auth/login")) {
22
- loginCalled = true;
23
- return new Response(JSON.stringify({ ok: true, changePassword: false }), {
24
- status: 200,
25
- headers: { "set-cookie": "miravo_session=test-session-id; HttpOnly; Path=/" },
26
- });
27
- }
28
-
29
- // Command request — verify session cookie is sent
30
- if (url.endsWith("/commands")) {
31
- commandCookie = (init?.headers as Record<string, string>)?.["cookie"] ?? "";
32
- return Response.json({
33
- success: true,
34
- command: "pause",
35
- correlationId: "req-1",
36
- message: "paused",
37
- });
38
- }
39
-
40
- return Response.json({ message: "Not found" }, { status: 404 });
41
- }) as typeof fetch;
42
-
43
- const command: EngineCommand = { type: "pause", correlationId: "req-1" };
44
- const result = await executeAdminCommand("http://127.0.0.1:8080", command, 1_000);
45
-
46
- expect(loginCalled).toBe(true);
47
- expect(commandCookie).toBe("miravo_session=test-session-id");
48
- expect(result.success).toBe(true);
49
- expect(result.command).toBe("pause");
50
- });
51
- });
@@ -1,326 +0,0 @@
1
- import type {
2
- CommandResult,
3
- EngineCommand,
4
- EngineMetricsSnapshot,
5
- EngineStateSnapshot,
6
- } from "@miravo/shared";
7
- import {
8
- CommandResultSchema,
9
- EngineMetricsSnapshotSchema,
10
- EngineStateSnapshotSchema,
11
- MiravoError,
12
- } from "@miravo/shared";
13
- import { loadCredential } from "./auth-store.ts";
14
-
15
- const DEFAULT_ADMIN_HOST = "127.0.0.1";
16
- const DEFAULT_ADMIN_PORT = 8080;
17
-
18
- interface SafeParseResult<T> {
19
- readonly success: boolean;
20
- readonly data?: T;
21
- }
22
-
23
- interface SafeParseSchema<T> {
24
- safeParse(value: unknown): SafeParseResult<T>;
25
- }
26
-
27
- interface RequestJsonOptions<T> {
28
- readonly baseUrl: string;
29
- readonly path: string;
30
- readonly timeoutMs: number;
31
- readonly schema: SafeParseSchema<T>;
32
- readonly method?: "GET" | "POST";
33
- readonly body?: string;
34
- }
35
-
36
- function normalizeAdminHost(host: string): string {
37
- return host === "0.0.0.0" || host === "::" ? DEFAULT_ADMIN_HOST : host;
38
- }
39
-
40
- function resolveAdminUrl(adminUrl?: string): string {
41
- const explicitUrl = adminUrl ?? Bun.env["MIRAVO_ADMIN_URL"];
42
- if (explicitUrl && explicitUrl.trim().length > 0) {
43
- return parseAdminUrl(explicitUrl);
44
- }
45
-
46
- const hostValue = Bun.env["MIRAVO_ADMIN_HOST"] ?? DEFAULT_ADMIN_HOST;
47
- if (hostValue.trim().length === 0) {
48
- throw new MiravoError(
49
- "Invalid MIRAVO_ADMIN_HOST environment variable: value is empty.",
50
- "Set MIRAVO_ADMIN_HOST to a valid host such as 127.0.0.1 or 0.0.0.0.",
51
- );
52
- }
53
- const host = normalizeAdminHost(hostValue);
54
- const portValue = Bun.env["MIRAVO_ADMIN_PORT"];
55
- if (!portValue) {
56
- return `http://${host}:${DEFAULT_ADMIN_PORT}`;
57
- }
58
-
59
- const parsedPort = Number(portValue);
60
- if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
61
- throw new MiravoError(
62
- `Invalid MIRAVO_ADMIN_PORT environment variable: "${portValue}".`,
63
- "Set MIRAVO_ADMIN_PORT to a valid port number between 1 and 65535.",
64
- );
65
- }
66
-
67
- return `http://${host}:${parsedPort}`;
68
- }
69
-
70
- function parseAdminUrl(input: string): string {
71
- let url: URL;
72
- try {
73
- url = new URL(input);
74
- } catch {
75
- throw new MiravoError(
76
- `Invalid admin URL: "${input}".`,
77
- "Pass --admin-url with a full HTTP URL such as http://127.0.0.1:8080.",
78
- );
79
- }
80
-
81
- if (url.protocol !== "http:" && url.protocol !== "https:") {
82
- throw new MiravoError(
83
- `Invalid admin URL protocol: "${url.protocol}".`,
84
- "Pass an HTTP or HTTPS URL such as http://127.0.0.1:8080.",
85
- );
86
- }
87
-
88
- return url.toString().replace(/\/$/, "");
89
- }
90
-
91
- // ---------------------------------------------------------------------------
92
- // Session-based auth for CLI client
93
- // ---------------------------------------------------------------------------
94
-
95
- let sessionCookie: string | null = null;
96
-
97
- async function loginToAdmin(adminUrl: string): Promise<void> {
98
- // Resolve password: env > file > default
99
- const envPassword = Bun.env["MIRAVO_ADMIN_PASSWORD"];
100
- let password = "admin";
101
- if (envPassword && envPassword.trim().length > 0) {
102
- password = envPassword;
103
- } else {
104
- const credential = await loadCredential();
105
- // If credential file exists, we can't reverse the hash — use "admin" as default.
106
- // The server will verify against the stored hash. If the user changed their password,
107
- // they need to set MIRAVO_ADMIN_PASSWORD env var for CLI access.
108
- if (!credential) {
109
- password = "admin";
110
- }
111
- }
112
-
113
- const response = await fetch(`${adminUrl}/auth/login`, {
114
- method: "POST",
115
- headers: { "content-type": "application/json" },
116
- body: JSON.stringify({ username: "admin", password }),
117
- signal: AbortSignal.timeout(10_000),
118
- });
119
-
120
- if (!response.ok) {
121
- throw new MiravoError(
122
- "CLI authentication failed.",
123
- "Set MIRAVO_ADMIN_PASSWORD or log in with default credentials (admin/admin).",
124
- );
125
- }
126
-
127
- const setCookie = response.headers.get("set-cookie");
128
- if (setCookie) {
129
- // Extract just the cookie value (before first ;)
130
- sessionCookie = setCookie.split(";")[0] ?? null;
131
- }
132
- }
133
-
134
- async function ensureAuthenticated(adminUrl: string): Promise<void> {
135
- if (sessionCookie) return;
136
- await loginToAdmin(adminUrl);
137
- }
138
-
139
- function buildRequestHeaders(body?: string): Record<string, string> {
140
- const headers: Record<string, string> = {};
141
- if (body) {
142
- headers["content-type"] = "application/json";
143
- }
144
- if (sessionCookie) {
145
- headers["cookie"] = sessionCookie;
146
- }
147
- return headers;
148
- }
149
-
150
- async function requestJson<T>(options: RequestJsonOptions<T>): Promise<T> {
151
- await ensureAuthenticated(options.baseUrl);
152
- const requestUrl = new URL(options.path, `${options.baseUrl}/`).toString();
153
- const controller = new AbortController();
154
- const timer = setTimeout(() => controller.abort(), options.timeoutMs);
155
-
156
- try {
157
- let response: Response;
158
- try {
159
- response = await fetch(requestUrl, {
160
- method: options.method ?? "GET",
161
- headers: buildRequestHeaders(options.body),
162
- body: options.body,
163
- signal: controller.signal,
164
- });
165
- } catch (error) {
166
- if (error instanceof DOMException && error.name === "AbortError") {
167
- throw new MiravoError(
168
- `Admin request to ${requestUrl} timed out after ${options.timeoutMs}ms.`,
169
- "Check that Miravo is running and the admin URL is reachable.",
170
- );
171
- }
172
-
173
- const message = error instanceof Error ? error.message : String(error);
174
- throw new MiravoError(
175
- `Admin request to ${requestUrl} failed: ${message}`,
176
- "Check that Miravo is running and the admin URL is correct.",
177
- );
178
- }
179
-
180
- // Re-authenticate on session expiry and retry once
181
- if (response.status === 401) {
182
- sessionCookie = null;
183
- await loginToAdmin(options.baseUrl);
184
- response = await fetch(requestUrl, {
185
- method: options.method ?? "GET",
186
- headers: buildRequestHeaders(options.body),
187
- body: options.body,
188
- signal: controller.signal,
189
- });
190
- }
191
-
192
- const text = await response.text();
193
- let parsedJson: unknown;
194
- if (text.length > 0) {
195
- try {
196
- parsedJson = JSON.parse(text);
197
- } catch {
198
- throw new MiravoError(
199
- `Admin server returned invalid JSON from ${requestUrl}.`,
200
- "Check the Miravo admin server logs and retry.",
201
- );
202
- }
203
- }
204
-
205
- if (!response.ok) {
206
- const details =
207
- typeof parsedJson === "object" &&
208
- parsedJson !== null &&
209
- "message" in parsedJson &&
210
- typeof (parsedJson as Record<string, unknown>)["message"] === "string"
211
- ? String((parsedJson as Record<string, unknown>)["message"])
212
- : `HTTP ${response.status}`;
213
- const fix =
214
- typeof parsedJson === "object" &&
215
- parsedJson !== null &&
216
- "error" in parsedJson &&
217
- typeof (parsedJson as Record<string, unknown>)["error"] === "string"
218
- ? String((parsedJson as Record<string, unknown>)["error"])
219
- : "Check that Miravo is running and the admin request is valid.";
220
- throw new MiravoError(`Admin request to ${requestUrl} failed: ${details}`, fix);
221
- }
222
-
223
- const validation = options.schema.safeParse(parsedJson);
224
- if (!validation.success || validation.data === undefined) {
225
- throw new MiravoError(
226
- `Admin server returned an unexpected payload from ${requestUrl}.`,
227
- "Check that the Miravo CLI and server are running the same version.",
228
- );
229
- }
230
-
231
- return validation.data;
232
- } finally {
233
- clearTimeout(timer);
234
- }
235
- }
236
-
237
- async function getAdminMetrics(
238
- adminUrl: string,
239
- timeoutMs: number,
240
- ): Promise<EngineMetricsSnapshot> {
241
- return requestJson({
242
- baseUrl: adminUrl,
243
- path: "/metrics",
244
- timeoutMs,
245
- schema: EngineMetricsSnapshotSchema,
246
- });
247
- }
248
-
249
- async function getAdminState(adminUrl: string, timeoutMs: number): Promise<EngineStateSnapshot> {
250
- return requestJson({
251
- baseUrl: adminUrl,
252
- path: "/state",
253
- timeoutMs,
254
- schema: EngineStateSnapshotSchema,
255
- });
256
- }
257
-
258
- async function getAdminSnapshot(
259
- adminUrl: string,
260
- timeoutMs: number,
261
- ): Promise<{
262
- readonly metrics: EngineMetricsSnapshot;
263
- readonly state: EngineStateSnapshot;
264
- }> {
265
- const [metrics, state] = await Promise.all([
266
- getAdminMetrics(adminUrl, timeoutMs),
267
- getAdminState(adminUrl, timeoutMs),
268
- ]);
269
- return { metrics, state };
270
- }
271
-
272
- async function executeAdminCommand(
273
- adminUrl: string,
274
- command: EngineCommand,
275
- timeoutMs: number,
276
- ): Promise<CommandResult> {
277
- return requestJson({
278
- baseUrl: adminUrl,
279
- path: "/commands",
280
- timeoutMs,
281
- schema: CommandResultSchema,
282
- method: "POST",
283
- body: JSON.stringify(command),
284
- });
285
- }
286
-
287
- /** Schema that accepts any JSON object — used for adapter introspection (protocol-specific shape). */
288
- const AnyObjectSchema = {
289
- safeParse(value: unknown): SafeParseResult<Record<string, unknown>> {
290
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
291
- return { success: true, data: value as Record<string, unknown> };
292
- }
293
- return { success: false };
294
- },
295
- };
296
-
297
- async function getManifest(adminUrl: string, timeoutMs: number): Promise<Record<string, unknown>> {
298
- return requestJson({
299
- baseUrl: adminUrl,
300
- path: "/manifest",
301
- timeoutMs,
302
- schema: AnyObjectSchema,
303
- });
304
- }
305
-
306
- async function getLeDtJson(
307
- adminUrl: string,
308
- modelName: string,
309
- timeoutMs: number,
310
- ): Promise<Record<string, unknown>> {
311
- return requestJson({
312
- baseUrl: adminUrl,
313
- path: `/catalog/models/${encodeURIComponent(modelName)}/le-dt.json`,
314
- timeoutMs,
315
- schema: AnyObjectSchema,
316
- });
317
- }
318
-
319
- export {
320
- executeAdminCommand,
321
- getAdminSnapshot,
322
- getAdminState,
323
- getLeDtJson,
324
- getManifest,
325
- resolveAdminUrl,
326
- };