@mediaruntime/cli 0.1.0 → 0.2.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.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { createHash, randomUUID as randomUUID2 } from "crypto";
4
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
5
5
  import { constants, realpathSync } from "fs";
6
6
  import {
7
7
  access,
@@ -52,6 +52,312 @@ var BundleDownloadError = class extends CliError {
52
52
  }
53
53
  };
54
54
 
55
+ // src/auth/api.ts
56
+ var PRODUCTION_ORIGIN = "https://mediaruntime.com";
57
+ var AuthApiError = class extends CliError {
58
+ status;
59
+ constructor(code, message, status) {
60
+ super(code, message, status >= 500 ? 5 : 3);
61
+ this.name = "AuthApiError";
62
+ this.status = status;
63
+ }
64
+ };
65
+ function endpoint(baseUrl, path) {
66
+ return `${baseUrl.replace(/\/+$/, "")}/api/cli/auth/${path}`;
67
+ }
68
+ async function responsePayload(response) {
69
+ try {
70
+ const value = await response.json();
71
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
72
+ } catch {
73
+ return {};
74
+ }
75
+ }
76
+ async function post(baseUrl, path, body, apiKey) {
77
+ let response;
78
+ try {
79
+ response = await fetch(endpoint(baseUrl, path), {
80
+ method: "POST",
81
+ redirect: "error",
82
+ headers: {
83
+ "Content-Type": "application/json",
84
+ ...apiKey ? { "X-API-Key": apiKey } : {}
85
+ },
86
+ body: JSON.stringify(body)
87
+ });
88
+ } catch (error) {
89
+ throw new CliError("auth_connection_error", "Could not reach MediaRuntime authentication", 5, { cause: error });
90
+ }
91
+ const payload = await responsePayload(response);
92
+ if (!response.ok) {
93
+ const errorPayload = payload;
94
+ throw new AuthApiError(
95
+ typeof errorPayload.error === "string" ? errorPayload.error : "authorization_failed",
96
+ typeof errorPayload.detail === "string" ? errorPayload.detail : `Authentication failed with HTTP ${response.status}`,
97
+ response.status
98
+ );
99
+ }
100
+ return payload;
101
+ }
102
+ function requireString(payload, key) {
103
+ const value = payload[key];
104
+ if (typeof value !== "string" || !value) throw new CliError("invalid_auth_response", "MediaRuntime returned an invalid authentication response", 5);
105
+ return value;
106
+ }
107
+ async function startAuthorization(baseUrl, codeChallenge) {
108
+ const payload = await post(baseUrl, "start", { codeChallenge, clientName: "MediaRuntime CLI" });
109
+ return {
110
+ deviceCode: requireString(payload, "deviceCode"),
111
+ userCode: requireString(payload, "userCode"),
112
+ verificationUri: requireString(payload, "verificationUri"),
113
+ verificationUriComplete: requireString(payload, "verificationUriComplete"),
114
+ expiresIn: Number(payload.expiresIn),
115
+ interval: Number(payload.interval)
116
+ };
117
+ }
118
+ async function exchangeAuthorization(baseUrl, deviceCode, codeVerifier) {
119
+ const payload = await post(baseUrl, "token", { deviceCode, codeVerifier });
120
+ const credentialType = requireString(payload, "credentialType");
121
+ if (credentialType !== "cli") throw new CliError("invalid_auth_response", "MediaRuntime returned the wrong credential type", 5);
122
+ return {
123
+ apiKey: requireString(payload, "apiKey"),
124
+ accountId: requireString(payload, "accountId"),
125
+ keyId: requireString(payload, "keyId"),
126
+ credentialType
127
+ };
128
+ }
129
+ async function inspectCredential(baseUrl, apiKey) {
130
+ const payload = await post(baseUrl, "status", {}, apiKey);
131
+ const credentialType = requireString(payload, "credentialType");
132
+ if (credentialType !== "cli" && credentialType !== "api_key") {
133
+ throw new CliError("invalid_auth_response", "MediaRuntime returned the wrong credential type", 5);
134
+ }
135
+ return {
136
+ status: "authenticated",
137
+ accountId: requireString(payload, "accountId"),
138
+ keyId: requireString(payload, "keyId"),
139
+ credentialType
140
+ };
141
+ }
142
+ async function revokeCredential(baseUrl, apiKey) {
143
+ await post(baseUrl, "revoke", {}, apiKey);
144
+ }
145
+
146
+ // src/commands/auth.ts
147
+ import { createHash, randomBytes } from "crypto";
148
+
149
+ // src/auth/browser.ts
150
+ import { spawn } from "child_process";
151
+ async function openBrowser(url) {
152
+ const command = process.platform === "darwin" ? { executable: "open", args: [url] } : process.platform === "win32" ? { executable: "rundll32", args: ["url.dll,FileProtocolHandler", url] } : { executable: "xdg-open", args: [url] };
153
+ await new Promise((resolve2, reject) => {
154
+ const child = spawn(command.executable, command.args, {
155
+ detached: true,
156
+ stdio: "ignore"
157
+ });
158
+ child.once("error", (error) => reject(new CliError(
159
+ "browser_open_failed",
160
+ "Could not open a browser; rerun with --no-browser and open the printed URL",
161
+ 2,
162
+ { cause: error }
163
+ )));
164
+ child.once("spawn", () => {
165
+ child.unref();
166
+ resolve2();
167
+ });
168
+ });
169
+ }
170
+
171
+ // src/auth/credential-store.ts
172
+ var SERVICE = "mediaruntime-cli";
173
+ function vaultAccount(baseUrl) {
174
+ return new URL(baseUrl).host.toLowerCase();
175
+ }
176
+ function validCredential(value) {
177
+ if (!value || typeof value !== "object") return false;
178
+ const item = value;
179
+ return typeof item.apiKey === "string" && item.apiKey.startsWith("sk_") && typeof item.accountId === "string" && item.accountId.length > 0 && typeof item.keyId === "string" && item.keyId.length > 0 && typeof item.baseUrl === "string" && item.baseUrl.length > 0 && typeof item.createdAt === "string" && item.createdAt.length > 0;
180
+ }
181
+ async function entry(baseUrl) {
182
+ try {
183
+ const { Entry } = await import("@napi-rs/keyring");
184
+ return new Entry(SERVICE, vaultAccount(baseUrl));
185
+ } catch (error) {
186
+ throw new CliError(
187
+ "credential_store_unavailable",
188
+ "The operating-system credential vault is unavailable; use MEDIARUNTIME_API_KEY on this machine",
189
+ 2,
190
+ { cause: error }
191
+ );
192
+ }
193
+ }
194
+ var OsCredentialStore = class {
195
+ async get(baseUrl) {
196
+ try {
197
+ const raw = (await entry(baseUrl)).getPassword();
198
+ if (!raw) return null;
199
+ const parsed = JSON.parse(raw);
200
+ if (!validCredential(parsed) || parsed.baseUrl !== baseUrl) {
201
+ throw new Error("Stored credential has an invalid shape");
202
+ }
203
+ return parsed;
204
+ } catch (error) {
205
+ if (error instanceof SyntaxError || error instanceof Error && error.message.includes("invalid shape")) {
206
+ throw new CliError("credential_store_invalid", "The stored MediaRuntime credential is invalid; run mediaruntime logout --local-only", 3);
207
+ }
208
+ if (error instanceof CliError) throw error;
209
+ if (error instanceof Error && /not found|no entry|no matching|item.*exist/i.test(error.message)) return null;
210
+ throw new CliError("credential_store_error", "Could not read the operating-system credential vault", 2, { cause: error });
211
+ }
212
+ }
213
+ async set(credential) {
214
+ try {
215
+ (await entry(credential.baseUrl)).setPassword(JSON.stringify(credential));
216
+ } catch (error) {
217
+ if (error instanceof CliError) throw error;
218
+ throw new CliError("credential_store_error", "Could not save the login in the operating-system credential vault", 2, { cause: error });
219
+ }
220
+ }
221
+ async delete(baseUrl) {
222
+ try {
223
+ (await entry(baseUrl)).deletePassword();
224
+ } catch (error) {
225
+ if (error instanceof CliError) throw error;
226
+ if (error instanceof Error && /not found|no entry|no matching|item.*exist/i.test(error.message)) return;
227
+ throw new CliError("credential_store_error", "Could not remove the login from the operating-system credential vault", 2, { cause: error });
228
+ }
229
+ }
230
+ };
231
+
232
+ // src/commands/auth.ts
233
+ var defaultSleep = (milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds));
234
+ function verifierChallenge(verifier) {
235
+ return createHash("sha256").update(verifier).digest("base64url");
236
+ }
237
+ function defaultVerifier() {
238
+ return randomBytes(32).toString("base64url");
239
+ }
240
+ function store(dependencies) {
241
+ return dependencies.credentialStore ?? new OsCredentialStore();
242
+ }
243
+ function assertOnly(args, allowed) {
244
+ const unexpected = args.find((arg) => !allowed.includes(arg));
245
+ if (unexpected) throw new UsageError(`Unknown authentication option: ${unexpected}`);
246
+ }
247
+ async function resolveCredential(baseUrl, dependencies = {}) {
248
+ const environment = process.env.MEDIARUNTIME_API_KEY?.trim();
249
+ if (environment) return { apiKey: environment, source: "environment" };
250
+ const stored = await store(dependencies).get(baseUrl);
251
+ return stored ? { apiKey: stored.apiKey, source: "keychain", stored } : null;
252
+ }
253
+ async function runLoginCommand(args, baseUrl, dependencies = {}) {
254
+ assertOnly(args, ["--no-browser"]);
255
+ const noBrowser = args.includes("--no-browser");
256
+ const write = dependencies.writeStdout ?? ((text) => process.stdout.write(text));
257
+ const existing = await store(dependencies).get(baseUrl);
258
+ if (existing) {
259
+ try {
260
+ await inspectCredential(baseUrl, existing.apiKey);
261
+ write(`Already logged in to ${baseUrl} (account ${existing.accountId}).
262
+ `);
263
+ return 0;
264
+ } catch (error) {
265
+ if (!(error instanceof AuthApiError) || error.status >= 500) throw error;
266
+ }
267
+ }
268
+ const verifier = dependencies.randomVerifier?.() ?? defaultVerifier();
269
+ if (verifier.length < 43 || verifier.length > 128) {
270
+ throw new CliError("invalid_verifier", "Generated PKCE verifier is invalid", 1);
271
+ }
272
+ const authorization = await startAuthorization(baseUrl, verifierChallenge(verifier));
273
+ if (!Number.isFinite(authorization.expiresIn) || authorization.expiresIn <= 0 || !Number.isFinite(authorization.interval) || authorization.interval < 1) {
274
+ throw new CliError("invalid_auth_response", "MediaRuntime returned invalid authorization timing", 5);
275
+ }
276
+ write(`Open this URL to authorize MediaRuntime CLI:
277
+ ${authorization.verificationUriComplete}
278
+
279
+ `);
280
+ write(`Confirm code: ${authorization.userCode}
281
+ `);
282
+ if (!noBrowser) await (dependencies.openBrowser ?? openBrowser)(authorization.verificationUriComplete);
283
+ write("Waiting for browser approval\u2026\n");
284
+ const now = dependencies.now ?? Date.now;
285
+ const sleep = dependencies.sleep ?? defaultSleep;
286
+ const deadline = now() + authorization.expiresIn * 1e3;
287
+ let credential;
288
+ while (now() < deadline) {
289
+ try {
290
+ credential = await exchangeAuthorization(baseUrl, authorization.deviceCode, verifier);
291
+ break;
292
+ } catch (error) {
293
+ if (error instanceof AuthApiError && error.code === "authorization_pending") {
294
+ await sleep(authorization.interval * 1e3);
295
+ continue;
296
+ }
297
+ throw error;
298
+ }
299
+ }
300
+ if (!credential) throw new CliError("authorization_timeout", "Browser authorization expired; run mediaruntime login again", 7);
301
+ try {
302
+ await inspectCredential(baseUrl, credential.apiKey);
303
+ await store(dependencies).set({
304
+ apiKey: credential.apiKey,
305
+ accountId: credential.accountId,
306
+ keyId: credential.keyId,
307
+ baseUrl,
308
+ createdAt: new Date(now()).toISOString()
309
+ });
310
+ } catch (error) {
311
+ await revokeCredential(baseUrl, credential.apiKey).catch(() => void 0);
312
+ throw error;
313
+ }
314
+ write(`Logged in to ${baseUrl} (account ${credential.accountId}).
315
+ `);
316
+ if (process.env.MEDIARUNTIME_API_KEY?.trim()) {
317
+ write("MEDIARUNTIME_API_KEY is set and will take precedence over this login.\n");
318
+ }
319
+ return 0;
320
+ }
321
+ async function runAuthStatusCommand(args, baseUrl, dependencies = {}) {
322
+ assertOnly(args, ["--json"]);
323
+ const json = args.includes("--json");
324
+ const write = dependencies.writeStdout ?? ((text) => process.stdout.write(text));
325
+ const resolved = await resolveCredential(baseUrl, dependencies);
326
+ if (!resolved) throw new CliError("authentication_required", "Run mediaruntime login or set MEDIARUNTIME_API_KEY", 3);
327
+ const status = await inspectCredential(baseUrl, resolved.apiKey);
328
+ if (json) {
329
+ write(`${JSON.stringify({ authenticated: true, source: resolved.source, ...status })}
330
+ `);
331
+ } else {
332
+ write(`Authenticated via ${resolved.source} (account ${status.accountId}, key ${status.keyId}).
333
+ `);
334
+ }
335
+ return 0;
336
+ }
337
+ async function runLogoutCommand(args, baseUrl, dependencies = {}) {
338
+ assertOnly(args, ["--local-only"]);
339
+ const localOnly = args.includes("--local-only");
340
+ const write = dependencies.writeStdout ?? ((text) => process.stdout.write(text));
341
+ const credentialStore = store(dependencies);
342
+ const credential = await credentialStore.get(baseUrl);
343
+ if (!credential) {
344
+ write(`No stored login for ${baseUrl}.
345
+ `);
346
+ if (process.env.MEDIARUNTIME_API_KEY?.trim()) write("MEDIARUNTIME_API_KEY remains active in this shell.\n");
347
+ return 0;
348
+ }
349
+ if (!localOnly) await revokeCredential(baseUrl, credential.apiKey);
350
+ await credentialStore.delete(baseUrl);
351
+ write(localOnly ? "Removed the local CLI login without server revocation.\n" : "Logged out and revoked the CLI credential.\n");
352
+ if (process.env.MEDIARUNTIME_API_KEY?.trim()) write("MEDIARUNTIME_API_KEY remains active in this shell.\n");
353
+ return 0;
354
+ }
355
+ async function runAuthCommand(args, baseUrl, dependencies = {}) {
356
+ const subcommand = args[0];
357
+ if (subcommand !== "status") throw new UsageError("Usage: mediaruntime auth status [--json]");
358
+ return runAuthStatusCommand(args.slice(1), baseUrl, dependencies);
359
+ }
360
+
55
361
  // src/commands/jobs.ts
56
362
  var UNSUCCESSFUL_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["FAILED", "REJECTED", "PARTIAL"]);
57
363
  function optionValue(args, index, option) {
@@ -360,47 +666,63 @@ async function runCommand(args, dependencies) {
360
666
  ...options.metadata === void 0 ? {} : { metadata: options.metadata },
361
667
  ...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey }
362
668
  };
363
- const submitted = await dependencies.jobs.create(params);
364
- if (!options.wait) {
365
- if (options.json) dependencies.writeStdout(`${JSON.stringify(receiptProjection(submitted))}
366
- `);
367
- else writeHumanReceipt(submitted, dependencies.writeStdout);
368
- return UNSUCCESSFUL_TERMINAL_STATUSES2.has(String(submitted.status).toUpperCase()) ? 6 : 0;
369
- }
370
- const details = await submitted.wait(
371
- options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
669
+ const isLocalSource = !/^(?:https?|gs):\/\//i.test(options.source);
670
+ dependencies.activity.start(
671
+ isLocalSource ? "Uploading local source and creating job\u2026" : "Creating job\u2026"
372
672
  );
373
- if (!options.json) writeHumanDetails(details, dependencies.writeStdout);
374
- if (String(details.status).toUpperCase() !== "COMPLETED") {
375
- if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection2(details))}
673
+ try {
674
+ const submitted = await dependencies.jobs.create(params);
675
+ if (!options.wait) {
676
+ dependencies.activity.stop();
677
+ if (options.json) dependencies.writeStdout(`${JSON.stringify(receiptProjection(submitted))}
376
678
  `);
377
- return 6;
378
- }
379
- if (options.download) {
380
- const url = details.bundle.downloadUrl;
381
- if (!details.bundle.available || !url) {
382
- throw new BundleDownloadError("Completed job does not have an available canonical bundle");
679
+ else writeHumanReceipt(submitted, dependencies.writeStdout);
680
+ return UNSUCCESSFUL_TERMINAL_STATUSES2.has(String(submitted.status).toUpperCase()) ? 6 : 0;
383
681
  }
384
- try {
385
- await dependencies.downloadBundle(url, options.download, {
386
- force: options.force,
387
- expectedSizeBytes: details.bundle.sizeBytes,
388
- expectedSha256: details.bundle.sha256
389
- });
390
- } catch (error) {
391
- if (error instanceof BundleDownloadError) throw error;
392
- throw new BundleDownloadError(`Could not download bundle to ${options.download}`, { cause: error });
682
+ dependencies.activity.update(`Waiting for job ${submitted.id} to complete\u2026`);
683
+ const details = await submitted.wait(
684
+ options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
685
+ );
686
+ if (String(details.status).toUpperCase() !== "COMPLETED") {
687
+ dependencies.activity.stop();
688
+ if (!options.json) writeHumanDetails(details, dependencies.writeStdout);
689
+ if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection2(details))}
690
+ `);
691
+ return 6;
692
+ }
693
+ if (options.download) {
694
+ const url = details.bundle.downloadUrl;
695
+ if (!details.bundle.available || !url) {
696
+ throw new BundleDownloadError("Completed job does not have an available canonical bundle");
697
+ }
698
+ dependencies.activity.update("Downloading and verifying ZIP bundle\u2026");
699
+ try {
700
+ await dependencies.downloadBundle(url, options.download, {
701
+ force: options.force,
702
+ expectedSizeBytes: details.bundle.sizeBytes,
703
+ expectedSha256: details.bundle.sha256
704
+ });
705
+ } catch (error) {
706
+ if (error instanceof BundleDownloadError) throw error;
707
+ throw new BundleDownloadError(`Could not download bundle to ${options.download}`, { cause: error });
708
+ }
393
709
  }
394
- if (!options.json) dependencies.writeStdout(`Downloaded bundle to ${options.download}
710
+ dependencies.activity.stop();
711
+ if (!options.json) {
712
+ writeHumanDetails(details, dependencies.writeStdout);
713
+ if (options.download) dependencies.writeStdout(`Downloaded bundle to ${options.download}
395
714
  `);
396
- }
397
- if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection2(details))}
715
+ }
716
+ if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection2(details))}
398
717
  `);
399
- return 0;
718
+ return 0;
719
+ } finally {
720
+ dependencies.activity.stop();
721
+ }
400
722
  }
401
723
 
402
724
  // src/commands/trigger.ts
403
- import { randomBytes, randomUUID } from "crypto";
725
+ import { randomBytes as randomBytes2, randomUUID } from "crypto";
404
726
  import { readFile } from "fs/promises";
405
727
 
406
728
  // src/trigger/destination.ts
@@ -628,7 +950,7 @@ async function runTriggerCommand(args, dependencies = {}) {
628
950
  fetch: dependencies.fetch ?? globalThis.fetch,
629
951
  now: dependencies.now ?? Date.now,
630
952
  randomUUID: dependencies.randomUUID ?? randomUUID,
631
- randomBytes: dependencies.randomBytes ?? randomBytes,
953
+ randomBytes: dependencies.randomBytes ?? randomBytes2,
632
954
  readFile: dependencies.readFile ?? readFile,
633
955
  writeStdout: dependencies.writeStdout ?? ((text) => process.stdout.write(text))
634
956
  };
@@ -676,8 +998,49 @@ async function runTriggerCommand(args, dependencies = {}) {
676
998
  return 0;
677
999
  }
678
1000
 
1001
+ // src/ui/activity.ts
1002
+ var FRAMES = ["\u28CB", "\u28D9", "\u28F9", "\u28F8", "\u28FC", "\u28F4", "\u28E6", "\u28E7", "\u28C7", "\u28CF"];
1003
+ var CLEAR_LINE = "\r\x1B[2K";
1004
+ function createActivityIndicator(write, enabled) {
1005
+ let timer;
1006
+ let frameIndex = 0;
1007
+ let message = "";
1008
+ let startedAt = 0;
1009
+ const render = () => {
1010
+ if (!timer && startedAt === 0) return;
1011
+ const elapsedSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
1012
+ const elapsed = elapsedSeconds > 0 ? ` (${elapsedSeconds}s)` : "";
1013
+ write(`${CLEAR_LINE}${FRAMES[frameIndex % FRAMES.length]} ${message}${elapsed}`);
1014
+ frameIndex += 1;
1015
+ };
1016
+ return {
1017
+ start(nextMessage) {
1018
+ if (!enabled) return;
1019
+ if (timer) clearInterval(timer);
1020
+ message = nextMessage;
1021
+ startedAt = Date.now();
1022
+ frameIndex = 0;
1023
+ render();
1024
+ timer = setInterval(render, 80);
1025
+ timer.unref?.();
1026
+ },
1027
+ update(nextMessage) {
1028
+ if (!enabled || startedAt === 0) return;
1029
+ message = nextMessage;
1030
+ render();
1031
+ },
1032
+ stop() {
1033
+ if (!enabled || startedAt === 0) return;
1034
+ if (timer) clearInterval(timer);
1035
+ timer = void 0;
1036
+ startedAt = 0;
1037
+ write(CLEAR_LINE);
1038
+ }
1039
+ };
1040
+ }
1041
+
679
1042
  // src/cli.ts
680
- var VERSION = "0.1.0";
1043
+ var VERSION = "0.2.1";
681
1044
  var HELP = `MediaRuntime CLI
682
1045
 
683
1046
  Usage:
@@ -685,6 +1048,9 @@ Usage:
685
1048
  mediaruntime jobs list [--status <status>] [--limit <n>] [--cursor <cursor>]
686
1049
  mediaruntime jobs get <job_id> [--download <bundle.zip>] [--force]
687
1050
  mediaruntime trigger <job.completed|job.failed|job.rejected> --to <local-url>
1051
+ mediaruntime login [--no-browser]
1052
+ mediaruntime auth status
1053
+ mediaruntime logout [--local-only]
688
1054
 
689
1055
  Global options:
690
1056
  --base-url <url> Developer override; normally leave unset
@@ -694,7 +1060,7 @@ Global options:
694
1060
  Run/jobs options:
695
1061
  --json Machine-readable, URL-redacted output
696
1062
 
697
- Authentication uses MEDIARUNTIME_API_KEY.
1063
+ Authentication uses a secure login or MEDIARUNTIME_API_KEY.
698
1064
  `;
699
1065
  function isFileExistsError(error) {
700
1066
  return error instanceof Error && "code" in error && error.code === "EEXIST";
@@ -728,7 +1094,7 @@ async function downloadBundleAtomically(url, destination, options) {
728
1094
  throw new BundleDownloadError(`Bundle download failed with HTTP ${response.status}`);
729
1095
  }
730
1096
  if (!response.body) throw new BundleDownloadError("Bundle download returned an empty response body");
731
- const hash = createHash("sha256");
1097
+ const hash = createHash2("sha256");
732
1098
  let sizeBytes = 0;
733
1099
  const integrity = new Transform({
734
1100
  transform(chunk, _encoding, callback) {
@@ -832,6 +1198,7 @@ async function executeCli(argv, dependencies = {}) {
832
1198
  const writeStdout = dependencies.writeStdout ?? ((text) => process.stdout.write(text));
833
1199
  const writeStderr = dependencies.writeStderr ?? ((text) => process.stderr.write(text));
834
1200
  const json = argv.includes("--json");
1201
+ const isStderrTTY = dependencies.isStderrTTY ?? (dependencies.writeStderr === void 0 && process.stderr.isTTY === true);
835
1202
  try {
836
1203
  const global = extractGlobalOptions(argv);
837
1204
  const command = global.args[0];
@@ -849,14 +1216,39 @@ async function executeCli(argv, dependencies = {}) {
849
1216
  }
850
1217
  const configuredBaseUrl = global.baseUrl ?? process.env.MEDIARUNTIME_API_URL;
851
1218
  const baseUrl = configuredBaseUrl ? normalizeBaseUrl(configuredBaseUrl) : void 0;
852
- if (!dependencies.createClient && !process.env.MEDIARUNTIME_API_KEY?.trim()) {
853
- throw new UsageError("Set MEDIARUNTIME_API_KEY before using run or jobs");
1219
+ const authBaseUrl = baseUrl ?? PRODUCTION_ORIGIN;
1220
+ const authDependencies = {
1221
+ ...dependencies.credentialStore ? { credentialStore: dependencies.credentialStore } : {},
1222
+ ...dependencies.openBrowser ? { openBrowser: dependencies.openBrowser } : {},
1223
+ ...dependencies.sleep ? { sleep: dependencies.sleep } : {},
1224
+ ...dependencies.now ? { now: dependencies.now } : {},
1225
+ ...dependencies.randomVerifier ? { randomVerifier: dependencies.randomVerifier } : {},
1226
+ writeStdout
1227
+ };
1228
+ if (command === "login") return await runLoginCommand(global.args.slice(1), authBaseUrl, authDependencies);
1229
+ if (command === "auth") return await runAuthCommand(global.args.slice(1), authBaseUrl, authDependencies);
1230
+ if (command === "logout") return await runLogoutCommand(global.args.slice(1), authBaseUrl, authDependencies);
1231
+ let apiKey = process.env.MEDIARUNTIME_API_KEY?.trim();
1232
+ if (!apiKey && (!dependencies.createClient || dependencies.credentialStore)) {
1233
+ apiKey = (await resolveCredential(authBaseUrl, authDependencies))?.apiKey;
1234
+ }
1235
+ if (!dependencies.createClient && !apiKey) {
1236
+ throw new UsageError("Run mediaruntime login or set MEDIARUNTIME_API_KEY before using run or jobs");
854
1237
  }
855
1238
  const client = dependencies.createClient?.({
856
- ...baseUrl === void 0 ? {} : { baseUrl }
857
- }) ?? new MediaRuntime({ ...baseUrl === void 0 ? {} : { baseUrl } });
1239
+ ...baseUrl === void 0 ? {} : { baseUrl },
1240
+ ...apiKey === void 0 ? {} : { apiKey }
1241
+ }) ?? new MediaRuntime({
1242
+ ...baseUrl === void 0 ? {} : { baseUrl },
1243
+ ...apiKey === void 0 ? {} : { apiKey }
1244
+ });
858
1245
  const downloadBundle = dependencies.downloadBundle ?? downloadBundleAtomically;
859
- const commandDependencies = { jobs: client.jobs, writeStdout, downloadBundle };
1246
+ const commandDependencies = {
1247
+ jobs: client.jobs,
1248
+ writeStdout,
1249
+ downloadBundle,
1250
+ activity: createActivityIndicator(writeStderr, isStderrTTY && !json)
1251
+ };
860
1252
  if (command === "run") return await runCommand(global.args.slice(1), commandDependencies);
861
1253
  if (command === "jobs") {
862
1254
  return await runJobsCommand(global.args.slice(1), commandDependencies);
@@ -889,6 +1281,11 @@ export {
889
1281
  downloadBundleAtomically,
890
1282
  executeCli,
891
1283
  parseTriggerArguments,
1284
+ resolveCredential,
1285
+ runAuthCommand,
1286
+ runAuthStatusCommand,
1287
+ runLoginCommand,
1288
+ runLogoutCommand,
892
1289
  runTriggerCommand,
893
1290
  signSyntheticWebhook
894
1291
  };