@mediaruntime/cli 0.1.0 → 0.2.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 — 2026-08-16
4
+
5
+ - Add browser-assisted `login`, `auth status`, and revoking `logout` commands.
6
+ - Store dedicated CLI credentials only in the operating-system credential vault.
7
+ - Keep `MEDIARUNTIME_API_KEY` as the higher-priority permanent automation interface.
8
+ - Bind the one-time exchange with PKCE and redact credentials from all CLI output.
9
+
3
10
  ## 0.1.0 — 2026-08-16
4
11
 
5
12
  - Add `run` with local uploads, frozen output aliases, safe waiting, and atomic canonical
package/README.md CHANGED
@@ -11,14 +11,25 @@ npm install --global @mediaruntime/cli
11
11
  mediaruntime --version
12
12
  ```
13
13
 
14
- Node.js 20 or newer is required. Authenticated commands read the API key from the
15
- environment; the hosted API URL is built in.
14
+ Node.js 20 or newer is required. For interactive use, authorize through the browser once:
15
+
16
+ ```bash
17
+ mediaruntime login
18
+ mediaruntime auth status
19
+ ```
20
+
21
+ The CLI stores its dedicated revocable key in the operating-system credential vault; it
22
+ never writes the key to a plaintext configuration file. Use `mediaruntime logout` to
23
+ revoke and remove it.
24
+
25
+ For CI, servers, containers, and deliberate account overrides, continue setting:
16
26
 
17
27
  ```bash
18
28
  export MEDIARUNTIME_API_KEY="sk_..."
19
29
  ```
20
30
 
21
- Do not pass credentials as command-line arguments. `--base-url` and
31
+ An explicit `MEDIARUNTIME_API_KEY` permanently remains supported and takes precedence
32
+ over the stored login. Do not pass credentials as command-line arguments. `--base-url` and
22
33
  `MEDIARUNTIME_API_URL` are development/staging overrides, not normal client setup.
23
34
 
24
35
  ## Run a job
@@ -104,6 +115,7 @@ The full implementation contract is in [docs/CLI_SDD.md](docs/CLI_SDD.md).
104
115
 
105
116
  ## Scope
106
117
 
107
- The first release intentionally does not implement `listen`, `login`, credential storage,
108
- or a webhook relay. Production completion continues to use the account destination under
109
- Account → Webhooks. The ZIP remains the canonical completed-job result.
118
+ The CLI does not implement `listen` or a webhook relay. Production completion continues
119
+ to use the account destination under Account → Webhooks. The ZIP remains the canonical
120
+ completed-job result. See [browser authentication](docs/AUTH.md) for the complete login,
121
+ credential precedence, and security contract.
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,18 @@
1
1
  import { ListJobsParams, JobPage, JobDetails, CreateJobParams, JobReceiptData, WaitForJobOptions } from '@mediaruntime/node';
2
2
 
3
+ interface StoredCredential {
4
+ apiKey: string;
5
+ accountId: string;
6
+ keyId: string;
7
+ baseUrl: string;
8
+ createdAt: string;
9
+ }
10
+ interface CredentialStore {
11
+ get(baseUrl: string): Promise<StoredCredential | null>;
12
+ set(credential: StoredCredential): Promise<void>;
13
+ delete(baseUrl: string): Promise<void>;
14
+ }
15
+
3
16
  interface JobsReadClient {
4
17
  list(params?: ListJobsParams): Promise<JobPage>;
5
18
  get(jobId: string): Promise<JobDetails>;
@@ -53,6 +66,24 @@ declare function signSyntheticWebhook(payload: Readonly<Record<string, unknown>>
53
66
  secret: string;
54
67
  }): SignedWebhook;
55
68
 
69
+ interface AuthCommandDependencies {
70
+ credentialStore?: CredentialStore;
71
+ openBrowser?: (url: string) => Promise<void>;
72
+ sleep?: (milliseconds: number) => Promise<void>;
73
+ now?: () => number;
74
+ randomVerifier?: () => string;
75
+ writeStdout?: (text: string) => void;
76
+ }
77
+ declare function resolveCredential(baseUrl: string, dependencies?: AuthCommandDependencies): Promise<{
78
+ apiKey: string;
79
+ source: "environment" | "keychain";
80
+ stored?: StoredCredential;
81
+ } | null>;
82
+ declare function runLoginCommand(args: string[], baseUrl: string, dependencies?: AuthCommandDependencies): Promise<number>;
83
+ declare function runAuthStatusCommand(args: string[], baseUrl: string, dependencies?: AuthCommandDependencies): Promise<number>;
84
+ declare function runLogoutCommand(args: string[], baseUrl: string, dependencies?: AuthCommandDependencies): Promise<number>;
85
+ declare function runAuthCommand(args: string[], baseUrl: string, dependencies?: AuthCommandDependencies): Promise<number>;
86
+
56
87
  type CliJobsClient = RunJobsClient & JobsReadClient;
57
88
  interface CliClient {
58
89
  jobs: CliJobsClient;
@@ -60,6 +91,7 @@ interface CliClient {
60
91
  interface CliDependencies {
61
92
  createClient?(options: {
62
93
  baseUrl?: string;
94
+ apiKey?: string;
63
95
  }): CliClient;
64
96
  downloadBundle?(url: string, destination: string, options: {
65
97
  force: boolean;
@@ -68,6 +100,11 @@ interface CliDependencies {
68
100
  }): Promise<void>;
69
101
  writeStdout?(text: string): void;
70
102
  writeStderr?(text: string): void;
103
+ credentialStore?: CredentialStore;
104
+ openBrowser?(url: string): Promise<void>;
105
+ sleep?(milliseconds: number): Promise<void>;
106
+ now?(): number;
107
+ randomVerifier?(): string;
71
108
  }
72
109
  declare function downloadBundleAtomically(url: string, destination: string, options: {
73
110
  force: boolean;
@@ -76,4 +113,4 @@ declare function downloadBundleAtomically(url: string, destination: string, opti
76
113
  }): Promise<void>;
77
114
  declare function executeCli(argv: string[], dependencies?: CliDependencies): Promise<number>;
78
115
 
79
- export { type CliClient, type CliDependencies, createSyntheticTerminalEvent, downloadBundleAtomically, executeCli, parseTriggerArguments, runTriggerCommand, signSyntheticWebhook };
116
+ export { type CliClient, type CliDependencies, createSyntheticTerminalEvent, downloadBundleAtomically, executeCli, parseTriggerArguments, resolveCredential, runAuthCommand, runAuthStatusCommand, runLoginCommand, runLogoutCommand, runTriggerCommand, signSyntheticWebhook };
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) {
@@ -400,7 +706,7 @@ async function runCommand(args, dependencies) {
400
706
  }
401
707
 
402
708
  // src/commands/trigger.ts
403
- import { randomBytes, randomUUID } from "crypto";
709
+ import { randomBytes as randomBytes2, randomUUID } from "crypto";
404
710
  import { readFile } from "fs/promises";
405
711
 
406
712
  // src/trigger/destination.ts
@@ -628,7 +934,7 @@ async function runTriggerCommand(args, dependencies = {}) {
628
934
  fetch: dependencies.fetch ?? globalThis.fetch,
629
935
  now: dependencies.now ?? Date.now,
630
936
  randomUUID: dependencies.randomUUID ?? randomUUID,
631
- randomBytes: dependencies.randomBytes ?? randomBytes,
937
+ randomBytes: dependencies.randomBytes ?? randomBytes2,
632
938
  readFile: dependencies.readFile ?? readFile,
633
939
  writeStdout: dependencies.writeStdout ?? ((text) => process.stdout.write(text))
634
940
  };
@@ -677,7 +983,7 @@ async function runTriggerCommand(args, dependencies = {}) {
677
983
  }
678
984
 
679
985
  // src/cli.ts
680
- var VERSION = "0.1.0";
986
+ var VERSION = "0.2.0";
681
987
  var HELP = `MediaRuntime CLI
682
988
 
683
989
  Usage:
@@ -685,6 +991,9 @@ Usage:
685
991
  mediaruntime jobs list [--status <status>] [--limit <n>] [--cursor <cursor>]
686
992
  mediaruntime jobs get <job_id> [--download <bundle.zip>] [--force]
687
993
  mediaruntime trigger <job.completed|job.failed|job.rejected> --to <local-url>
994
+ mediaruntime login [--no-browser]
995
+ mediaruntime auth status
996
+ mediaruntime logout [--local-only]
688
997
 
689
998
  Global options:
690
999
  --base-url <url> Developer override; normally leave unset
@@ -694,7 +1003,7 @@ Global options:
694
1003
  Run/jobs options:
695
1004
  --json Machine-readable, URL-redacted output
696
1005
 
697
- Authentication uses MEDIARUNTIME_API_KEY.
1006
+ Authentication uses a secure login or MEDIARUNTIME_API_KEY.
698
1007
  `;
699
1008
  function isFileExistsError(error) {
700
1009
  return error instanceof Error && "code" in error && error.code === "EEXIST";
@@ -728,7 +1037,7 @@ async function downloadBundleAtomically(url, destination, options) {
728
1037
  throw new BundleDownloadError(`Bundle download failed with HTTP ${response.status}`);
729
1038
  }
730
1039
  if (!response.body) throw new BundleDownloadError("Bundle download returned an empty response body");
731
- const hash = createHash("sha256");
1040
+ const hash = createHash2("sha256");
732
1041
  let sizeBytes = 0;
733
1042
  const integrity = new Transform({
734
1043
  transform(chunk, _encoding, callback) {
@@ -849,12 +1158,32 @@ async function executeCli(argv, dependencies = {}) {
849
1158
  }
850
1159
  const configuredBaseUrl = global.baseUrl ?? process.env.MEDIARUNTIME_API_URL;
851
1160
  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");
1161
+ const authBaseUrl = baseUrl ?? PRODUCTION_ORIGIN;
1162
+ const authDependencies = {
1163
+ ...dependencies.credentialStore ? { credentialStore: dependencies.credentialStore } : {},
1164
+ ...dependencies.openBrowser ? { openBrowser: dependencies.openBrowser } : {},
1165
+ ...dependencies.sleep ? { sleep: dependencies.sleep } : {},
1166
+ ...dependencies.now ? { now: dependencies.now } : {},
1167
+ ...dependencies.randomVerifier ? { randomVerifier: dependencies.randomVerifier } : {},
1168
+ writeStdout
1169
+ };
1170
+ if (command === "login") return await runLoginCommand(global.args.slice(1), authBaseUrl, authDependencies);
1171
+ if (command === "auth") return await runAuthCommand(global.args.slice(1), authBaseUrl, authDependencies);
1172
+ if (command === "logout") return await runLogoutCommand(global.args.slice(1), authBaseUrl, authDependencies);
1173
+ let apiKey = process.env.MEDIARUNTIME_API_KEY?.trim();
1174
+ if (!apiKey && (!dependencies.createClient || dependencies.credentialStore)) {
1175
+ apiKey = (await resolveCredential(authBaseUrl, authDependencies))?.apiKey;
1176
+ }
1177
+ if (!dependencies.createClient && !apiKey) {
1178
+ throw new UsageError("Run mediaruntime login or set MEDIARUNTIME_API_KEY before using run or jobs");
854
1179
  }
855
1180
  const client = dependencies.createClient?.({
856
- ...baseUrl === void 0 ? {} : { baseUrl }
857
- }) ?? new MediaRuntime({ ...baseUrl === void 0 ? {} : { baseUrl } });
1181
+ ...baseUrl === void 0 ? {} : { baseUrl },
1182
+ ...apiKey === void 0 ? {} : { apiKey }
1183
+ }) ?? new MediaRuntime({
1184
+ ...baseUrl === void 0 ? {} : { baseUrl },
1185
+ ...apiKey === void 0 ? {} : { apiKey }
1186
+ });
858
1187
  const downloadBundle = dependencies.downloadBundle ?? downloadBundleAtomically;
859
1188
  const commandDependencies = { jobs: client.jobs, writeStdout, downloadBundle };
860
1189
  if (command === "run") return await runCommand(global.args.slice(1), commandDependencies);
@@ -889,6 +1218,11 @@ export {
889
1218
  downloadBundleAtomically,
890
1219
  executeCli,
891
1220
  parseTriggerArguments,
1221
+ resolveCredential,
1222
+ runAuthCommand,
1223
+ runAuthStatusCommand,
1224
+ runLoginCommand,
1225
+ runLogoutCommand,
892
1226
  runTriggerCommand,
893
1227
  signSyntheticWebhook
894
1228
  };
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/errors.ts","../src/commands/jobs.ts","../src/commands/run.ts","../src/commands/trigger.ts","../src/trigger/destination.ts","../src/trigger/event.ts","../src/trigger/signature.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { constants, realpathSync } from \"node:fs\";\nimport {\n access,\n link,\n rename,\n rm,\n} from \"node:fs/promises\";\nimport { createWriteStream } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { Readable, Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n JobWaitTimeoutError,\n MediaRuntime,\n MediaRuntimeApiError,\n MediaRuntimeConnectionError,\n} from \"@mediaruntime/node\";\nimport {\n runJobsCommand,\n type JobsReadClient,\n} from \"./commands/jobs.js\";\nimport {\n runCommand,\n type RunJobsClient,\n} from \"./commands/run.js\";\nimport { runTriggerCommand } from \"./commands/trigger.js\";\nimport { BundleDownloadError, CliError, UsageError } from \"./errors.js\";\nimport { isLoopbackDestination } from \"./trigger/destination.js\";\n\nexport { parseTriggerArguments, runTriggerCommand } from \"./commands/trigger.js\";\nexport { createSyntheticTerminalEvent } from \"./trigger/event.js\";\nexport { signSyntheticWebhook } from \"./trigger/signature.js\";\n\nconst VERSION = \"0.1.0\";\nconst HELP = `MediaRuntime CLI\n\nUsage:\n mediaruntime run <source> --output <alias> [--output <alias>] [--wait]\n mediaruntime jobs list [--status <status>] [--limit <n>] [--cursor <cursor>]\n mediaruntime jobs get <job_id> [--download <bundle.zip>] [--force]\n mediaruntime trigger <job.completed|job.failed|job.rejected> --to <local-url>\n\nGlobal options:\n --base-url <url> Developer override; normally leave unset\n --help Show help\n --version Show version\n\nRun/jobs options:\n --json Machine-readable, URL-redacted output\n\nAuthentication uses MEDIARUNTIME_API_KEY.\n`;\n\ntype CliJobsClient = RunJobsClient & JobsReadClient;\n\nexport interface CliClient {\n jobs: CliJobsClient;\n}\n\nexport interface CliDependencies {\n createClient?(options: { baseUrl?: string }): CliClient;\n downloadBundle?(\n url: string,\n destination: string,\n options: {\n force: boolean;\n expectedSizeBytes?: number | null;\n expectedSha256?: string | null;\n },\n ): Promise<void>;\n writeStdout?(text: string): void;\n writeStderr?(text: string): void;\n}\n\nfunction isFileExistsError(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && error.code === \"EEXIST\";\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function downloadBundleAtomically(\n url: string,\n destination: string,\n options: {\n force: boolean;\n expectedSizeBytes?: number | null;\n expectedSha256?: string | null;\n },\n): Promise<void> {\n const target = resolve(destination);\n if (!options.force && await exists(target)) {\n throw new BundleDownloadError(`Refusing to overwrite existing file: ${destination}`);\n }\n const temporary = resolve(\n dirname(target),\n `.${basename(target)}.${randomUUID()}.part`,\n );\n\n try {\n let response: Response;\n try {\n response = await fetch(url);\n } catch (error) {\n throw new BundleDownloadError(\"Bundle download failed before receiving a response\", { cause: error });\n }\n if (!response.ok) {\n await response.body?.cancel();\n throw new BundleDownloadError(`Bundle download failed with HTTP ${response.status}`);\n }\n if (!response.body) throw new BundleDownloadError(\"Bundle download returned an empty response body\");\n const hash = createHash(\"sha256\");\n let sizeBytes = 0;\n const integrity = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n sizeBytes += chunk.length;\n hash.update(chunk);\n callback(null, chunk);\n },\n });\n await pipeline(\n Readable.fromWeb(response.body as unknown as NodeReadableStream<Uint8Array>),\n integrity,\n createWriteStream(temporary, { flags: \"wx\", mode: 0o600 }),\n );\n const sha256 = hash.digest(\"hex\");\n if (options.expectedSizeBytes !== undefined && options.expectedSizeBytes !== null &&\n sizeBytes !== options.expectedSizeBytes) {\n throw new BundleDownloadError(\n `Bundle size mismatch: expected ${options.expectedSizeBytes} bytes, received ${sizeBytes}`,\n );\n }\n if (options.expectedSha256 && sha256 !== options.expectedSha256.toLowerCase()) {\n throw new BundleDownloadError(\"Bundle SHA-256 mismatch\");\n }\n\n if (options.force) {\n await rename(temporary, target);\n } else {\n try {\n // A hard link publishes the completed same-filesystem temporary file atomically\n // and fails rather than replacing a destination created during the download.\n await link(temporary, target);\n } catch (error) {\n if (isFileExistsError(error)) {\n throw new BundleDownloadError(`Refusing to overwrite existing file: ${destination}`);\n }\n throw error;\n }\n await rm(temporary);\n }\n } catch (error) {\n await rm(temporary, { force: true });\n if (error instanceof BundleDownloadError) throw error;\n throw new BundleDownloadError(`Could not write bundle to ${destination}`, { cause: error });\n }\n}\n\nfunction normalizeBaseUrl(raw: string): string {\n let value: URL;\n try {\n value = new URL(raw);\n } catch {\n throw new UsageError(\"--base-url must be an absolute HTTP(S) origin\");\n }\n if (value.username || value.password || value.search || value.hash || value.pathname !== \"/\") {\n throw new UsageError(\"--base-url must be an origin without credentials, path, query, or fragment\");\n }\n if (value.protocol !== \"https:\" && !(value.protocol === \"http:\" && isLoopbackDestination(value))) {\n throw new UsageError(\"--base-url must use HTTPS unless it points to loopback\");\n }\n return value.origin;\n}\n\nfunction extractGlobalOptions(argv: string[]): { baseUrl?: string; args: string[] } {\n const args = [...argv];\n let baseUrl: string | undefined;\n while (args[0] === \"--base-url\") {\n const value = args[1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(\"--base-url requires a value\");\n baseUrl = normalizeBaseUrl(value);\n args.splice(0, 2);\n }\n return { ...(baseUrl === undefined ? {} : { baseUrl }), args };\n}\n\nfunction safeApiError(error: MediaRuntimeApiError): Record<string, unknown> {\n return {\n name: error.name,\n message: error.message,\n code: error.code,\n status: error.status,\n retryable: error.retryable,\n requestId: error.requestId ?? null,\n field: error.field ?? null,\n };\n}\n\nfunction exitCodeFor(error: unknown): number {\n if (error instanceof CliError) return error.exitCode;\n if (error instanceof JobWaitTimeoutError) return 7;\n if (error instanceof MediaRuntimeConnectionError) return 5;\n if (error instanceof MediaRuntimeApiError) {\n if ([401, 402, 403].includes(error.status)) return 3;\n if (error.status >= 500) return 5;\n return 4;\n }\n if (error instanceof DOMException && error.name === \"AbortError\") return 130;\n if (error instanceof TypeError && /fetch|network|socket|connect/i.test(error.message)) return 5;\n return 1;\n}\n\nfunction safeError(error: unknown, exitCode: number): Record<string, unknown> {\n if (error instanceof MediaRuntimeApiError) return { ...safeApiError(error), exitCode };\n return {\n code: error instanceof CliError ? error.code : \"cli_error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n exitCode,\n };\n}\n\nexport async function executeCli(\n argv: string[],\n dependencies: CliDependencies = {},\n): Promise<number> {\n const writeStdout = dependencies.writeStdout ?? ((text: string) => process.stdout.write(text));\n const writeStderr = dependencies.writeStderr ?? ((text: string) => process.stderr.write(text));\n const json = argv.includes(\"--json\");\n try {\n const global = extractGlobalOptions(argv);\n const command = global.args[0];\n if (!command || command === \"--help\" || command === \"-h\") {\n writeStdout(HELP);\n return 0;\n }\n if (command === \"--version\" || command === \"-v\") {\n writeStdout(`${VERSION}\\n`);\n return 0;\n }\n if (command === \"trigger\") {\n return await runTriggerCommand(global.args.slice(1), { writeStdout });\n }\n\n const configuredBaseUrl = global.baseUrl ?? process.env.MEDIARUNTIME_API_URL;\n const baseUrl = configuredBaseUrl ? normalizeBaseUrl(configuredBaseUrl) : undefined;\n if (!dependencies.createClient && !process.env.MEDIARUNTIME_API_KEY?.trim()) {\n throw new UsageError(\"Set MEDIARUNTIME_API_KEY before using run or jobs\");\n }\n const client = dependencies.createClient?.({\n ...(baseUrl === undefined ? {} : { baseUrl }),\n }) ?? new MediaRuntime({ ...(baseUrl === undefined ? {} : { baseUrl }) });\n const downloadBundle = dependencies.downloadBundle ?? downloadBundleAtomically;\n const commandDependencies = { jobs: client.jobs, writeStdout, downloadBundle };\n if (command === \"run\") return await runCommand(global.args.slice(1), commandDependencies);\n if (command === \"jobs\") {\n return await runJobsCommand(global.args.slice(1), commandDependencies);\n }\n throw new UsageError(`Unknown command: ${command}`);\n } catch (error) {\n const exitCode = exitCodeFor(error);\n if (json) {\n writeStderr(`${JSON.stringify({ error: safeError(error, exitCode) })}\\n`);\n } else {\n const requestId = error instanceof MediaRuntimeApiError && error.requestId\n ? ` (request ${error.requestId})`\n : \"\";\n writeStderr(`Error: ${error instanceof Error ? error.message : \"Unknown error\"}${requestId}\\n`);\n }\n return exitCode;\n }\n}\n\nlet isEntrypoint = false;\nif (process.argv[1] !== undefined) {\n try {\n // npm exposes binaries through a symlink. Resolve both sides so the installed command\n // executes while importing dist/cli.js in tests remains side-effect free.\n isEntrypoint = realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n isEntrypoint = false;\n }\n}\nif (isEntrypoint) process.exitCode = await executeCli(process.argv.slice(2));\n","export class CliError extends Error {\n readonly code: string;\n readonly exitCode: number;\n\n constructor(code: string, message: string, exitCode: number, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"CliError\";\n this.code = code;\n this.exitCode = exitCode;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class UsageError extends CliError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(\"usage_error\", message, 2, options);\n this.name = \"UsageError\";\n }\n}\n\nexport class TriggerDeliveryError extends CliError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(\"trigger_delivery_error\", message, 8, options);\n this.name = \"TriggerDeliveryError\";\n }\n}\n\nexport class BundleDownloadError extends CliError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(\"bundle_download_error\", message, 9, options);\n this.name = \"BundleDownloadError\";\n }\n}\n","import type { JobDetails, JobPage, JobStatus, ListJobsParams } from \"@mediaruntime/node\";\nimport { BundleDownloadError, UsageError } from \"../errors.js\";\n\nconst UNSUCCESSFUL_TERMINAL_STATUSES = new Set([\"FAILED\", \"REJECTED\", \"PARTIAL\"]);\n\nexport interface JobsReadClient {\n list(params?: ListJobsParams): Promise<JobPage>;\n get(jobId: string): Promise<JobDetails>;\n}\n\nexport interface JobsCommandDependencies {\n jobs: JobsReadClient;\n writeStdout(text: string): void;\n downloadBundle(\n url: string,\n destination: string,\n options: { force: boolean; expectedSizeBytes?: number | null; expectedSha256?: string | null },\n ): Promise<void>;\n}\n\nfunction optionValue(args: string[], index: number, option: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(`${option} requires a value`);\n return value;\n}\n\nfunction detailsProjection(job: JobDetails): Record<string, unknown> {\n return {\n id: job.id,\n status: job.status,\n tier: job.tier,\n usage: job.usage,\n billing: job.billing,\n bundle: {\n available: job.bundle.available,\n expiresAt: job.bundle.expiresAt,\n sizeBytes: job.bundle.sizeBytes,\n sha256: job.bundle.sha256,\n retentionDays: job.bundle.retentionDays,\n },\n media: job.media,\n metadata: job.metadata,\n error: job.error,\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n startedAt: job.startedAt,\n completedAt: job.completedAt,\n };\n}\n\nfunction humanDetails(job: JobDetails): string {\n const lines = [\n `Job: ${job.id}`,\n `Status: ${job.status}`,\n `Tier: ${job.tier.billed ?? job.tier.effective ?? job.tier.requested ?? \"-\"}`,\n `Bundle: ${job.bundle.available ? \"available\" : \"unavailable\"}`,\n ];\n if (job.bundle.sizeBytes !== null) lines.push(`Bundle size: ${job.bundle.sizeBytes} bytes`);\n if (job.error) lines.push(`Error: ${job.error}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction cell(value: unknown): string {\n if (value === null || value === undefined || value === \"\") return \"-\";\n return String(value);\n}\n\nfunction humanList(page: JobPage): string {\n const rows = page.jobs.map((job) => [\n job.id,\n job.status,\n cell(job.tierBilled),\n cell(job.unitsTotal),\n job.bundleAvailable ? \"yes\" : \"no\",\n cell(job.updatedAt),\n ]);\n const headings = [\"ID\", \"STATUS\", \"TIER\", \"UNITS\", \"BUNDLE\", \"UPDATED\"];\n const widths = headings.map((heading, index) =>\n Math.max(heading.length, ...rows.map((row) => row[index]?.length ?? 0)),\n );\n const render = (row: string[]): string =>\n row.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(\" \").trimEnd();\n const lines = [render(headings), render(widths.map((width) => \"-\".repeat(width))), ...rows.map(render)];\n if (page.nextCursor) lines.push(`Next cursor: ${page.nextCursor}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nasync function listCommand(args: string[], dependencies: JobsCommandDependencies): Promise<number> {\n let status: string | undefined;\n let limit: number | undefined;\n let cursor: string | undefined;\n let json = false;\n for (let index = 0; index < args.length; index += 1) {\n const argument = args[index];\n if (argument === \"--status\") {\n status = optionValue(args, index, argument).toUpperCase();\n index += 1;\n } else if (argument === \"--limit\") {\n const value = Number(optionValue(args, index, argument));\n if (!Number.isSafeInteger(value) || value < 1 || value > 100) {\n throw new UsageError(\"--limit must be an integer between 1 and 100\");\n }\n limit = value;\n index += 1;\n } else if (argument === \"--cursor\") {\n cursor = optionValue(args, index, argument);\n index += 1;\n } else if (argument === \"--json\") {\n json = true;\n } else {\n throw new UsageError(`Unknown jobs list option: ${argument}`);\n }\n }\n const params: ListJobsParams = {\n ...(status === undefined ? {} : { status: status as JobStatus }),\n ...(limit === undefined ? {} : { limit }),\n ...(cursor === undefined ? {} : { cursor }),\n };\n const page = await dependencies.jobs.list(params);\n dependencies.writeStdout(json ? `${JSON.stringify(page)}\\n` : humanList(page));\n return 0;\n}\n\nasync function getCommand(args: string[], dependencies: JobsCommandDependencies): Promise<number> {\n let jobId: string | undefined;\n let download: string | undefined;\n let force = false;\n let json = false;\n for (let index = 0; index < args.length; index += 1) {\n const argument = args[index];\n if (argument === \"--download\") {\n download = optionValue(args, index, argument);\n index += 1;\n } else if (argument === \"--force\") {\n force = true;\n } else if (argument === \"--json\") {\n json = true;\n } else if (argument?.startsWith(\"-\")) {\n throw new UsageError(`Unknown jobs get option: ${argument}`);\n } else if (!jobId) {\n jobId = argument;\n } else {\n throw new UsageError(\"jobs get accepts exactly one job ID\");\n }\n }\n if (!jobId) throw new UsageError(\"Usage: mediaruntime jobs get <job_id>\");\n if (force && !download) throw new UsageError(\"--force requires --download\");\n const job = await dependencies.jobs.get(jobId);\n if (!json) dependencies.writeStdout(humanDetails(job));\n\n if (UNSUCCESSFUL_TERMINAL_STATUSES.has(String(job.status).toUpperCase())) {\n if (json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(job))}\\n`);\n return 6;\n }\n if (download) {\n if (String(job.status).toUpperCase() !== \"COMPLETED\" ||\n !job.bundle.available || !job.bundle.downloadUrl) {\n throw new BundleDownloadError(\"Bundle download requires a COMPLETED job with an available canonical bundle\");\n }\n try {\n await dependencies.downloadBundle(job.bundle.downloadUrl, download, {\n force,\n expectedSizeBytes: job.bundle.sizeBytes,\n expectedSha256: job.bundle.sha256,\n });\n } catch (error) {\n if (error instanceof BundleDownloadError) throw error;\n throw new BundleDownloadError(`Could not download bundle to ${download}`, { cause: error });\n }\n if (!json) dependencies.writeStdout(`Downloaded bundle to ${download}\\n`);\n }\n if (json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(job))}\\n`);\n return 0;\n}\n\nexport async function runJobsCommand(\n args: string[],\n dependencies: JobsCommandDependencies,\n): Promise<number> {\n const subcommand = args[0];\n if (subcommand === \"list\") return listCommand(args.slice(1), dependencies);\n if (subcommand === \"get\") return getCommand(args.slice(1), dependencies);\n throw new UsageError(\"Usage: mediaruntime jobs <list|get>\");\n}\n","import type {\n CreateJobParams,\n JobDetails,\n JobOutput,\n JobReceiptData,\n Metadata,\n OutputAlias,\n WaitForJobOptions,\n} from \"@mediaruntime/node\";\nimport { BundleDownloadError, UsageError } from \"../errors.js\";\n\nconst UNSUCCESSFUL_TERMINAL_STATUSES = new Set([\"FAILED\", \"REJECTED\", \"PARTIAL\"]);\nconst OUTPUT_ALIASES = new Set([\n \"video.web\",\n \"video.streaming\",\n \"video.social\",\n \"audio.web\",\n \"audio.transcription\",\n \"image.web\",\n]);\n\ninterface SubmittedJob extends JobReceiptData {\n wait(options?: WaitForJobOptions): Promise<JobDetails>;\n}\n\nexport interface RunJobsClient {\n create(params: CreateJobParams): Promise<SubmittedJob>;\n}\n\nexport interface RunCommandDependencies {\n jobs: RunJobsClient;\n writeStdout(text: string): void;\n downloadBundle(\n url: string,\n destination: string,\n options: { force: boolean; expectedSizeBytes?: number | null; expectedSha256?: string | null },\n ): Promise<void>;\n}\n\ninterface RunOptions {\n source: string;\n outputs: string[];\n metadata?: Metadata;\n idempotencyKey?: string;\n wait: boolean;\n timeoutMs?: number;\n download?: string;\n force: boolean;\n json: boolean;\n}\n\nfunction optionValue(args: string[], index: number, option: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(`${option} requires a value`);\n return value;\n}\n\nfunction parseMetadata(value: string): Metadata {\n let parsed: unknown;\n try {\n parsed = JSON.parse(value) as unknown;\n } catch {\n throw new UsageError(\"--metadata must be a valid JSON object\");\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new UsageError(\"--metadata must be a JSON object\");\n }\n return parsed as Metadata;\n}\n\nfunction parseRunOptions(args: string[]): RunOptions {\n let source: string | undefined;\n const outputs: string[] = [];\n let metadata: Metadata | undefined;\n let idempotencyKey: string | undefined;\n let wait = false;\n let timeoutMs: number | undefined;\n let download: string | undefined;\n let force = false;\n let json = false;\n\n for (let index = 0; index < args.length; index += 1) {\n const argument = args[index];\n if (argument === \"--output\" || argument === \"-o\") {\n outputs.push(optionValue(args, index, argument));\n index += 1;\n } else if (argument === \"--metadata\") {\n metadata = parseMetadata(optionValue(args, index, argument));\n index += 1;\n } else if (argument === \"--idempotency-key\") {\n idempotencyKey = optionValue(args, index, argument);\n index += 1;\n } else if (argument === \"--wait\") {\n wait = true;\n } else if (argument === \"--timeout-ms\") {\n const value = Number(optionValue(args, index, argument));\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new UsageError(\"--timeout-ms must be a positive integer\");\n }\n timeoutMs = value;\n index += 1;\n } else if (argument === \"--download\") {\n download = optionValue(args, index, argument);\n wait = true;\n index += 1;\n } else if (argument === \"--force\") {\n force = true;\n } else if (argument === \"--json\") {\n json = true;\n } else if (argument?.startsWith(\"-\")) {\n throw new UsageError(`Unknown run option: ${argument}`);\n } else if (source === undefined) {\n source = argument;\n } else {\n throw new UsageError(\"run accepts exactly one source\");\n }\n }\n\n if (!source) throw new UsageError(\"Usage: mediaruntime run <source> --output <alias> [--wait]\");\n if (outputs.length === 0) throw new UsageError(\"run requires at least one --output\");\n const unsupported = outputs.find((output) => !OUTPUT_ALIASES.has(output));\n if (unsupported) throw new UsageError(`Unsupported output alias: ${unsupported}`);\n if (force && !download) throw new UsageError(\"--force requires --download\");\n if (timeoutMs !== undefined && !wait) throw new UsageError(\"--timeout-ms requires --wait or --download\");\n return {\n source,\n outputs,\n ...(metadata === undefined ? {} : { metadata }),\n ...(idempotencyKey === undefined ? {} : { idempotencyKey }),\n wait,\n ...(timeoutMs === undefined ? {} : { timeoutMs }),\n ...(download === undefined ? {} : { download }),\n force,\n json,\n };\n}\n\nfunction receiptProjection(job: SubmittedJob): JobReceiptData {\n return {\n id: job.id,\n status: job.status,\n tier: job.tier,\n requiredTier: job.requiredTier,\n outputs: job.outputs,\n message: job.message,\n };\n}\n\nfunction detailsProjection(job: JobDetails): Record<string, unknown> {\n return {\n id: job.id,\n status: job.status,\n tier: job.tier,\n usage: job.usage,\n billing: job.billing,\n bundle: {\n available: job.bundle.available,\n expiresAt: job.bundle.expiresAt,\n sizeBytes: job.bundle.sizeBytes,\n sha256: job.bundle.sha256,\n retentionDays: job.bundle.retentionDays,\n },\n media: job.media,\n metadata: job.metadata,\n error: job.error,\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n startedAt: job.startedAt,\n completedAt: job.completedAt,\n };\n}\n\nfunction writeHumanReceipt(job: SubmittedJob, write: (text: string) => void): void {\n write(`Job: ${job.id}\\nStatus: ${job.status}\\nTier: ${job.tier || \"-\"}\\n`);\n}\n\nfunction writeHumanDetails(job: JobDetails, write: (text: string) => void): void {\n const error = job.error ? `\\nError: ${job.error}` : \"\";\n write(\n `Job: ${job.id}\\nStatus: ${job.status}\\n` +\n `Bundle: ${job.bundle.available ? \"available\" : \"unavailable\"}${error}\\n`,\n );\n}\n\nexport async function runCommand(\n args: string[],\n dependencies: RunCommandDependencies,\n): Promise<number> {\n const options = parseRunOptions(args);\n const params: CreateJobParams = {\n source: options.source,\n outputs: options.outputs as Array<OutputAlias | JobOutput>,\n ...(options.metadata === undefined ? {} : { metadata: options.metadata }),\n ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),\n };\n const submitted = await dependencies.jobs.create(params);\n\n if (!options.wait) {\n if (options.json) dependencies.writeStdout(`${JSON.stringify(receiptProjection(submitted))}\\n`);\n else writeHumanReceipt(submitted, dependencies.writeStdout);\n return UNSUCCESSFUL_TERMINAL_STATUSES.has(String(submitted.status).toUpperCase()) ? 6 : 0;\n }\n\n const details = await submitted.wait(\n options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs },\n );\n if (!options.json) writeHumanDetails(details, dependencies.writeStdout);\n if (String(details.status).toUpperCase() !== \"COMPLETED\") {\n if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(details))}\\n`);\n return 6;\n }\n if (options.download) {\n const url = details.bundle.downloadUrl;\n if (!details.bundle.available || !url) {\n throw new BundleDownloadError(\"Completed job does not have an available canonical bundle\");\n }\n try {\n await dependencies.downloadBundle(url, options.download, {\n force: options.force,\n expectedSizeBytes: details.bundle.sizeBytes,\n expectedSha256: details.bundle.sha256,\n });\n } catch (error) {\n if (error instanceof BundleDownloadError) throw error;\n throw new BundleDownloadError(`Could not download bundle to ${options.download}`, { cause: error });\n }\n if (!options.json) dependencies.writeStdout(`Downloaded bundle to ${options.download}\\n`);\n }\n if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(details))}\\n`);\n return 0;\n}\n","import { randomBytes, randomUUID } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\n\nimport { parseLocalDestination } from \"../trigger/destination.js\";\nimport {\n SUPPORTED_TRIGGER_EVENTS,\n createSyntheticTerminalEvent,\n isTriggerEventType,\n type TriggerEventType,\n} from \"../trigger/event.js\";\nimport { signSyntheticWebhook } from \"../trigger/signature.js\";\nimport { TriggerDeliveryError, UsageError } from \"../errors.js\";\n\nexport interface TriggerCommandDependencies {\n env?: Readonly<Record<string, string | undefined>>;\n fetch?: typeof globalThis.fetch;\n now?: () => number;\n randomUUID?: () => string;\n randomBytes?: (size: number) => Uint8Array;\n readFile?: (path: string, encoding: \"utf8\") => Promise<string>;\n writeStdout?: (text: string) => void;\n}\n\ninterface TriggerArguments {\n type: TriggerEventType;\n destination: URL;\n secretFile?: string;\n generateSecret: boolean;\n jobId?: string;\n accountId?: string;\n json: boolean;\n}\n\nfunction requireOptionValue(args: string[], index: number, option: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(`${option} requires a value`);\n return value;\n}\n\nexport function parseTriggerArguments(args: string[]): TriggerArguments {\n const typeValue = args[0];\n if (!typeValue || typeValue.startsWith(\"--\")) {\n throw new UsageError(\n `trigger requires an event type: ${SUPPORTED_TRIGGER_EVENTS.join(\", \")}`,\n );\n }\n if (!isTriggerEventType(typeValue)) {\n throw new UsageError(\n `Unsupported trigger event ${JSON.stringify(typeValue)}; expected ${SUPPORTED_TRIGGER_EVENTS.join(\", \")}`,\n );\n }\n\n let to: string | undefined;\n let secretFile: string | undefined;\n let generateSecret = false;\n let jobId: string | undefined;\n let accountId: string | undefined;\n let json = false;\n for (let index = 1; index < args.length; index += 1) {\n const option = args[index];\n if (!option) continue;\n if (option === \"--generate-secret\") {\n generateSecret = true;\n continue;\n }\n if (option === \"--json\") {\n json = true;\n continue;\n }\n if ([\"--to\", \"--secret-file\", \"--job-id\", \"--account-id\"].includes(option)) {\n const value = requireOptionValue(args, index, option);\n index += 1;\n if (option === \"--to\") to = value;\n else if (option === \"--secret-file\") secretFile = value;\n else if (option === \"--job-id\") jobId = value;\n else accountId = value;\n continue;\n }\n throw new UsageError(`Unknown trigger option: ${option}`);\n }\n\n if (!to) throw new UsageError(\"trigger requires an explicit --to local URL\");\n if (generateSecret && secretFile) {\n throw new UsageError(\"--generate-secret and --secret-file are mutually exclusive\");\n }\n if (generateSecret && json) {\n throw new UsageError(\"--generate-secret and --json are mutually exclusive\");\n }\n if (jobId !== undefined && !jobId.trim()) throw new UsageError(\"--job-id must not be empty\");\n if (accountId !== undefined && !accountId.trim()) {\n throw new UsageError(\"--account-id must not be empty\");\n }\n return {\n type: typeValue,\n destination: parseLocalDestination(to),\n ...(secretFile === undefined ? {} : { secretFile }),\n generateSecret,\n ...(jobId === undefined ? {} : { jobId: jobId.trim() }),\n ...(accountId === undefined ? {} : { accountId: accountId.trim() }),\n json,\n };\n}\n\nasync function resolveSecret(\n args: TriggerArguments,\n dependencies: Required<\n Pick<TriggerCommandDependencies, \"env\" | \"randomBytes\" | \"readFile\" | \"writeStdout\">\n >,\n): Promise<string> {\n if (args.generateSecret) {\n const generated = Buffer.from(dependencies.randomBytes(32)).toString(\"base64url\");\n dependencies.writeStdout(\n `Generated webhook secret (configure the local receiver with this value): ${generated}\\n`,\n );\n return generated;\n }\n let secret: string | undefined;\n if (args.secretFile) {\n try {\n secret = await dependencies.readFile(args.secretFile, \"utf8\");\n } catch (error) {\n throw new UsageError(`Could not read webhook secret file: ${args.secretFile}`, { cause: error });\n }\n } else {\n secret = dependencies.env.MEDIARUNTIME_WEBHOOK_SECRET;\n }\n const normalized = secret?.trim();\n if (!normalized) {\n throw new UsageError(\n \"Set MEDIARUNTIME_WEBHOOK_SECRET, pass --secret-file, or explicitly use --generate-secret\",\n );\n }\n return normalized;\n}\n\nexport async function runTriggerCommand(\n args: string[],\n dependencies: TriggerCommandDependencies = {},\n): Promise<number> {\n const parsed = parseTriggerArguments(args);\n const deps = {\n env: dependencies.env ?? process.env,\n fetch: dependencies.fetch ?? globalThis.fetch,\n now: dependencies.now ?? Date.now,\n randomUUID: dependencies.randomUUID ?? randomUUID,\n randomBytes: dependencies.randomBytes ?? randomBytes,\n readFile: dependencies.readFile ?? readFile,\n writeStdout: dependencies.writeStdout ?? ((text: string) => process.stdout.write(text)),\n };\n const secret = await resolveSecret(parsed, deps);\n const timestamp = Math.floor(deps.now() / 1000);\n const unique = deps.randomUUID();\n const eventId = `evt_cli_${unique}`;\n const jobId = parsed.jobId ?? `job_cli_${unique}`;\n const accountId = parsed.accountId ?? \"acc_cli_local\";\n const payload = createSyntheticTerminalEvent(parsed.type, {\n eventId,\n jobId,\n accountId,\n timestamp,\n });\n const signed = signSyntheticWebhook(payload, { eventId, timestamp, secret });\n\n const response = await deps.fetch(parsed.destination, {\n method: \"POST\",\n headers: signed.headers,\n body: signed.body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new TriggerDeliveryError(\n `Local webhook endpoint returned HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : \"\"}`,\n );\n }\n if (parsed.json) {\n deps.writeStdout(`${JSON.stringify({\n eventId,\n type: parsed.type,\n jobId,\n status: payload.status,\n destination: parsed.destination.href,\n httpStatus: response.status,\n })}\\n`);\n } else {\n deps.writeStdout(\n `Sent synthetic ${parsed.type} event ${eventId} to ${parsed.destination.href} (HTTP ${response.status})\\n`,\n );\n }\n return 0;\n}\n","import { UsageError } from \"../errors.js\";\n\nexport function isLoopbackDestination(value: URL): boolean {\n let hostname = value.hostname.toLowerCase();\n if (hostname.endsWith(\".\")) hostname = hostname.slice(0, -1);\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n hostname = hostname.slice(1, -1);\n }\n if (hostname === \"localhost\" || hostname === \"::1\" || hostname === \"0:0:0:0:0:0:0:1\") {\n return true;\n }\n\n const octets = hostname.split(\".\");\n if (octets.length !== 4 || octets.some((part) => !/^\\d{1,3}$/.test(part))) return false;\n const numbers = octets.map(Number);\n return numbers[0] === 127 && numbers.every((part) => part >= 0 && part <= 255);\n}\n\nexport function parseLocalDestination(raw: string): URL {\n let destination: URL;\n try {\n destination = new URL(raw);\n } catch {\n throw new UsageError(\"--to must be an absolute local HTTP(S) URL\");\n }\n if (destination.protocol !== \"http:\" && destination.protocol !== \"https:\") {\n throw new UsageError(\"--to must use http:// or https://\");\n }\n if (destination.username || destination.password) {\n throw new UsageError(\"--to must not contain URL credentials\");\n }\n if (!isLoopbackDestination(destination)) {\n throw new UsageError(\n \"trigger only sends to loopback destinations (localhost, 127.0.0.0/8, or ::1)\",\n );\n }\n return destination;\n}\n","export const SUPPORTED_TRIGGER_EVENTS = [\n \"job.completed\",\n \"job.failed\",\n \"job.rejected\",\n] as const;\n\nexport type TriggerEventType = (typeof SUPPORTED_TRIGGER_EVENTS)[number];\n\nexport function isTriggerEventType(value: string): value is TriggerEventType {\n return (SUPPORTED_TRIGGER_EVENTS as readonly string[]).includes(value);\n}\n\nexport interface SyntheticEventOptions {\n eventId: string;\n jobId: string;\n accountId: string;\n timestamp: number;\n}\n\nexport function createSyntheticTerminalEvent(\n type: TriggerEventType,\n options: SyntheticEventOptions,\n): Record<string, unknown> {\n const status = type.slice(\"job.\".length).toUpperCase();\n const occurredAt = new Date(options.timestamp * 1000).toISOString();\n const base = {\n event_id: options.eventId,\n job_id: options.jobId,\n account_id: options.accountId,\n status,\n };\n\n if (type === \"job.completed\") {\n const expiresAt = new Date((options.timestamp + 7 * 24 * 60 * 60) * 1000).toISOString();\n return {\n ...base,\n completedAt: occurredAt,\n delivery: {\n mode: \"PULL\",\n retentionDays: 7,\n expiresAt,\n bundle: {\n type: \"zip\",\n filename: `${options.jobId}_outputs.zip`,\n size_bytes: 0,\n sha256: \"0\".repeat(64),\n download: {\n url: `http://127.0.0.1/__mediaruntime_synthetic__/${encodeURIComponent(options.jobId)}.zip`,\n expiresAt,\n },\n },\n layout: \"bundle_relative_v1\",\n deliverables: [],\n manifests: {},\n },\n billing: { status: \"PAID\" },\n usage: { units_total: 0 },\n meta: { synthetic: true, request_metadata: {} },\n };\n }\n\n const failed = type === \"job.failed\";\n return {\n ...base,\n [failed ? \"failedAt\" : \"rejectedAt\"]: occurredAt,\n delivery: { mode: \"NONE\" },\n billing: { status: \"RELEASED\" },\n error: {\n code: failed ? \"SYNTHETIC_ENGINE_FAILED\" : \"SYNTHETIC_REJECTED\",\n message: `Synthetic ${type} event from the MediaRuntime CLI`,\n },\n meta: { synthetic: true, request_metadata: {} },\n };\n}\n","import { createHmac } from \"node:crypto\";\n\nexport const WEBHOOK_HEADERS = {\n id: \"X-Transcoder-Id\",\n timestamp: \"X-Transcoder-Timestamp\",\n signature: \"X-Transcoder-Signature\",\n} as const;\n\nexport interface SignedWebhook {\n body: string;\n headers: Record<string, string>;\n}\n\nexport function signSyntheticWebhook(\n payload: Readonly<Record<string, unknown>>,\n options: { eventId: string; timestamp: number; secret: string },\n): SignedWebhook {\n if (!options.eventId.trim()) throw new Error(\"Webhook event ID must not be empty\");\n if (!Number.isInteger(options.timestamp) || options.timestamp < 0) {\n throw new Error(\"Webhook timestamp must be a non-negative integer\");\n }\n if (!options.secret) throw new Error(\"Webhook secret must not be empty\");\n\n // JSON is serialized once. These exact bytes are both signed and sent.\n const body = JSON.stringify(payload);\n const bodyBytes = Buffer.from(body, \"utf8\");\n const prefix = Buffer.from(`${options.timestamp}.${options.eventId}.`, \"utf8\");\n const digest = createHmac(\"sha256\", options.secret)\n .update(prefix)\n .update(bodyBytes)\n .digest(\"hex\");\n\n return {\n body,\n headers: {\n \"Content-Type\": \"application/json\",\n [WEBHOOK_HEADERS.id]: options.eventId,\n [WEBHOOK_HEADERS.timestamp]: String(options.timestamp),\n [WEBHOOK_HEADERS.signature]: `t=${options.timestamp},v1=${digest}`,\n },\n };\n}\n"],"mappings":"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","names":["randomUUID","UNSUCCESSFUL_TERMINAL_STATUSES","optionValue","detailsProjection","randomUUID"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/errors.ts","../src/auth/api.ts","../src/commands/auth.ts","../src/auth/browser.ts","../src/auth/credential-store.ts","../src/commands/jobs.ts","../src/commands/run.ts","../src/commands/trigger.ts","../src/trigger/destination.ts","../src/trigger/event.ts","../src/trigger/signature.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { constants, realpathSync } from \"node:fs\";\nimport {\n access,\n link,\n rename,\n rm,\n} from \"node:fs/promises\";\nimport { createWriteStream } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { Readable, Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n JobWaitTimeoutError,\n MediaRuntime,\n MediaRuntimeApiError,\n MediaRuntimeConnectionError,\n} from \"@mediaruntime/node\";\nimport { PRODUCTION_ORIGIN } from \"./auth/api.js\";\nimport type { CredentialStore } from \"./auth/credential-store.js\";\nimport {\n resolveCredential,\n runAuthCommand,\n runLoginCommand,\n runLogoutCommand,\n type AuthCommandDependencies,\n} from \"./commands/auth.js\";\nimport {\n runJobsCommand,\n type JobsReadClient,\n} from \"./commands/jobs.js\";\nimport {\n runCommand,\n type RunJobsClient,\n} from \"./commands/run.js\";\nimport { runTriggerCommand } from \"./commands/trigger.js\";\nimport { BundleDownloadError, CliError, UsageError } from \"./errors.js\";\nimport { isLoopbackDestination } from \"./trigger/destination.js\";\n\nexport { parseTriggerArguments, runTriggerCommand } from \"./commands/trigger.js\";\nexport { createSyntheticTerminalEvent } from \"./trigger/event.js\";\nexport { signSyntheticWebhook } from \"./trigger/signature.js\";\nexport {\n resolveCredential,\n runAuthCommand,\n runAuthStatusCommand,\n runLoginCommand,\n runLogoutCommand,\n} from \"./commands/auth.js\";\n\nconst VERSION = \"0.2.0\";\nconst HELP = `MediaRuntime CLI\n\nUsage:\n mediaruntime run <source> --output <alias> [--output <alias>] [--wait]\n mediaruntime jobs list [--status <status>] [--limit <n>] [--cursor <cursor>]\n mediaruntime jobs get <job_id> [--download <bundle.zip>] [--force]\n mediaruntime trigger <job.completed|job.failed|job.rejected> --to <local-url>\n mediaruntime login [--no-browser]\n mediaruntime auth status\n mediaruntime logout [--local-only]\n\nGlobal options:\n --base-url <url> Developer override; normally leave unset\n --help Show help\n --version Show version\n\nRun/jobs options:\n --json Machine-readable, URL-redacted output\n\nAuthentication uses a secure login or MEDIARUNTIME_API_KEY.\n`;\n\ntype CliJobsClient = RunJobsClient & JobsReadClient;\n\nexport interface CliClient {\n jobs: CliJobsClient;\n}\n\nexport interface CliDependencies {\n createClient?(options: { baseUrl?: string; apiKey?: string }): CliClient;\n downloadBundle?(\n url: string,\n destination: string,\n options: {\n force: boolean;\n expectedSizeBytes?: number | null;\n expectedSha256?: string | null;\n },\n ): Promise<void>;\n writeStdout?(text: string): void;\n writeStderr?(text: string): void;\n credentialStore?: CredentialStore;\n openBrowser?(url: string): Promise<void>;\n sleep?(milliseconds: number): Promise<void>;\n now?(): number;\n randomVerifier?(): string;\n}\n\nfunction isFileExistsError(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && error.code === \"EEXIST\";\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function downloadBundleAtomically(\n url: string,\n destination: string,\n options: {\n force: boolean;\n expectedSizeBytes?: number | null;\n expectedSha256?: string | null;\n },\n): Promise<void> {\n const target = resolve(destination);\n if (!options.force && await exists(target)) {\n throw new BundleDownloadError(`Refusing to overwrite existing file: ${destination}`);\n }\n const temporary = resolve(\n dirname(target),\n `.${basename(target)}.${randomUUID()}.part`,\n );\n\n try {\n let response: Response;\n try {\n response = await fetch(url);\n } catch (error) {\n throw new BundleDownloadError(\"Bundle download failed before receiving a response\", { cause: error });\n }\n if (!response.ok) {\n await response.body?.cancel();\n throw new BundleDownloadError(`Bundle download failed with HTTP ${response.status}`);\n }\n if (!response.body) throw new BundleDownloadError(\"Bundle download returned an empty response body\");\n const hash = createHash(\"sha256\");\n let sizeBytes = 0;\n const integrity = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n sizeBytes += chunk.length;\n hash.update(chunk);\n callback(null, chunk);\n },\n });\n await pipeline(\n Readable.fromWeb(response.body as unknown as NodeReadableStream<Uint8Array>),\n integrity,\n createWriteStream(temporary, { flags: \"wx\", mode: 0o600 }),\n );\n const sha256 = hash.digest(\"hex\");\n if (options.expectedSizeBytes !== undefined && options.expectedSizeBytes !== null &&\n sizeBytes !== options.expectedSizeBytes) {\n throw new BundleDownloadError(\n `Bundle size mismatch: expected ${options.expectedSizeBytes} bytes, received ${sizeBytes}`,\n );\n }\n if (options.expectedSha256 && sha256 !== options.expectedSha256.toLowerCase()) {\n throw new BundleDownloadError(\"Bundle SHA-256 mismatch\");\n }\n\n if (options.force) {\n await rename(temporary, target);\n } else {\n try {\n // A hard link publishes the completed same-filesystem temporary file atomically\n // and fails rather than replacing a destination created during the download.\n await link(temporary, target);\n } catch (error) {\n if (isFileExistsError(error)) {\n throw new BundleDownloadError(`Refusing to overwrite existing file: ${destination}`);\n }\n throw error;\n }\n await rm(temporary);\n }\n } catch (error) {\n await rm(temporary, { force: true });\n if (error instanceof BundleDownloadError) throw error;\n throw new BundleDownloadError(`Could not write bundle to ${destination}`, { cause: error });\n }\n}\n\nfunction normalizeBaseUrl(raw: string): string {\n let value: URL;\n try {\n value = new URL(raw);\n } catch {\n throw new UsageError(\"--base-url must be an absolute HTTP(S) origin\");\n }\n if (value.username || value.password || value.search || value.hash || value.pathname !== \"/\") {\n throw new UsageError(\"--base-url must be an origin without credentials, path, query, or fragment\");\n }\n if (value.protocol !== \"https:\" && !(value.protocol === \"http:\" && isLoopbackDestination(value))) {\n throw new UsageError(\"--base-url must use HTTPS unless it points to loopback\");\n }\n return value.origin;\n}\n\nfunction extractGlobalOptions(argv: string[]): { baseUrl?: string; args: string[] } {\n const args = [...argv];\n let baseUrl: string | undefined;\n while (args[0] === \"--base-url\") {\n const value = args[1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(\"--base-url requires a value\");\n baseUrl = normalizeBaseUrl(value);\n args.splice(0, 2);\n }\n return { ...(baseUrl === undefined ? {} : { baseUrl }), args };\n}\n\nfunction safeApiError(error: MediaRuntimeApiError): Record<string, unknown> {\n return {\n name: error.name,\n message: error.message,\n code: error.code,\n status: error.status,\n retryable: error.retryable,\n requestId: error.requestId ?? null,\n field: error.field ?? null,\n };\n}\n\nfunction exitCodeFor(error: unknown): number {\n if (error instanceof CliError) return error.exitCode;\n if (error instanceof JobWaitTimeoutError) return 7;\n if (error instanceof MediaRuntimeConnectionError) return 5;\n if (error instanceof MediaRuntimeApiError) {\n if ([401, 402, 403].includes(error.status)) return 3;\n if (error.status >= 500) return 5;\n return 4;\n }\n if (error instanceof DOMException && error.name === \"AbortError\") return 130;\n if (error instanceof TypeError && /fetch|network|socket|connect/i.test(error.message)) return 5;\n return 1;\n}\n\nfunction safeError(error: unknown, exitCode: number): Record<string, unknown> {\n if (error instanceof MediaRuntimeApiError) return { ...safeApiError(error), exitCode };\n return {\n code: error instanceof CliError ? error.code : \"cli_error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n exitCode,\n };\n}\n\nexport async function executeCli(\n argv: string[],\n dependencies: CliDependencies = {},\n): Promise<number> {\n const writeStdout = dependencies.writeStdout ?? ((text: string) => process.stdout.write(text));\n const writeStderr = dependencies.writeStderr ?? ((text: string) => process.stderr.write(text));\n const json = argv.includes(\"--json\");\n try {\n const global = extractGlobalOptions(argv);\n const command = global.args[0];\n if (!command || command === \"--help\" || command === \"-h\") {\n writeStdout(HELP);\n return 0;\n }\n if (command === \"--version\" || command === \"-v\") {\n writeStdout(`${VERSION}\\n`);\n return 0;\n }\n if (command === \"trigger\") {\n return await runTriggerCommand(global.args.slice(1), { writeStdout });\n }\n\n const configuredBaseUrl = global.baseUrl ?? process.env.MEDIARUNTIME_API_URL;\n const baseUrl = configuredBaseUrl ? normalizeBaseUrl(configuredBaseUrl) : undefined;\n const authBaseUrl = baseUrl ?? PRODUCTION_ORIGIN;\n const authDependencies: AuthCommandDependencies = {\n ...(dependencies.credentialStore ? { credentialStore: dependencies.credentialStore } : {}),\n ...(dependencies.openBrowser ? { openBrowser: dependencies.openBrowser } : {}),\n ...(dependencies.sleep ? { sleep: dependencies.sleep } : {}),\n ...(dependencies.now ? { now: dependencies.now } : {}),\n ...(dependencies.randomVerifier ? { randomVerifier: dependencies.randomVerifier } : {}),\n writeStdout,\n };\n if (command === \"login\") return await runLoginCommand(global.args.slice(1), authBaseUrl, authDependencies);\n if (command === \"auth\") return await runAuthCommand(global.args.slice(1), authBaseUrl, authDependencies);\n if (command === \"logout\") return await runLogoutCommand(global.args.slice(1), authBaseUrl, authDependencies);\n\n let apiKey = process.env.MEDIARUNTIME_API_KEY?.trim();\n if (!apiKey && (!dependencies.createClient || dependencies.credentialStore)) {\n apiKey = (await resolveCredential(authBaseUrl, authDependencies))?.apiKey;\n }\n if (!dependencies.createClient && !apiKey) {\n throw new UsageError(\"Run mediaruntime login or set MEDIARUNTIME_API_KEY before using run or jobs\");\n }\n const client = dependencies.createClient?.({\n ...(baseUrl === undefined ? {} : { baseUrl }),\n ...(apiKey === undefined ? {} : { apiKey }),\n }) ?? new MediaRuntime({\n ...(baseUrl === undefined ? {} : { baseUrl }),\n ...(apiKey === undefined ? {} : { apiKey }),\n });\n const downloadBundle = dependencies.downloadBundle ?? downloadBundleAtomically;\n const commandDependencies = { jobs: client.jobs, writeStdout, downloadBundle };\n if (command === \"run\") return await runCommand(global.args.slice(1), commandDependencies);\n if (command === \"jobs\") {\n return await runJobsCommand(global.args.slice(1), commandDependencies);\n }\n throw new UsageError(`Unknown command: ${command}`);\n } catch (error) {\n const exitCode = exitCodeFor(error);\n if (json) {\n writeStderr(`${JSON.stringify({ error: safeError(error, exitCode) })}\\n`);\n } else {\n const requestId = error instanceof MediaRuntimeApiError && error.requestId\n ? ` (request ${error.requestId})`\n : \"\";\n writeStderr(`Error: ${error instanceof Error ? error.message : \"Unknown error\"}${requestId}\\n`);\n }\n return exitCode;\n }\n}\n\nlet isEntrypoint = false;\nif (process.argv[1] !== undefined) {\n try {\n // npm exposes binaries through a symlink. Resolve both sides so the installed command\n // executes while importing dist/cli.js in tests remains side-effect free.\n isEntrypoint = realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n isEntrypoint = false;\n }\n}\nif (isEntrypoint) process.exitCode = await executeCli(process.argv.slice(2));\n","export class CliError extends Error {\n readonly code: string;\n readonly exitCode: number;\n\n constructor(code: string, message: string, exitCode: number, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"CliError\";\n this.code = code;\n this.exitCode = exitCode;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class UsageError extends CliError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(\"usage_error\", message, 2, options);\n this.name = \"UsageError\";\n }\n}\n\nexport class TriggerDeliveryError extends CliError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(\"trigger_delivery_error\", message, 8, options);\n this.name = \"TriggerDeliveryError\";\n }\n}\n\nexport class BundleDownloadError extends CliError {\n constructor(message: string, options?: { cause?: unknown }) {\n super(\"bundle_download_error\", message, 9, options);\n this.name = \"BundleDownloadError\";\n }\n}\n","import { CliError } from \"../errors.js\";\n\nexport const PRODUCTION_ORIGIN = \"https://mediaruntime.com\";\n\ninterface ErrorPayload {\n error?: unknown;\n detail?: unknown;\n}\n\nexport interface DeviceAuthorization {\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n verificationUriComplete: string;\n expiresIn: number;\n interval: number;\n}\n\nexport interface IssuedCredential {\n apiKey: string;\n accountId: string;\n keyId: string;\n credentialType: \"cli\";\n}\n\nexport interface CredentialStatus {\n status: \"authenticated\";\n accountId: string;\n keyId: string;\n credentialType: \"cli\" | \"api_key\";\n}\n\nexport class AuthApiError extends CliError {\n readonly status: number;\n\n constructor(code: string, message: string, status: number) {\n super(code, message, status >= 500 ? 5 : 3);\n this.name = \"AuthApiError\";\n this.status = status;\n }\n}\n\nfunction endpoint(baseUrl: string, path: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}/api/cli/auth/${path}`;\n}\n\nasync function responsePayload(response: Response): Promise<Record<string, unknown>> {\n try {\n const value: unknown = await response.json();\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n } catch {\n return {};\n }\n}\n\nasync function post(\n baseUrl: string,\n path: string,\n body: Record<string, unknown>,\n apiKey?: string,\n): Promise<Record<string, unknown>> {\n let response: Response;\n try {\n response = await fetch(endpoint(baseUrl, path), {\n method: \"POST\",\n redirect: \"error\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(apiKey ? { \"X-API-Key\": apiKey } : {}),\n },\n body: JSON.stringify(body),\n });\n } catch (error) {\n throw new CliError(\"auth_connection_error\", \"Could not reach MediaRuntime authentication\", 5, { cause: error });\n }\n const payload = await responsePayload(response);\n if (!response.ok) {\n const errorPayload = payload as ErrorPayload;\n throw new AuthApiError(\n typeof errorPayload.error === \"string\" ? errorPayload.error : \"authorization_failed\",\n typeof errorPayload.detail === \"string\" ? errorPayload.detail : `Authentication failed with HTTP ${response.status}`,\n response.status,\n );\n }\n return payload;\n}\n\nfunction requireString(payload: Record<string, unknown>, key: string): string {\n const value = payload[key];\n if (typeof value !== \"string\" || !value) throw new CliError(\"invalid_auth_response\", \"MediaRuntime returned an invalid authentication response\", 5);\n return value;\n}\n\nexport async function startAuthorization(baseUrl: string, codeChallenge: string): Promise<DeviceAuthorization> {\n const payload = await post(baseUrl, \"start\", { codeChallenge, clientName: \"MediaRuntime CLI\" });\n return {\n deviceCode: requireString(payload, \"deviceCode\"),\n userCode: requireString(payload, \"userCode\"),\n verificationUri: requireString(payload, \"verificationUri\"),\n verificationUriComplete: requireString(payload, \"verificationUriComplete\"),\n expiresIn: Number(payload.expiresIn),\n interval: Number(payload.interval),\n };\n}\n\nexport async function exchangeAuthorization(baseUrl: string, deviceCode: string, codeVerifier: string): Promise<IssuedCredential> {\n const payload = await post(baseUrl, \"token\", { deviceCode, codeVerifier });\n const credentialType = requireString(payload, \"credentialType\");\n if (credentialType !== \"cli\") throw new CliError(\"invalid_auth_response\", \"MediaRuntime returned the wrong credential type\", 5);\n return {\n apiKey: requireString(payload, \"apiKey\"),\n accountId: requireString(payload, \"accountId\"),\n keyId: requireString(payload, \"keyId\"),\n credentialType,\n };\n}\n\nexport async function inspectCredential(baseUrl: string, apiKey: string): Promise<CredentialStatus> {\n const payload = await post(baseUrl, \"status\", {}, apiKey);\n const credentialType = requireString(payload, \"credentialType\");\n if (credentialType !== \"cli\" && credentialType !== \"api_key\") {\n throw new CliError(\"invalid_auth_response\", \"MediaRuntime returned the wrong credential type\", 5);\n }\n return {\n status: \"authenticated\",\n accountId: requireString(payload, \"accountId\"),\n keyId: requireString(payload, \"keyId\"),\n credentialType,\n };\n}\n\nexport async function revokeCredential(baseUrl: string, apiKey: string): Promise<void> {\n await post(baseUrl, \"revoke\", {}, apiKey);\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n AuthApiError,\n exchangeAuthorization,\n inspectCredential,\n revokeCredential,\n startAuthorization,\n} from \"../auth/api.js\";\nimport { openBrowser } from \"../auth/browser.js\";\nimport {\n OsCredentialStore,\n type CredentialStore,\n type StoredCredential,\n} from \"../auth/credential-store.js\";\nimport { CliError, UsageError } from \"../errors.js\";\n\nexport interface AuthCommandDependencies {\n credentialStore?: CredentialStore;\n openBrowser?: (url: string) => Promise<void>;\n sleep?: (milliseconds: number) => Promise<void>;\n now?: () => number;\n randomVerifier?: () => string;\n writeStdout?: (text: string) => void;\n}\n\nconst defaultSleep = (milliseconds: number) => new Promise<void>((resolve) => setTimeout(resolve, milliseconds));\n\nfunction verifierChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\nfunction defaultVerifier(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nfunction store(dependencies: AuthCommandDependencies): CredentialStore {\n return dependencies.credentialStore ?? new OsCredentialStore();\n}\n\nfunction assertOnly(args: string[], allowed: string[]): void {\n const unexpected = args.find((arg) => !allowed.includes(arg));\n if (unexpected) throw new UsageError(`Unknown authentication option: ${unexpected}`);\n}\n\nexport async function resolveCredential(\n baseUrl: string,\n dependencies: AuthCommandDependencies = {},\n): Promise<{ apiKey: string; source: \"environment\" | \"keychain\"; stored?: StoredCredential } | null> {\n const environment = process.env.MEDIARUNTIME_API_KEY?.trim();\n if (environment) return { apiKey: environment, source: \"environment\" };\n const stored = await store(dependencies).get(baseUrl);\n return stored ? { apiKey: stored.apiKey, source: \"keychain\", stored } : null;\n}\n\nexport async function runLoginCommand(\n args: string[],\n baseUrl: string,\n dependencies: AuthCommandDependencies = {},\n): Promise<number> {\n assertOnly(args, [\"--no-browser\"]);\n const noBrowser = args.includes(\"--no-browser\");\n const write = dependencies.writeStdout ?? ((text: string) => process.stdout.write(text));\n const existing = await store(dependencies).get(baseUrl);\n if (existing) {\n try {\n await inspectCredential(baseUrl, existing.apiKey);\n write(`Already logged in to ${baseUrl} (account ${existing.accountId}).\\n`);\n return 0;\n } catch (error) {\n if (!(error instanceof AuthApiError) || error.status >= 500) throw error;\n }\n }\n\n const verifier = dependencies.randomVerifier?.() ?? defaultVerifier();\n if (verifier.length < 43 || verifier.length > 128) {\n throw new CliError(\"invalid_verifier\", \"Generated PKCE verifier is invalid\", 1);\n }\n const authorization = await startAuthorization(baseUrl, verifierChallenge(verifier));\n if (!Number.isFinite(authorization.expiresIn) || authorization.expiresIn <= 0 ||\n !Number.isFinite(authorization.interval) || authorization.interval < 1) {\n throw new CliError(\"invalid_auth_response\", \"MediaRuntime returned invalid authorization timing\", 5);\n }\n\n write(`Open this URL to authorize MediaRuntime CLI:\\n${authorization.verificationUriComplete}\\n\\n`);\n write(`Confirm code: ${authorization.userCode}\\n`);\n if (!noBrowser) await (dependencies.openBrowser ?? openBrowser)(authorization.verificationUriComplete);\n write(\"Waiting for browser approval…\\n\");\n\n const now = dependencies.now ?? Date.now;\n const sleep = dependencies.sleep ?? defaultSleep;\n const deadline = now() + authorization.expiresIn * 1000;\n let credential;\n while (now() < deadline) {\n try {\n credential = await exchangeAuthorization(baseUrl, authorization.deviceCode, verifier);\n break;\n } catch (error) {\n if (error instanceof AuthApiError && error.code === \"authorization_pending\") {\n await sleep(authorization.interval * 1000);\n continue;\n }\n throw error;\n }\n }\n if (!credential) throw new CliError(\"authorization_timeout\", \"Browser authorization expired; run mediaruntime login again\", 7);\n\n try {\n await inspectCredential(baseUrl, credential.apiKey);\n await store(dependencies).set({\n apiKey: credential.apiKey,\n accountId: credential.accountId,\n keyId: credential.keyId,\n baseUrl,\n createdAt: new Date(now()).toISOString(),\n });\n } catch (error) {\n await revokeCredential(baseUrl, credential.apiKey).catch(() => undefined);\n throw error;\n }\n\n write(`Logged in to ${baseUrl} (account ${credential.accountId}).\\n`);\n if (process.env.MEDIARUNTIME_API_KEY?.trim()) {\n write(\"MEDIARUNTIME_API_KEY is set and will take precedence over this login.\\n\");\n }\n return 0;\n}\n\nexport async function runAuthStatusCommand(\n args: string[],\n baseUrl: string,\n dependencies: AuthCommandDependencies = {},\n): Promise<number> {\n assertOnly(args, [\"--json\"]);\n const json = args.includes(\"--json\");\n const write = dependencies.writeStdout ?? ((text: string) => process.stdout.write(text));\n const resolved = await resolveCredential(baseUrl, dependencies);\n if (!resolved) throw new CliError(\"authentication_required\", \"Run mediaruntime login or set MEDIARUNTIME_API_KEY\", 3);\n const status = await inspectCredential(baseUrl, resolved.apiKey);\n if (json) {\n write(`${JSON.stringify({ authenticated: true, source: resolved.source, ...status })}\\n`);\n } else {\n write(`Authenticated via ${resolved.source} (account ${status.accountId}, key ${status.keyId}).\\n`);\n }\n return 0;\n}\n\nexport async function runLogoutCommand(\n args: string[],\n baseUrl: string,\n dependencies: AuthCommandDependencies = {},\n): Promise<number> {\n assertOnly(args, [\"--local-only\"]);\n const localOnly = args.includes(\"--local-only\");\n const write = dependencies.writeStdout ?? ((text: string) => process.stdout.write(text));\n const credentialStore = store(dependencies);\n const credential = await credentialStore.get(baseUrl);\n if (!credential) {\n write(`No stored login for ${baseUrl}.\\n`);\n if (process.env.MEDIARUNTIME_API_KEY?.trim()) write(\"MEDIARUNTIME_API_KEY remains active in this shell.\\n\");\n return 0;\n }\n if (!localOnly) await revokeCredential(baseUrl, credential.apiKey);\n await credentialStore.delete(baseUrl);\n write(localOnly ? \"Removed the local CLI login without server revocation.\\n\" : \"Logged out and revoked the CLI credential.\\n\");\n if (process.env.MEDIARUNTIME_API_KEY?.trim()) write(\"MEDIARUNTIME_API_KEY remains active in this shell.\\n\");\n return 0;\n}\n\nexport async function runAuthCommand(\n args: string[],\n baseUrl: string,\n dependencies: AuthCommandDependencies = {},\n): Promise<number> {\n const subcommand = args[0];\n if (subcommand !== \"status\") throw new UsageError(\"Usage: mediaruntime auth status [--json]\");\n return runAuthStatusCommand(args.slice(1), baseUrl, dependencies);\n}\n","import { spawn } from \"node:child_process\";\nimport { CliError } from \"../errors.js\";\n\nexport async function openBrowser(url: string): Promise<void> {\n const command = process.platform === \"darwin\"\n ? { executable: \"open\", args: [url] }\n : process.platform === \"win32\"\n ? { executable: \"rundll32\", args: [\"url.dll,FileProtocolHandler\", url] }\n : { executable: \"xdg-open\", args: [url] };\n\n await new Promise<void>((resolve, reject) => {\n const child = spawn(command.executable, command.args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.once(\"error\", (error) => reject(new CliError(\n \"browser_open_failed\",\n \"Could not open a browser; rerun with --no-browser and open the printed URL\",\n 2,\n { cause: error },\n )));\n child.once(\"spawn\", () => {\n child.unref();\n resolve();\n });\n });\n}\n","import { CliError } from \"../errors.js\";\n\nconst SERVICE = \"mediaruntime-cli\";\n\nexport interface StoredCredential {\n apiKey: string;\n accountId: string;\n keyId: string;\n baseUrl: string;\n createdAt: string;\n}\n\nexport interface CredentialStore {\n get(baseUrl: string): Promise<StoredCredential | null>;\n set(credential: StoredCredential): Promise<void>;\n delete(baseUrl: string): Promise<void>;\n}\n\nfunction vaultAccount(baseUrl: string): string {\n return new URL(baseUrl).host.toLowerCase();\n}\n\nfunction validCredential(value: unknown): value is StoredCredential {\n if (!value || typeof value !== \"object\") return false;\n const item = value as Record<string, unknown>;\n return typeof item.apiKey === \"string\" && item.apiKey.startsWith(\"sk_\") &&\n typeof item.accountId === \"string\" && item.accountId.length > 0 &&\n typeof item.keyId === \"string\" && item.keyId.length > 0 &&\n typeof item.baseUrl === \"string\" && item.baseUrl.length > 0 &&\n typeof item.createdAt === \"string\" && item.createdAt.length > 0;\n}\n\nasync function entry(baseUrl: string) {\n try {\n const { Entry } = await import(\"@napi-rs/keyring\");\n return new Entry(SERVICE, vaultAccount(baseUrl));\n } catch (error) {\n throw new CliError(\n \"credential_store_unavailable\",\n \"The operating-system credential vault is unavailable; use MEDIARUNTIME_API_KEY on this machine\",\n 2,\n { cause: error },\n );\n }\n}\n\nexport class OsCredentialStore implements CredentialStore {\n async get(baseUrl: string): Promise<StoredCredential | null> {\n try {\n const raw = (await entry(baseUrl)).getPassword();\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n if (!validCredential(parsed) || parsed.baseUrl !== baseUrl) {\n throw new Error(\"Stored credential has an invalid shape\");\n }\n return parsed;\n } catch (error) {\n if (error instanceof SyntaxError || (error instanceof Error && error.message.includes(\"invalid shape\"))) {\n throw new CliError(\"credential_store_invalid\", \"The stored MediaRuntime credential is invalid; run mediaruntime logout --local-only\", 3);\n }\n if (error instanceof CliError) throw error;\n // Keyring implementations return a not-found error rather than null on some platforms.\n if (error instanceof Error && /not found|no entry|no matching|item.*exist/i.test(error.message)) return null;\n throw new CliError(\"credential_store_error\", \"Could not read the operating-system credential vault\", 2, { cause: error });\n }\n }\n\n async set(credential: StoredCredential): Promise<void> {\n try {\n (await entry(credential.baseUrl)).setPassword(JSON.stringify(credential));\n } catch (error) {\n if (error instanceof CliError) throw error;\n throw new CliError(\"credential_store_error\", \"Could not save the login in the operating-system credential vault\", 2, { cause: error });\n }\n }\n\n async delete(baseUrl: string): Promise<void> {\n try {\n (await entry(baseUrl)).deletePassword();\n } catch (error) {\n if (error instanceof CliError) throw error;\n if (error instanceof Error && /not found|no entry|no matching|item.*exist/i.test(error.message)) return;\n throw new CliError(\"credential_store_error\", \"Could not remove the login from the operating-system credential vault\", 2, { cause: error });\n }\n }\n}\n","import type { JobDetails, JobPage, JobStatus, ListJobsParams } from \"@mediaruntime/node\";\nimport { BundleDownloadError, UsageError } from \"../errors.js\";\n\nconst UNSUCCESSFUL_TERMINAL_STATUSES = new Set([\"FAILED\", \"REJECTED\", \"PARTIAL\"]);\n\nexport interface JobsReadClient {\n list(params?: ListJobsParams): Promise<JobPage>;\n get(jobId: string): Promise<JobDetails>;\n}\n\nexport interface JobsCommandDependencies {\n jobs: JobsReadClient;\n writeStdout(text: string): void;\n downloadBundle(\n url: string,\n destination: string,\n options: { force: boolean; expectedSizeBytes?: number | null; expectedSha256?: string | null },\n ): Promise<void>;\n}\n\nfunction optionValue(args: string[], index: number, option: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(`${option} requires a value`);\n return value;\n}\n\nfunction detailsProjection(job: JobDetails): Record<string, unknown> {\n return {\n id: job.id,\n status: job.status,\n tier: job.tier,\n usage: job.usage,\n billing: job.billing,\n bundle: {\n available: job.bundle.available,\n expiresAt: job.bundle.expiresAt,\n sizeBytes: job.bundle.sizeBytes,\n sha256: job.bundle.sha256,\n retentionDays: job.bundle.retentionDays,\n },\n media: job.media,\n metadata: job.metadata,\n error: job.error,\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n startedAt: job.startedAt,\n completedAt: job.completedAt,\n };\n}\n\nfunction humanDetails(job: JobDetails): string {\n const lines = [\n `Job: ${job.id}`,\n `Status: ${job.status}`,\n `Tier: ${job.tier.billed ?? job.tier.effective ?? job.tier.requested ?? \"-\"}`,\n `Bundle: ${job.bundle.available ? \"available\" : \"unavailable\"}`,\n ];\n if (job.bundle.sizeBytes !== null) lines.push(`Bundle size: ${job.bundle.sizeBytes} bytes`);\n if (job.error) lines.push(`Error: ${job.error}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction cell(value: unknown): string {\n if (value === null || value === undefined || value === \"\") return \"-\";\n return String(value);\n}\n\nfunction humanList(page: JobPage): string {\n const rows = page.jobs.map((job) => [\n job.id,\n job.status,\n cell(job.tierBilled),\n cell(job.unitsTotal),\n job.bundleAvailable ? \"yes\" : \"no\",\n cell(job.updatedAt),\n ]);\n const headings = [\"ID\", \"STATUS\", \"TIER\", \"UNITS\", \"BUNDLE\", \"UPDATED\"];\n const widths = headings.map((heading, index) =>\n Math.max(heading.length, ...rows.map((row) => row[index]?.length ?? 0)),\n );\n const render = (row: string[]): string =>\n row.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(\" \").trimEnd();\n const lines = [render(headings), render(widths.map((width) => \"-\".repeat(width))), ...rows.map(render)];\n if (page.nextCursor) lines.push(`Next cursor: ${page.nextCursor}`);\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nasync function listCommand(args: string[], dependencies: JobsCommandDependencies): Promise<number> {\n let status: string | undefined;\n let limit: number | undefined;\n let cursor: string | undefined;\n let json = false;\n for (let index = 0; index < args.length; index += 1) {\n const argument = args[index];\n if (argument === \"--status\") {\n status = optionValue(args, index, argument).toUpperCase();\n index += 1;\n } else if (argument === \"--limit\") {\n const value = Number(optionValue(args, index, argument));\n if (!Number.isSafeInteger(value) || value < 1 || value > 100) {\n throw new UsageError(\"--limit must be an integer between 1 and 100\");\n }\n limit = value;\n index += 1;\n } else if (argument === \"--cursor\") {\n cursor = optionValue(args, index, argument);\n index += 1;\n } else if (argument === \"--json\") {\n json = true;\n } else {\n throw new UsageError(`Unknown jobs list option: ${argument}`);\n }\n }\n const params: ListJobsParams = {\n ...(status === undefined ? {} : { status: status as JobStatus }),\n ...(limit === undefined ? {} : { limit }),\n ...(cursor === undefined ? {} : { cursor }),\n };\n const page = await dependencies.jobs.list(params);\n dependencies.writeStdout(json ? `${JSON.stringify(page)}\\n` : humanList(page));\n return 0;\n}\n\nasync function getCommand(args: string[], dependencies: JobsCommandDependencies): Promise<number> {\n let jobId: string | undefined;\n let download: string | undefined;\n let force = false;\n let json = false;\n for (let index = 0; index < args.length; index += 1) {\n const argument = args[index];\n if (argument === \"--download\") {\n download = optionValue(args, index, argument);\n index += 1;\n } else if (argument === \"--force\") {\n force = true;\n } else if (argument === \"--json\") {\n json = true;\n } else if (argument?.startsWith(\"-\")) {\n throw new UsageError(`Unknown jobs get option: ${argument}`);\n } else if (!jobId) {\n jobId = argument;\n } else {\n throw new UsageError(\"jobs get accepts exactly one job ID\");\n }\n }\n if (!jobId) throw new UsageError(\"Usage: mediaruntime jobs get <job_id>\");\n if (force && !download) throw new UsageError(\"--force requires --download\");\n const job = await dependencies.jobs.get(jobId);\n if (!json) dependencies.writeStdout(humanDetails(job));\n\n if (UNSUCCESSFUL_TERMINAL_STATUSES.has(String(job.status).toUpperCase())) {\n if (json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(job))}\\n`);\n return 6;\n }\n if (download) {\n if (String(job.status).toUpperCase() !== \"COMPLETED\" ||\n !job.bundle.available || !job.bundle.downloadUrl) {\n throw new BundleDownloadError(\"Bundle download requires a COMPLETED job with an available canonical bundle\");\n }\n try {\n await dependencies.downloadBundle(job.bundle.downloadUrl, download, {\n force,\n expectedSizeBytes: job.bundle.sizeBytes,\n expectedSha256: job.bundle.sha256,\n });\n } catch (error) {\n if (error instanceof BundleDownloadError) throw error;\n throw new BundleDownloadError(`Could not download bundle to ${download}`, { cause: error });\n }\n if (!json) dependencies.writeStdout(`Downloaded bundle to ${download}\\n`);\n }\n if (json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(job))}\\n`);\n return 0;\n}\n\nexport async function runJobsCommand(\n args: string[],\n dependencies: JobsCommandDependencies,\n): Promise<number> {\n const subcommand = args[0];\n if (subcommand === \"list\") return listCommand(args.slice(1), dependencies);\n if (subcommand === \"get\") return getCommand(args.slice(1), dependencies);\n throw new UsageError(\"Usage: mediaruntime jobs <list|get>\");\n}\n","import type {\n CreateJobParams,\n JobDetails,\n JobOutput,\n JobReceiptData,\n Metadata,\n OutputAlias,\n WaitForJobOptions,\n} from \"@mediaruntime/node\";\nimport { BundleDownloadError, UsageError } from \"../errors.js\";\n\nconst UNSUCCESSFUL_TERMINAL_STATUSES = new Set([\"FAILED\", \"REJECTED\", \"PARTIAL\"]);\nconst OUTPUT_ALIASES = new Set([\n \"video.web\",\n \"video.streaming\",\n \"video.social\",\n \"audio.web\",\n \"audio.transcription\",\n \"image.web\",\n]);\n\ninterface SubmittedJob extends JobReceiptData {\n wait(options?: WaitForJobOptions): Promise<JobDetails>;\n}\n\nexport interface RunJobsClient {\n create(params: CreateJobParams): Promise<SubmittedJob>;\n}\n\nexport interface RunCommandDependencies {\n jobs: RunJobsClient;\n writeStdout(text: string): void;\n downloadBundle(\n url: string,\n destination: string,\n options: { force: boolean; expectedSizeBytes?: number | null; expectedSha256?: string | null },\n ): Promise<void>;\n}\n\ninterface RunOptions {\n source: string;\n outputs: string[];\n metadata?: Metadata;\n idempotencyKey?: string;\n wait: boolean;\n timeoutMs?: number;\n download?: string;\n force: boolean;\n json: boolean;\n}\n\nfunction optionValue(args: string[], index: number, option: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(`${option} requires a value`);\n return value;\n}\n\nfunction parseMetadata(value: string): Metadata {\n let parsed: unknown;\n try {\n parsed = JSON.parse(value) as unknown;\n } catch {\n throw new UsageError(\"--metadata must be a valid JSON object\");\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new UsageError(\"--metadata must be a JSON object\");\n }\n return parsed as Metadata;\n}\n\nfunction parseRunOptions(args: string[]): RunOptions {\n let source: string | undefined;\n const outputs: string[] = [];\n let metadata: Metadata | undefined;\n let idempotencyKey: string | undefined;\n let wait = false;\n let timeoutMs: number | undefined;\n let download: string | undefined;\n let force = false;\n let json = false;\n\n for (let index = 0; index < args.length; index += 1) {\n const argument = args[index];\n if (argument === \"--output\" || argument === \"-o\") {\n outputs.push(optionValue(args, index, argument));\n index += 1;\n } else if (argument === \"--metadata\") {\n metadata = parseMetadata(optionValue(args, index, argument));\n index += 1;\n } else if (argument === \"--idempotency-key\") {\n idempotencyKey = optionValue(args, index, argument);\n index += 1;\n } else if (argument === \"--wait\") {\n wait = true;\n } else if (argument === \"--timeout-ms\") {\n const value = Number(optionValue(args, index, argument));\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new UsageError(\"--timeout-ms must be a positive integer\");\n }\n timeoutMs = value;\n index += 1;\n } else if (argument === \"--download\") {\n download = optionValue(args, index, argument);\n wait = true;\n index += 1;\n } else if (argument === \"--force\") {\n force = true;\n } else if (argument === \"--json\") {\n json = true;\n } else if (argument?.startsWith(\"-\")) {\n throw new UsageError(`Unknown run option: ${argument}`);\n } else if (source === undefined) {\n source = argument;\n } else {\n throw new UsageError(\"run accepts exactly one source\");\n }\n }\n\n if (!source) throw new UsageError(\"Usage: mediaruntime run <source> --output <alias> [--wait]\");\n if (outputs.length === 0) throw new UsageError(\"run requires at least one --output\");\n const unsupported = outputs.find((output) => !OUTPUT_ALIASES.has(output));\n if (unsupported) throw new UsageError(`Unsupported output alias: ${unsupported}`);\n if (force && !download) throw new UsageError(\"--force requires --download\");\n if (timeoutMs !== undefined && !wait) throw new UsageError(\"--timeout-ms requires --wait or --download\");\n return {\n source,\n outputs,\n ...(metadata === undefined ? {} : { metadata }),\n ...(idempotencyKey === undefined ? {} : { idempotencyKey }),\n wait,\n ...(timeoutMs === undefined ? {} : { timeoutMs }),\n ...(download === undefined ? {} : { download }),\n force,\n json,\n };\n}\n\nfunction receiptProjection(job: SubmittedJob): JobReceiptData {\n return {\n id: job.id,\n status: job.status,\n tier: job.tier,\n requiredTier: job.requiredTier,\n outputs: job.outputs,\n message: job.message,\n };\n}\n\nfunction detailsProjection(job: JobDetails): Record<string, unknown> {\n return {\n id: job.id,\n status: job.status,\n tier: job.tier,\n usage: job.usage,\n billing: job.billing,\n bundle: {\n available: job.bundle.available,\n expiresAt: job.bundle.expiresAt,\n sizeBytes: job.bundle.sizeBytes,\n sha256: job.bundle.sha256,\n retentionDays: job.bundle.retentionDays,\n },\n media: job.media,\n metadata: job.metadata,\n error: job.error,\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n startedAt: job.startedAt,\n completedAt: job.completedAt,\n };\n}\n\nfunction writeHumanReceipt(job: SubmittedJob, write: (text: string) => void): void {\n write(`Job: ${job.id}\\nStatus: ${job.status}\\nTier: ${job.tier || \"-\"}\\n`);\n}\n\nfunction writeHumanDetails(job: JobDetails, write: (text: string) => void): void {\n const error = job.error ? `\\nError: ${job.error}` : \"\";\n write(\n `Job: ${job.id}\\nStatus: ${job.status}\\n` +\n `Bundle: ${job.bundle.available ? \"available\" : \"unavailable\"}${error}\\n`,\n );\n}\n\nexport async function runCommand(\n args: string[],\n dependencies: RunCommandDependencies,\n): Promise<number> {\n const options = parseRunOptions(args);\n const params: CreateJobParams = {\n source: options.source,\n outputs: options.outputs as Array<OutputAlias | JobOutput>,\n ...(options.metadata === undefined ? {} : { metadata: options.metadata }),\n ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),\n };\n const submitted = await dependencies.jobs.create(params);\n\n if (!options.wait) {\n if (options.json) dependencies.writeStdout(`${JSON.stringify(receiptProjection(submitted))}\\n`);\n else writeHumanReceipt(submitted, dependencies.writeStdout);\n return UNSUCCESSFUL_TERMINAL_STATUSES.has(String(submitted.status).toUpperCase()) ? 6 : 0;\n }\n\n const details = await submitted.wait(\n options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs },\n );\n if (!options.json) writeHumanDetails(details, dependencies.writeStdout);\n if (String(details.status).toUpperCase() !== \"COMPLETED\") {\n if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(details))}\\n`);\n return 6;\n }\n if (options.download) {\n const url = details.bundle.downloadUrl;\n if (!details.bundle.available || !url) {\n throw new BundleDownloadError(\"Completed job does not have an available canonical bundle\");\n }\n try {\n await dependencies.downloadBundle(url, options.download, {\n force: options.force,\n expectedSizeBytes: details.bundle.sizeBytes,\n expectedSha256: details.bundle.sha256,\n });\n } catch (error) {\n if (error instanceof BundleDownloadError) throw error;\n throw new BundleDownloadError(`Could not download bundle to ${options.download}`, { cause: error });\n }\n if (!options.json) dependencies.writeStdout(`Downloaded bundle to ${options.download}\\n`);\n }\n if (options.json) dependencies.writeStdout(`${JSON.stringify(detailsProjection(details))}\\n`);\n return 0;\n}\n","import { randomBytes, randomUUID } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\n\nimport { parseLocalDestination } from \"../trigger/destination.js\";\nimport {\n SUPPORTED_TRIGGER_EVENTS,\n createSyntheticTerminalEvent,\n isTriggerEventType,\n type TriggerEventType,\n} from \"../trigger/event.js\";\nimport { signSyntheticWebhook } from \"../trigger/signature.js\";\nimport { TriggerDeliveryError, UsageError } from \"../errors.js\";\n\nexport interface TriggerCommandDependencies {\n env?: Readonly<Record<string, string | undefined>>;\n fetch?: typeof globalThis.fetch;\n now?: () => number;\n randomUUID?: () => string;\n randomBytes?: (size: number) => Uint8Array;\n readFile?: (path: string, encoding: \"utf8\") => Promise<string>;\n writeStdout?: (text: string) => void;\n}\n\ninterface TriggerArguments {\n type: TriggerEventType;\n destination: URL;\n secretFile?: string;\n generateSecret: boolean;\n jobId?: string;\n accountId?: string;\n json: boolean;\n}\n\nfunction requireOptionValue(args: string[], index: number, option: string): string {\n const value = args[index + 1];\n if (!value || value.startsWith(\"--\")) throw new UsageError(`${option} requires a value`);\n return value;\n}\n\nexport function parseTriggerArguments(args: string[]): TriggerArguments {\n const typeValue = args[0];\n if (!typeValue || typeValue.startsWith(\"--\")) {\n throw new UsageError(\n `trigger requires an event type: ${SUPPORTED_TRIGGER_EVENTS.join(\", \")}`,\n );\n }\n if (!isTriggerEventType(typeValue)) {\n throw new UsageError(\n `Unsupported trigger event ${JSON.stringify(typeValue)}; expected ${SUPPORTED_TRIGGER_EVENTS.join(\", \")}`,\n );\n }\n\n let to: string | undefined;\n let secretFile: string | undefined;\n let generateSecret = false;\n let jobId: string | undefined;\n let accountId: string | undefined;\n let json = false;\n for (let index = 1; index < args.length; index += 1) {\n const option = args[index];\n if (!option) continue;\n if (option === \"--generate-secret\") {\n generateSecret = true;\n continue;\n }\n if (option === \"--json\") {\n json = true;\n continue;\n }\n if ([\"--to\", \"--secret-file\", \"--job-id\", \"--account-id\"].includes(option)) {\n const value = requireOptionValue(args, index, option);\n index += 1;\n if (option === \"--to\") to = value;\n else if (option === \"--secret-file\") secretFile = value;\n else if (option === \"--job-id\") jobId = value;\n else accountId = value;\n continue;\n }\n throw new UsageError(`Unknown trigger option: ${option}`);\n }\n\n if (!to) throw new UsageError(\"trigger requires an explicit --to local URL\");\n if (generateSecret && secretFile) {\n throw new UsageError(\"--generate-secret and --secret-file are mutually exclusive\");\n }\n if (generateSecret && json) {\n throw new UsageError(\"--generate-secret and --json are mutually exclusive\");\n }\n if (jobId !== undefined && !jobId.trim()) throw new UsageError(\"--job-id must not be empty\");\n if (accountId !== undefined && !accountId.trim()) {\n throw new UsageError(\"--account-id must not be empty\");\n }\n return {\n type: typeValue,\n destination: parseLocalDestination(to),\n ...(secretFile === undefined ? {} : { secretFile }),\n generateSecret,\n ...(jobId === undefined ? {} : { jobId: jobId.trim() }),\n ...(accountId === undefined ? {} : { accountId: accountId.trim() }),\n json,\n };\n}\n\nasync function resolveSecret(\n args: TriggerArguments,\n dependencies: Required<\n Pick<TriggerCommandDependencies, \"env\" | \"randomBytes\" | \"readFile\" | \"writeStdout\">\n >,\n): Promise<string> {\n if (args.generateSecret) {\n const generated = Buffer.from(dependencies.randomBytes(32)).toString(\"base64url\");\n dependencies.writeStdout(\n `Generated webhook secret (configure the local receiver with this value): ${generated}\\n`,\n );\n return generated;\n }\n let secret: string | undefined;\n if (args.secretFile) {\n try {\n secret = await dependencies.readFile(args.secretFile, \"utf8\");\n } catch (error) {\n throw new UsageError(`Could not read webhook secret file: ${args.secretFile}`, { cause: error });\n }\n } else {\n secret = dependencies.env.MEDIARUNTIME_WEBHOOK_SECRET;\n }\n const normalized = secret?.trim();\n if (!normalized) {\n throw new UsageError(\n \"Set MEDIARUNTIME_WEBHOOK_SECRET, pass --secret-file, or explicitly use --generate-secret\",\n );\n }\n return normalized;\n}\n\nexport async function runTriggerCommand(\n args: string[],\n dependencies: TriggerCommandDependencies = {},\n): Promise<number> {\n const parsed = parseTriggerArguments(args);\n const deps = {\n env: dependencies.env ?? process.env,\n fetch: dependencies.fetch ?? globalThis.fetch,\n now: dependencies.now ?? Date.now,\n randomUUID: dependencies.randomUUID ?? randomUUID,\n randomBytes: dependencies.randomBytes ?? randomBytes,\n readFile: dependencies.readFile ?? readFile,\n writeStdout: dependencies.writeStdout ?? ((text: string) => process.stdout.write(text)),\n };\n const secret = await resolveSecret(parsed, deps);\n const timestamp = Math.floor(deps.now() / 1000);\n const unique = deps.randomUUID();\n const eventId = `evt_cli_${unique}`;\n const jobId = parsed.jobId ?? `job_cli_${unique}`;\n const accountId = parsed.accountId ?? \"acc_cli_local\";\n const payload = createSyntheticTerminalEvent(parsed.type, {\n eventId,\n jobId,\n accountId,\n timestamp,\n });\n const signed = signSyntheticWebhook(payload, { eventId, timestamp, secret });\n\n const response = await deps.fetch(parsed.destination, {\n method: \"POST\",\n headers: signed.headers,\n body: signed.body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new TriggerDeliveryError(\n `Local webhook endpoint returned HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : \"\"}`,\n );\n }\n if (parsed.json) {\n deps.writeStdout(`${JSON.stringify({\n eventId,\n type: parsed.type,\n jobId,\n status: payload.status,\n destination: parsed.destination.href,\n httpStatus: response.status,\n })}\\n`);\n } else {\n deps.writeStdout(\n `Sent synthetic ${parsed.type} event ${eventId} to ${parsed.destination.href} (HTTP ${response.status})\\n`,\n );\n }\n return 0;\n}\n","import { UsageError } from \"../errors.js\";\n\nexport function isLoopbackDestination(value: URL): boolean {\n let hostname = value.hostname.toLowerCase();\n if (hostname.endsWith(\".\")) hostname = hostname.slice(0, -1);\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n hostname = hostname.slice(1, -1);\n }\n if (hostname === \"localhost\" || hostname === \"::1\" || hostname === \"0:0:0:0:0:0:0:1\") {\n return true;\n }\n\n const octets = hostname.split(\".\");\n if (octets.length !== 4 || octets.some((part) => !/^\\d{1,3}$/.test(part))) return false;\n const numbers = octets.map(Number);\n return numbers[0] === 127 && numbers.every((part) => part >= 0 && part <= 255);\n}\n\nexport function parseLocalDestination(raw: string): URL {\n let destination: URL;\n try {\n destination = new URL(raw);\n } catch {\n throw new UsageError(\"--to must be an absolute local HTTP(S) URL\");\n }\n if (destination.protocol !== \"http:\" && destination.protocol !== \"https:\") {\n throw new UsageError(\"--to must use http:// or https://\");\n }\n if (destination.username || destination.password) {\n throw new UsageError(\"--to must not contain URL credentials\");\n }\n if (!isLoopbackDestination(destination)) {\n throw new UsageError(\n \"trigger only sends to loopback destinations (localhost, 127.0.0.0/8, or ::1)\",\n );\n }\n return destination;\n}\n","export const SUPPORTED_TRIGGER_EVENTS = [\n \"job.completed\",\n \"job.failed\",\n \"job.rejected\",\n] as const;\n\nexport type TriggerEventType = (typeof SUPPORTED_TRIGGER_EVENTS)[number];\n\nexport function isTriggerEventType(value: string): value is TriggerEventType {\n return (SUPPORTED_TRIGGER_EVENTS as readonly string[]).includes(value);\n}\n\nexport interface SyntheticEventOptions {\n eventId: string;\n jobId: string;\n accountId: string;\n timestamp: number;\n}\n\nexport function createSyntheticTerminalEvent(\n type: TriggerEventType,\n options: SyntheticEventOptions,\n): Record<string, unknown> {\n const status = type.slice(\"job.\".length).toUpperCase();\n const occurredAt = new Date(options.timestamp * 1000).toISOString();\n const base = {\n event_id: options.eventId,\n job_id: options.jobId,\n account_id: options.accountId,\n status,\n };\n\n if (type === \"job.completed\") {\n const expiresAt = new Date((options.timestamp + 7 * 24 * 60 * 60) * 1000).toISOString();\n return {\n ...base,\n completedAt: occurredAt,\n delivery: {\n mode: \"PULL\",\n retentionDays: 7,\n expiresAt,\n bundle: {\n type: \"zip\",\n filename: `${options.jobId}_outputs.zip`,\n size_bytes: 0,\n sha256: \"0\".repeat(64),\n download: {\n url: `http://127.0.0.1/__mediaruntime_synthetic__/${encodeURIComponent(options.jobId)}.zip`,\n expiresAt,\n },\n },\n layout: \"bundle_relative_v1\",\n deliverables: [],\n manifests: {},\n },\n billing: { status: \"PAID\" },\n usage: { units_total: 0 },\n meta: { synthetic: true, request_metadata: {} },\n };\n }\n\n const failed = type === \"job.failed\";\n return {\n ...base,\n [failed ? \"failedAt\" : \"rejectedAt\"]: occurredAt,\n delivery: { mode: \"NONE\" },\n billing: { status: \"RELEASED\" },\n error: {\n code: failed ? \"SYNTHETIC_ENGINE_FAILED\" : \"SYNTHETIC_REJECTED\",\n message: `Synthetic ${type} event from the MediaRuntime CLI`,\n },\n meta: { synthetic: true, request_metadata: {} },\n };\n}\n","import { createHmac } from \"node:crypto\";\n\nexport const WEBHOOK_HEADERS = {\n id: \"X-Transcoder-Id\",\n timestamp: \"X-Transcoder-Timestamp\",\n signature: \"X-Transcoder-Signature\",\n} as const;\n\nexport interface SignedWebhook {\n body: string;\n headers: Record<string, string>;\n}\n\nexport function signSyntheticWebhook(\n payload: Readonly<Record<string, unknown>>,\n options: { eventId: string; timestamp: number; secret: string },\n): SignedWebhook {\n if (!options.eventId.trim()) throw new Error(\"Webhook event ID must not be empty\");\n if (!Number.isInteger(options.timestamp) || options.timestamp < 0) {\n throw new Error(\"Webhook timestamp must be a non-negative integer\");\n }\n if (!options.secret) throw new Error(\"Webhook secret must not be empty\");\n\n // JSON is serialized once. These exact bytes are both signed and sent.\n const body = JSON.stringify(payload);\n const bodyBytes = Buffer.from(body, \"utf8\");\n const prefix = Buffer.from(`${options.timestamp}.${options.eventId}.`, \"utf8\");\n const digest = createHmac(\"sha256\", options.secret)\n .update(prefix)\n .update(bodyBytes)\n .digest(\"hex\");\n\n return {\n body,\n headers: {\n \"Content-Type\": \"application/json\",\n [WEBHOOK_HEADERS.id]: options.eventId,\n [WEBHOOK_HEADERS.timestamp]: String(options.timestamp),\n [WEBHOOK_HEADERS.signature]: `t=${options.timestamp},v1=${digest}`,\n },\n };\n}\n"],"mappings":";;;AAAA,SAAS,cAAAA,aAAY,cAAAC,mBAAkB;AACvC,SAAS,WAAW,oBAAoB;AACxC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAClC,SAAS,UAAU,SAAS,eAAe;AAC3C,SAAS,UAAU,iBAAiB;AACpC,SAAS,gBAAgB;AAEzB,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACnBA,IAAM,WAAN,cAAuB,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,EAET,YAAY,MAAc,SAAiB,UAAkB,SAA+B;AAC1F,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAEO,IAAM,aAAN,cAAyB,SAAS;AAAA,EACvC,YAAY,SAAiB,SAA+B;AAC1D,UAAM,eAAe,SAAS,GAAG,OAAO;AACxC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,uBAAN,cAAmC,SAAS;AAAA,EACjD,YAAY,SAAiB,SAA+B;AAC1D,UAAM,0BAA0B,SAAS,GAAG,OAAO;AACnD,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,sBAAN,cAAkC,SAAS;AAAA,EAChD,YAAY,SAAiB,SAA+B;AAC1D,UAAM,yBAAyB,SAAS,GAAG,OAAO;AAClD,SAAK,OAAO;AAAA,EACd;AACF;;;AC9BO,IAAM,oBAAoB;AA8B1B,IAAM,eAAN,cAA2B,SAAS;AAAA,EAChC;AAAA,EAET,YAAY,MAAc,SAAiB,QAAgB;AACzD,UAAM,MAAM,SAAS,UAAU,MAAM,IAAI,CAAC;AAC1C,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,SAAS,SAAS,SAAiB,MAAsB;AACvD,SAAO,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,iBAAiB,IAAI;AAC5D;AAEA,eAAe,gBAAgB,UAAsD;AACnF,MAAI;AACF,UAAM,QAAiB,MAAM,SAAS,KAAK;AAC3C,WAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC7D,QACA,CAAC;AAAA,EACP,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,KACb,SACA,MACA,MACA,QACkC;AAClC,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,SAAS,SAAS,IAAI,GAAG;AAAA,MAC9C,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAI,SAAS,EAAE,aAAa,OAAO,IAAI,CAAC;AAAA,MAC1C;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,IAAI,SAAS,yBAAyB,+CAA+C,GAAG,EAAE,OAAO,MAAM,CAAC;AAAA,EAChH;AACA,QAAM,UAAU,MAAM,gBAAgB,QAAQ;AAC9C,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,eAAe;AACrB,UAAM,IAAI;AAAA,MACR,OAAO,aAAa,UAAU,WAAW,aAAa,QAAQ;AAAA,MAC9D,OAAO,aAAa,WAAW,WAAW,aAAa,SAAS,mCAAmC,SAAS,MAAM;AAAA,MAClH,SAAS;AAAA,IACX;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,SAAkC,KAAqB;AAC5E,QAAM,QAAQ,QAAQ,GAAG;AACzB,MAAI,OAAO,UAAU,YAAY,CAAC,MAAO,OAAM,IAAI,SAAS,yBAAyB,4DAA4D,CAAC;AAClJ,SAAO;AACT;AAEA,eAAsB,mBAAmB,SAAiB,eAAqD;AAC7G,QAAM,UAAU,MAAM,KAAK,SAAS,SAAS,EAAE,eAAe,YAAY,mBAAmB,CAAC;AAC9F,SAAO;AAAA,IACL,YAAY,cAAc,SAAS,YAAY;AAAA,IAC/C,UAAU,cAAc,SAAS,UAAU;AAAA,IAC3C,iBAAiB,cAAc,SAAS,iBAAiB;AAAA,IACzD,yBAAyB,cAAc,SAAS,yBAAyB;AAAA,IACzE,WAAW,OAAO,QAAQ,SAAS;AAAA,IACnC,UAAU,OAAO,QAAQ,QAAQ;AAAA,EACnC;AACF;AAEA,eAAsB,sBAAsB,SAAiB,YAAoB,cAAiD;AAChI,QAAM,UAAU,MAAM,KAAK,SAAS,SAAS,EAAE,YAAY,aAAa,CAAC;AACzE,QAAM,iBAAiB,cAAc,SAAS,gBAAgB;AAC9D,MAAI,mBAAmB,MAAO,OAAM,IAAI,SAAS,yBAAyB,mDAAmD,CAAC;AAC9H,SAAO;AAAA,IACL,QAAQ,cAAc,SAAS,QAAQ;AAAA,IACvC,WAAW,cAAc,SAAS,WAAW;AAAA,IAC7C,OAAO,cAAc,SAAS,OAAO;AAAA,IACrC;AAAA,EACF;AACF;AAEA,eAAsB,kBAAkB,SAAiB,QAA2C;AAClG,QAAM,UAAU,MAAM,KAAK,SAAS,UAAU,CAAC,GAAG,MAAM;AACxD,QAAM,iBAAiB,cAAc,SAAS,gBAAgB;AAC9D,MAAI,mBAAmB,SAAS,mBAAmB,WAAW;AAC5D,UAAM,IAAI,SAAS,yBAAyB,mDAAmD,CAAC;AAAA,EAClG;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,WAAW,cAAc,SAAS,WAAW;AAAA,IAC7C,OAAO,cAAc,SAAS,OAAO;AAAA,IACrC;AAAA,EACF;AACF;AAEA,eAAsB,iBAAiB,SAAiB,QAA+B;AACrF,QAAM,KAAK,SAAS,UAAU,CAAC,GAAG,MAAM;AAC1C;;;ACvIA,SAAS,YAAY,mBAAmB;;;ACAxC,SAAS,aAAa;AAGtB,eAAsB,YAAY,KAA4B;AAC5D,QAAM,UAAU,QAAQ,aAAa,WACjC,EAAE,YAAY,QAAQ,MAAM,CAAC,GAAG,EAAE,IAClC,QAAQ,aAAa,UACnB,EAAE,YAAY,YAAY,MAAM,CAAC,+BAA+B,GAAG,EAAE,IACrE,EAAE,YAAY,YAAY,MAAM,CAAC,GAAG,EAAE;AAE5C,QAAM,IAAI,QAAc,CAACC,UAAS,WAAW;AAC3C,UAAM,QAAQ,MAAM,QAAQ,YAAY,QAAQ,MAAM;AAAA,MACpD,UAAU;AAAA,MACV,OAAO;AAAA,IACT,CAAC;AACD,UAAM,KAAK,SAAS,CAAC,UAAU,OAAO,IAAI;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,OAAO,MAAM;AAAA,IACjB,CAAC,CAAC;AACF,UAAM,KAAK,SAAS,MAAM;AACxB,YAAM,MAAM;AACZ,MAAAA,SAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;;;ACxBA,IAAM,UAAU;AAgBhB,SAAS,aAAa,SAAyB;AAC7C,SAAO,IAAI,IAAI,OAAO,EAAE,KAAK,YAAY;AAC3C;AAEA,SAAS,gBAAgB,OAA2C;AAClE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,OAAO;AACb,SAAO,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,WAAW,KAAK,KACpE,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS,KAC9D,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,SAAS,KACtD,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,SAAS,KAC1D,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS;AAClE;AAEA,eAAe,MAAM,SAAiB;AACpC,MAAI;AACF,UAAM,EAAE,MAAM,IAAI,MAAM,OAAO,kBAAkB;AACjD,WAAO,IAAI,MAAM,SAAS,aAAa,OAAO,CAAC;AAAA,EACjD,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,OAAO,MAAM;AAAA,IACjB;AAAA,EACF;AACF;AAEO,IAAM,oBAAN,MAAmD;AAAA,EACxD,MAAM,IAAI,SAAmD;AAC3D,QAAI;AACF,YAAM,OAAO,MAAM,MAAM,OAAO,GAAG,YAAY;AAC/C,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,UAAI,CAAC,gBAAgB,MAAM,KAAK,OAAO,YAAY,SAAS;AAC1D,cAAM,IAAI,MAAM,wCAAwC;AAAA,MAC1D;AACA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,eAAgB,iBAAiB,SAAS,MAAM,QAAQ,SAAS,eAAe,GAAI;AACvG,cAAM,IAAI,SAAS,4BAA4B,uFAAuF,CAAC;AAAA,MACzI;AACA,UAAI,iBAAiB,SAAU,OAAM;AAErC,UAAI,iBAAiB,SAAS,8CAA8C,KAAK,MAAM,OAAO,EAAG,QAAO;AACxG,YAAM,IAAI,SAAS,0BAA0B,wDAAwD,GAAG,EAAE,OAAO,MAAM,CAAC;AAAA,IAC1H;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,YAA6C;AACrD,QAAI;AACF,OAAC,MAAM,MAAM,WAAW,OAAO,GAAG,YAAY,KAAK,UAAU,UAAU,CAAC;AAAA,IAC1E,SAAS,OAAO;AACd,UAAI,iBAAiB,SAAU,OAAM;AACrC,YAAM,IAAI,SAAS,0BAA0B,qEAAqE,GAAG,EAAE,OAAO,MAAM,CAAC;AAAA,IACvI;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,SAAgC;AAC3C,QAAI;AACF,OAAC,MAAM,MAAM,OAAO,GAAG,eAAe;AAAA,IACxC,SAAS,OAAO;AACd,UAAI,iBAAiB,SAAU,OAAM;AACrC,UAAI,iBAAiB,SAAS,8CAA8C,KAAK,MAAM,OAAO,EAAG;AACjG,YAAM,IAAI,SAAS,0BAA0B,yEAAyE,GAAG,EAAE,OAAO,MAAM,CAAC;AAAA,IAC3I;AAAA,EACF;AACF;;;AF5DA,IAAM,eAAe,CAAC,iBAAyB,IAAI,QAAc,CAACC,aAAY,WAAWA,UAAS,YAAY,CAAC;AAE/G,SAAS,kBAAkB,UAA0B;AACnD,SAAO,WAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,WAAW;AACjE;AAEA,SAAS,kBAA0B;AACjC,SAAO,YAAY,EAAE,EAAE,SAAS,WAAW;AAC7C;AAEA,SAAS,MAAM,cAAwD;AACrE,SAAO,aAAa,mBAAmB,IAAI,kBAAkB;AAC/D;AAEA,SAAS,WAAW,MAAgB,SAAyB;AAC3D,QAAM,aAAa,KAAK,KAAK,CAAC,QAAQ,CAAC,QAAQ,SAAS,GAAG,CAAC;AAC5D,MAAI,WAAY,OAAM,IAAI,WAAW,kCAAkC,UAAU,EAAE;AACrF;AAEA,eAAsB,kBACpB,SACA,eAAwC,CAAC,GAC0D;AACnG,QAAM,cAAc,QAAQ,IAAI,sBAAsB,KAAK;AAC3D,MAAI,YAAa,QAAO,EAAE,QAAQ,aAAa,QAAQ,cAAc;AACrE,QAAM,SAAS,MAAM,MAAM,YAAY,EAAE,IAAI,OAAO;AACpD,SAAO,SAAS,EAAE,QAAQ,OAAO,QAAQ,QAAQ,YAAY,OAAO,IAAI;AAC1E;AAEA,eAAsB,gBACpB,MACA,SACA,eAAwC,CAAC,GACxB;AACjB,aAAW,MAAM,CAAC,cAAc,CAAC;AACjC,QAAM,YAAY,KAAK,SAAS,cAAc;AAC9C,QAAM,QAAQ,aAAa,gBAAgB,CAAC,SAAiB,QAAQ,OAAO,MAAM,IAAI;AACtF,QAAM,WAAW,MAAM,MAAM,YAAY,EAAE,IAAI,OAAO;AACtD,MAAI,UAAU;AACZ,QAAI;AACF,YAAM,kBAAkB,SAAS,SAAS,MAAM;AAChD,YAAM,wBAAwB,OAAO,aAAa,SAAS,SAAS;AAAA,CAAM;AAC1E,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,EAAE,iBAAiB,iBAAiB,MAAM,UAAU,IAAK,OAAM;AAAA,IACrE;AAAA,EACF;AAEA,QAAM,WAAW,aAAa,iBAAiB,KAAK,gBAAgB;AACpE,MAAI,SAAS,SAAS,MAAM,SAAS,SAAS,KAAK;AACjD,UAAM,IAAI,SAAS,oBAAoB,sCAAsC,CAAC;AAAA,EAChF;AACA,QAAM,gBAAgB,MAAM,mBAAmB,SAAS,kBAAkB,QAAQ,CAAC;AACnF,MAAI,CAAC,OAAO,SAAS,cAAc,SAAS,KAAK,cAAc,aAAa,KACxE,CAAC,OAAO,SAAS,cAAc,QAAQ,KAAK,cAAc,WAAW,GAAG;AAC1E,UAAM,IAAI,SAAS,yBAAyB,sDAAsD,CAAC;AAAA,EACrG;AAEA,QAAM;AAAA,EAAiD,cAAc,uBAAuB;AAAA;AAAA,CAAM;AAClG,QAAM,iBAAiB,cAAc,QAAQ;AAAA,CAAI;AACjD,MAAI,CAAC,UAAW,QAAO,aAAa,eAAe,aAAa,cAAc,uBAAuB;AACrG,QAAM,sCAAiC;AAEvC,QAAM,MAAM,aAAa,OAAO,KAAK;AACrC,QAAM,QAAQ,aAAa,SAAS;AACpC,QAAM,WAAW,IAAI,IAAI,cAAc,YAAY;AACnD,MAAI;AACJ,SAAO,IAAI,IAAI,UAAU;AACvB,QAAI;AACF,mBAAa,MAAM,sBAAsB,SAAS,cAAc,YAAY,QAAQ;AACpF;AAAA,IACF,SAAS,OAAO;AACd,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,yBAAyB;AAC3E,cAAM,MAAM,cAAc,WAAW,GAAI;AACzC;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACA,MAAI,CAAC,WAAY,OAAM,IAAI,SAAS,yBAAyB,+DAA+D,CAAC;AAE7H,MAAI;AACF,UAAM,kBAAkB,SAAS,WAAW,MAAM;AAClD,UAAM,MAAM,YAAY,EAAE,IAAI;AAAA,MAC5B,QAAQ,WAAW;AAAA,MACnB,WAAW,WAAW;AAAA,MACtB,OAAO,WAAW;AAAA,MAClB;AAAA,MACA,WAAW,IAAI,KAAK,IAAI,CAAC,EAAE,YAAY;AAAA,IACzC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,iBAAiB,SAAS,WAAW,MAAM,EAAE,MAAM,MAAM,MAAS;AACxE,UAAM;AAAA,EACR;AAEA,QAAM,gBAAgB,OAAO,aAAa,WAAW,SAAS;AAAA,CAAM;AACpE,MAAI,QAAQ,IAAI,sBAAsB,KAAK,GAAG;AAC5C,UAAM,yEAAyE;AAAA,EACjF;AACA,SAAO;AACT;AAEA,eAAsB,qBACpB,MACA,SACA,eAAwC,CAAC,GACxB;AACjB,aAAW,MAAM,CAAC,QAAQ,CAAC;AAC3B,QAAM,OAAO,KAAK,SAAS,QAAQ;AACnC,QAAM,QAAQ,aAAa,gBAAgB,CAAC,SAAiB,QAAQ,OAAO,MAAM,IAAI;AACtF,QAAM,WAAW,MAAM,kBAAkB,SAAS,YAAY;AAC9D,MAAI,CAAC,SAAU,OAAM,IAAI,SAAS,2BAA2B,sDAAsD,CAAC;AACpH,QAAM,SAAS,MAAM,kBAAkB,SAAS,SAAS,MAAM;AAC/D,MAAI,MAAM;AACR,UAAM,GAAG,KAAK,UAAU,EAAE,eAAe,MAAM,QAAQ,SAAS,QAAQ,GAAG,OAAO,CAAC,CAAC;AAAA,CAAI;AAAA,EAC1F,OAAO;AACL,UAAM,qBAAqB,SAAS,MAAM,aAAa,OAAO,SAAS,SAAS,OAAO,KAAK;AAAA,CAAM;AAAA,EACpG;AACA,SAAO;AACT;AAEA,eAAsB,iBACpB,MACA,SACA,eAAwC,CAAC,GACxB;AACjB,aAAW,MAAM,CAAC,cAAc,CAAC;AACjC,QAAM,YAAY,KAAK,SAAS,cAAc;AAC9C,QAAM,QAAQ,aAAa,gBAAgB,CAAC,SAAiB,QAAQ,OAAO,MAAM,IAAI;AACtF,QAAM,kBAAkB,MAAM,YAAY;AAC1C,QAAM,aAAa,MAAM,gBAAgB,IAAI,OAAO;AACpD,MAAI,CAAC,YAAY;AACf,UAAM,uBAAuB,OAAO;AAAA,CAAK;AACzC,QAAI,QAAQ,IAAI,sBAAsB,KAAK,EAAG,OAAM,sDAAsD;AAC1G,WAAO;AAAA,EACT;AACA,MAAI,CAAC,UAAW,OAAM,iBAAiB,SAAS,WAAW,MAAM;AACjE,QAAM,gBAAgB,OAAO,OAAO;AACpC,QAAM,YAAY,6DAA6D,8CAA8C;AAC7H,MAAI,QAAQ,IAAI,sBAAsB,KAAK,EAAG,OAAM,sDAAsD;AAC1G,SAAO;AACT;AAEA,eAAsB,eACpB,MACA,SACA,eAAwC,CAAC,GACxB;AACjB,QAAM,aAAa,KAAK,CAAC;AACzB,MAAI,eAAe,SAAU,OAAM,IAAI,WAAW,0CAA0C;AAC5F,SAAO,qBAAqB,KAAK,MAAM,CAAC,GAAG,SAAS,YAAY;AAClE;;;AG7KA,IAAM,iCAAiC,oBAAI,IAAI,CAAC,UAAU,YAAY,SAAS,CAAC;AAiBhF,SAAS,YAAY,MAAgB,OAAe,QAAwB;AAC1E,QAAM,QAAQ,KAAK,QAAQ,CAAC;AAC5B,MAAI,CAAC,SAAS,MAAM,WAAW,IAAI,EAAG,OAAM,IAAI,WAAW,GAAG,MAAM,mBAAmB;AACvF,SAAO;AACT;AAEA,SAAS,kBAAkB,KAA0C;AACnE,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ,MAAM,IAAI;AAAA,IACV,OAAO,IAAI;AAAA,IACX,SAAS,IAAI;AAAA,IACb,QAAQ;AAAA,MACN,WAAW,IAAI,OAAO;AAAA,MACtB,WAAW,IAAI,OAAO;AAAA,MACtB,WAAW,IAAI,OAAO;AAAA,MACtB,QAAQ,IAAI,OAAO;AAAA,MACnB,eAAe,IAAI,OAAO;AAAA,IAC5B;AAAA,IACA,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA,IACd,OAAO,IAAI;AAAA,IACX,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,EACnB;AACF;AAEA,SAAS,aAAa,KAAyB;AAC7C,QAAM,QAAQ;AAAA,IACZ,QAAQ,IAAI,EAAE;AAAA,IACd,WAAW,IAAI,MAAM;AAAA,IACrB,SAAS,IAAI,KAAK,UAAU,IAAI,KAAK,aAAa,IAAI,KAAK,aAAa,GAAG;AAAA,IAC3E,WAAW,IAAI,OAAO,YAAY,cAAc,aAAa;AAAA,EAC/D;AACA,MAAI,IAAI,OAAO,cAAc,KAAM,OAAM,KAAK,gBAAgB,IAAI,OAAO,SAAS,QAAQ;AAC1F,MAAI,IAAI,MAAO,OAAM,KAAK,UAAU,IAAI,KAAK,EAAE;AAC/C,SAAO,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA;AAC5B;AAEA,SAAS,KAAK,OAAwB;AACpC,MAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,GAAI,QAAO;AAClE,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,UAAU,MAAuB;AACxC,QAAM,OAAO,KAAK,KAAK,IAAI,CAAC,QAAQ;AAAA,IAClC,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,KAAK,IAAI,UAAU;AAAA,IACnB,KAAK,IAAI,UAAU;AAAA,IACnB,IAAI,kBAAkB,QAAQ;AAAA,IAC9B,KAAK,IAAI,SAAS;AAAA,EACpB,CAAC;AACD,QAAM,WAAW,CAAC,MAAM,UAAU,QAAQ,SAAS,UAAU,SAAS;AACtE,QAAM,SAAS,SAAS;AAAA,IAAI,CAAC,SAAS,UACpC,KAAK,IAAI,QAAQ,QAAQ,GAAG,KAAK,IAAI,CAAC,QAAQ,IAAI,KAAK,GAAG,UAAU,CAAC,CAAC;AAAA,EACxE;AACA,QAAM,SAAS,CAAC,QACd,IAAI,IAAI,CAAC,OAAO,UAAU,MAAM,OAAO,OAAO,KAAK,KAAK,MAAM,MAAM,CAAC,EAAE,KAAK,IAAI,EAAE,QAAQ;AAC5F,QAAM,QAAQ,CAAC,OAAO,QAAQ,GAAG,OAAO,OAAO,IAAI,CAAC,UAAU,IAAI,OAAO,KAAK,CAAC,CAAC,GAAG,GAAG,KAAK,IAAI,MAAM,CAAC;AACtG,MAAI,KAAK,WAAY,OAAM,KAAK,gBAAgB,KAAK,UAAU,EAAE;AACjE,SAAO,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA;AAC5B;AAEA,eAAe,YAAY,MAAgB,cAAwD;AACjG,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI,OAAO;AACX,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,WAAW,KAAK,KAAK;AAC3B,QAAI,aAAa,YAAY;AAC3B,eAAS,YAAY,MAAM,OAAO,QAAQ,EAAE,YAAY;AACxD,eAAS;AAAA,IACX,WAAW,aAAa,WAAW;AACjC,YAAM,QAAQ,OAAO,YAAY,MAAM,OAAO,QAAQ,CAAC;AACvD,UAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,KAAK,QAAQ,KAAK;AAC5D,cAAM,IAAI,WAAW,8CAA8C;AAAA,MACrE;AACA,cAAQ;AACR,eAAS;AAAA,IACX,WAAW,aAAa,YAAY;AAClC,eAAS,YAAY,MAAM,OAAO,QAAQ;AAC1C,eAAS;AAAA,IACX,WAAW,aAAa,UAAU;AAChC,aAAO;AAAA,IACT,OAAO;AACL,YAAM,IAAI,WAAW,6BAA6B,QAAQ,EAAE;AAAA,IAC9D;AAAA,EACF;AACA,QAAM,SAAyB;AAAA,IAC7B,GAAI,WAAW,SAAY,CAAC,IAAI,EAAE,OAA4B;AAAA,IAC9D,GAAI,UAAU,SAAY,CAAC,IAAI,EAAE,MAAM;AAAA,IACvC,GAAI,WAAW,SAAY,CAAC,IAAI,EAAE,OAAO;AAAA,EAC3C;AACA,QAAM,OAAO,MAAM,aAAa,KAAK,KAAK,MAAM;AAChD,eAAa,YAAY,OAAO,GAAG,KAAK,UAAU,IAAI,CAAC;AAAA,IAAO,UAAU,IAAI,CAAC;AAC7E,SAAO;AACT;AAEA,eAAe,WAAW,MAAgB,cAAwD;AAChG,MAAI;AACJ,MAAI;AACJ,MAAI,QAAQ;AACZ,MAAI,OAAO;AACX,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,WAAW,KAAK,KAAK;AAC3B,QAAI,aAAa,cAAc;AAC7B,iBAAW,YAAY,MAAM,OAAO,QAAQ;AAC5C,eAAS;AAAA,IACX,WAAW,aAAa,WAAW;AACjC,cAAQ;AAAA,IACV,WAAW,aAAa,UAAU;AAChC,aAAO;AAAA,IACT,WAAW,UAAU,WAAW,GAAG,GAAG;AACpC,YAAM,IAAI,WAAW,4BAA4B,QAAQ,EAAE;AAAA,IAC7D,WAAW,CAAC,OAAO;AACjB,cAAQ;AAAA,IACV,OAAO;AACL,YAAM,IAAI,WAAW,qCAAqC;AAAA,IAC5D;AAAA,EACF;AACA,MAAI,CAAC,MAAO,OAAM,IAAI,WAAW,uCAAuC;AACxE,MAAI,SAAS,CAAC,SAAU,OAAM,IAAI,WAAW,6BAA6B;AAC1E,QAAM,MAAM,MAAM,aAAa,KAAK,IAAI,KAAK;AAC7C,MAAI,CAAC,KAAM,cAAa,YAAY,aAAa,GAAG,CAAC;AAErD,MAAI,+BAA+B,IAAI,OAAO,IAAI,MAAM,EAAE,YAAY,CAAC,GAAG;AACxE,QAAI,KAAM,cAAa,YAAY,GAAG,KAAK,UAAU,kBAAkB,GAAG,CAAC,CAAC;AAAA,CAAI;AAChF,WAAO;AAAA,EACT;AACA,MAAI,UAAU;AACZ,QAAI,OAAO,IAAI,MAAM,EAAE,YAAY,MAAM,eACrC,CAAC,IAAI,OAAO,aAAa,CAAC,IAAI,OAAO,aAAa;AACpD,YAAM,IAAI,oBAAoB,6EAA6E;AAAA,IAC7G;AACA,QAAI;AACF,YAAM,aAAa,eAAe,IAAI,OAAO,aAAa,UAAU;AAAA,QAClE;AAAA,QACA,mBAAmB,IAAI,OAAO;AAAA,QAC9B,gBAAgB,IAAI,OAAO;AAAA,MAC7B,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,iBAAiB,oBAAqB,OAAM;AAChD,YAAM,IAAI,oBAAoB,gCAAgC,QAAQ,IAAI,EAAE,OAAO,MAAM,CAAC;AAAA,IAC5F;AACA,QAAI,CAAC,KAAM,cAAa,YAAY,wBAAwB,QAAQ;AAAA,CAAI;AAAA,EAC1E;AACA,MAAI,KAAM,cAAa,YAAY,GAAG,KAAK,UAAU,kBAAkB,GAAG,CAAC,CAAC;AAAA,CAAI;AAChF,SAAO;AACT;AAEA,eAAsB,eACpB,MACA,cACiB;AACjB,QAAM,aAAa,KAAK,CAAC;AACzB,MAAI,eAAe,OAAQ,QAAO,YAAY,KAAK,MAAM,CAAC,GAAG,YAAY;AACzE,MAAI,eAAe,MAAO,QAAO,WAAW,KAAK,MAAM,CAAC,GAAG,YAAY;AACvE,QAAM,IAAI,WAAW,qCAAqC;AAC5D;;;AC5KA,IAAMC,kCAAiC,oBAAI,IAAI,CAAC,UAAU,YAAY,SAAS,CAAC;AAChF,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAgCD,SAASC,aAAY,MAAgB,OAAe,QAAwB;AAC1E,QAAM,QAAQ,KAAK,QAAQ,CAAC;AAC5B,MAAI,CAAC,SAAS,MAAM,WAAW,IAAI,EAAG,OAAM,IAAI,WAAW,GAAG,MAAM,mBAAmB;AACvF,SAAO;AACT;AAEA,SAAS,cAAc,OAAyB;AAC9C,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,KAAK;AAAA,EAC3B,QAAQ;AACN,UAAM,IAAI,WAAW,wCAAwC;AAAA,EAC/D;AACA,MAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI,WAAW,kCAAkC;AAAA,EACzD;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,MAA4B;AACnD,MAAI;AACJ,QAAM,UAAoB,CAAC;AAC3B,MAAI;AACJ,MAAI;AACJ,MAAI,OAAO;AACX,MAAI;AACJ,MAAI;AACJ,MAAI,QAAQ;AACZ,MAAI,OAAO;AAEX,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,WAAW,KAAK,KAAK;AAC3B,QAAI,aAAa,cAAc,aAAa,MAAM;AAChD,cAAQ,KAAKA,aAAY,MAAM,OAAO,QAAQ,CAAC;AAC/C,eAAS;AAAA,IACX,WAAW,aAAa,cAAc;AACpC,iBAAW,cAAcA,aAAY,MAAM,OAAO,QAAQ,CAAC;AAC3D,eAAS;AAAA,IACX,WAAW,aAAa,qBAAqB;AAC3C,uBAAiBA,aAAY,MAAM,OAAO,QAAQ;AAClD,eAAS;AAAA,IACX,WAAW,aAAa,UAAU;AAChC,aAAO;AAAA,IACT,WAAW,aAAa,gBAAgB;AACtC,YAAM,QAAQ,OAAOA,aAAY,MAAM,OAAO,QAAQ,CAAC;AACvD,UAAI,CAAC,OAAO,cAAc,KAAK,KAAK,SAAS,GAAG;AAC9C,cAAM,IAAI,WAAW,yCAAyC;AAAA,MAChE;AACA,kBAAY;AACZ,eAAS;AAAA,IACX,WAAW,aAAa,cAAc;AACpC,iBAAWA,aAAY,MAAM,OAAO,QAAQ;AAC5C,aAAO;AACP,eAAS;AAAA,IACX,WAAW,aAAa,WAAW;AACjC,cAAQ;AAAA,IACV,WAAW,aAAa,UAAU;AAChC,aAAO;AAAA,IACT,WAAW,UAAU,WAAW,GAAG,GAAG;AACpC,YAAM,IAAI,WAAW,uBAAuB,QAAQ,EAAE;AAAA,IACxD,WAAW,WAAW,QAAW;AAC/B,eAAS;AAAA,IACX,OAAO;AACL,YAAM,IAAI,WAAW,gCAAgC;AAAA,IACvD;AAAA,EACF;AAEA,MAAI,CAAC,OAAQ,OAAM,IAAI,WAAW,4DAA4D;AAC9F,MAAI,QAAQ,WAAW,EAAG,OAAM,IAAI,WAAW,oCAAoC;AACnF,QAAM,cAAc,QAAQ,KAAK,CAAC,WAAW,CAAC,eAAe,IAAI,MAAM,CAAC;AACxE,MAAI,YAAa,OAAM,IAAI,WAAW,6BAA6B,WAAW,EAAE;AAChF,MAAI,SAAS,CAAC,SAAU,OAAM,IAAI,WAAW,6BAA6B;AAC1E,MAAI,cAAc,UAAa,CAAC,KAAM,OAAM,IAAI,WAAW,4CAA4C;AACvG,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;AAAA,IAC7C,GAAI,mBAAmB,SAAY,CAAC,IAAI,EAAE,eAAe;AAAA,IACzD;AAAA,IACA,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,UAAU;AAAA,IAC/C,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;AAAA,IAC7C;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,KAAmC;AAC5D,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ,MAAM,IAAI;AAAA,IACV,cAAc,IAAI;AAAA,IAClB,SAAS,IAAI;AAAA,IACb,SAAS,IAAI;AAAA,EACf;AACF;AAEA,SAASC,mBAAkB,KAA0C;AACnE,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ,MAAM,IAAI;AAAA,IACV,OAAO,IAAI;AAAA,IACX,SAAS,IAAI;AAAA,IACb,QAAQ;AAAA,MACN,WAAW,IAAI,OAAO;AAAA,MACtB,WAAW,IAAI,OAAO;AAAA,MACtB,WAAW,IAAI,OAAO;AAAA,MACtB,QAAQ,IAAI,OAAO;AAAA,MACnB,eAAe,IAAI,OAAO;AAAA,IAC5B;AAAA,IACA,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA,IACd,OAAO,IAAI;AAAA,IACX,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,EACnB;AACF;AAEA,SAAS,kBAAkB,KAAmB,OAAqC;AACjF,QAAM,QAAQ,IAAI,EAAE;AAAA,UAAa,IAAI,MAAM;AAAA,QAAW,IAAI,QAAQ,GAAG;AAAA,CAAI;AAC3E;AAEA,SAAS,kBAAkB,KAAiB,OAAqC;AAC/E,QAAM,QAAQ,IAAI,QAAQ;AAAA,SAAY,IAAI,KAAK,KAAK;AACpD;AAAA,IACE,QAAQ,IAAI,EAAE;AAAA,UAAa,IAAI,MAAM;AAAA,UACxB,IAAI,OAAO,YAAY,cAAc,aAAa,GAAG,KAAK;AAAA;AAAA,EACzE;AACF;AAEA,eAAsB,WACpB,MACA,cACiB;AACjB,QAAM,UAAU,gBAAgB,IAAI;AACpC,QAAM,SAA0B;AAAA,IAC9B,QAAQ,QAAQ;AAAA,IAChB,SAAS,QAAQ;AAAA,IACjB,GAAI,QAAQ,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,QAAQ,SAAS;AAAA,IACvE,GAAI,QAAQ,mBAAmB,SAAY,CAAC,IAAI,EAAE,gBAAgB,QAAQ,eAAe;AAAA,EAC3F;AACA,QAAM,YAAY,MAAM,aAAa,KAAK,OAAO,MAAM;AAEvD,MAAI,CAAC,QAAQ,MAAM;AACjB,QAAI,QAAQ,KAAM,cAAa,YAAY,GAAG,KAAK,UAAU,kBAAkB,SAAS,CAAC,CAAC;AAAA,CAAI;AAAA,QACzF,mBAAkB,WAAW,aAAa,WAAW;AAC1D,WAAOF,gCAA+B,IAAI,OAAO,UAAU,MAAM,EAAE,YAAY,CAAC,IAAI,IAAI;AAAA,EAC1F;AAEA,QAAM,UAAU,MAAM,UAAU;AAAA,IAC9B,QAAQ,cAAc,SAAY,CAAC,IAAI,EAAE,WAAW,QAAQ,UAAU;AAAA,EACxE;AACA,MAAI,CAAC,QAAQ,KAAM,mBAAkB,SAAS,aAAa,WAAW;AACtE,MAAI,OAAO,QAAQ,MAAM,EAAE,YAAY,MAAM,aAAa;AACxD,QAAI,QAAQ,KAAM,cAAa,YAAY,GAAG,KAAK,UAAUE,mBAAkB,OAAO,CAAC,CAAC;AAAA,CAAI;AAC5F,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,UAAU;AACpB,UAAM,MAAM,QAAQ,OAAO;AAC3B,QAAI,CAAC,QAAQ,OAAO,aAAa,CAAC,KAAK;AACrC,YAAM,IAAI,oBAAoB,2DAA2D;AAAA,IAC3F;AACA,QAAI;AACF,YAAM,aAAa,eAAe,KAAK,QAAQ,UAAU;AAAA,QACvD,OAAO,QAAQ;AAAA,QACf,mBAAmB,QAAQ,OAAO;AAAA,QAClC,gBAAgB,QAAQ,OAAO;AAAA,MACjC,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,iBAAiB,oBAAqB,OAAM;AAChD,YAAM,IAAI,oBAAoB,gCAAgC,QAAQ,QAAQ,IAAI,EAAE,OAAO,MAAM,CAAC;AAAA,IACpG;AACA,QAAI,CAAC,QAAQ,KAAM,cAAa,YAAY,wBAAwB,QAAQ,QAAQ;AAAA,CAAI;AAAA,EAC1F;AACA,MAAI,QAAQ,KAAM,cAAa,YAAY,GAAG,KAAK,UAAUA,mBAAkB,OAAO,CAAC,CAAC;AAAA,CAAI;AAC5F,SAAO;AACT;;;ACtOA,SAAS,eAAAC,cAAa,kBAAkB;AACxC,SAAS,gBAAgB;;;ACClB,SAAS,sBAAsB,OAAqB;AACzD,MAAI,WAAW,MAAM,SAAS,YAAY;AAC1C,MAAI,SAAS,SAAS,GAAG,EAAG,YAAW,SAAS,MAAM,GAAG,EAAE;AAC3D,MAAI,SAAS,WAAW,GAAG,KAAK,SAAS,SAAS,GAAG,GAAG;AACtD,eAAW,SAAS,MAAM,GAAG,EAAE;AAAA,EACjC;AACA,MAAI,aAAa,eAAe,aAAa,SAAS,aAAa,mBAAmB;AACpF,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,SAAS,MAAM,GAAG;AACjC,MAAI,OAAO,WAAW,KAAK,OAAO,KAAK,CAAC,SAAS,CAAC,YAAY,KAAK,IAAI,CAAC,EAAG,QAAO;AAClF,QAAM,UAAU,OAAO,IAAI,MAAM;AACjC,SAAO,QAAQ,CAAC,MAAM,OAAO,QAAQ,MAAM,CAAC,SAAS,QAAQ,KAAK,QAAQ,GAAG;AAC/E;AAEO,SAAS,sBAAsB,KAAkB;AACtD,MAAI;AACJ,MAAI;AACF,kBAAc,IAAI,IAAI,GAAG;AAAA,EAC3B,QAAQ;AACN,UAAM,IAAI,WAAW,4CAA4C;AAAA,EACnE;AACA,MAAI,YAAY,aAAa,WAAW,YAAY,aAAa,UAAU;AACzE,UAAM,IAAI,WAAW,mCAAmC;AAAA,EAC1D;AACA,MAAI,YAAY,YAAY,YAAY,UAAU;AAChD,UAAM,IAAI,WAAW,uCAAuC;AAAA,EAC9D;AACA,MAAI,CAAC,sBAAsB,WAAW,GAAG;AACvC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACrCO,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF;AAIO,SAAS,mBAAmB,OAA0C;AAC3E,SAAQ,yBAA+C,SAAS,KAAK;AACvE;AASO,SAAS,6BACd,MACA,SACyB;AACzB,QAAM,SAAS,KAAK,MAAM,OAAO,MAAM,EAAE,YAAY;AACrD,QAAM,aAAa,IAAI,KAAK,QAAQ,YAAY,GAAI,EAAE,YAAY;AAClE,QAAM,OAAO;AAAA,IACX,UAAU,QAAQ;AAAA,IAClB,QAAQ,QAAQ;AAAA,IAChB,YAAY,QAAQ;AAAA,IACpB;AAAA,EACF;AAEA,MAAI,SAAS,iBAAiB;AAC5B,UAAM,YAAY,IAAI,MAAM,QAAQ,YAAY,IAAI,KAAK,KAAK,MAAM,GAAI,EAAE,YAAY;AACtF,WAAO;AAAA,MACL,GAAG;AAAA,MACH,aAAa;AAAA,MACb,UAAU;AAAA,QACR,MAAM;AAAA,QACN,eAAe;AAAA,QACf;AAAA,QACA,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,UAAU,GAAG,QAAQ,KAAK;AAAA,UAC1B,YAAY;AAAA,UACZ,QAAQ,IAAI,OAAO,EAAE;AAAA,UACrB,UAAU;AAAA,YACR,KAAK,+CAA+C,mBAAmB,QAAQ,KAAK,CAAC;AAAA,YACrF;AAAA,UACF;AAAA,QACF;AAAA,QACA,QAAQ;AAAA,QACR,cAAc,CAAC;AAAA,QACf,WAAW,CAAC;AAAA,MACd;AAAA,MACA,SAAS,EAAE,QAAQ,OAAO;AAAA,MAC1B,OAAO,EAAE,aAAa,EAAE;AAAA,MACxB,MAAM,EAAE,WAAW,MAAM,kBAAkB,CAAC,EAAE;AAAA,IAChD;AAAA,EACF;AAEA,QAAM,SAAS,SAAS;AACxB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,CAAC,SAAS,aAAa,YAAY,GAAG;AAAA,IACtC,UAAU,EAAE,MAAM,OAAO;AAAA,IACzB,SAAS,EAAE,QAAQ,WAAW;AAAA,IAC9B,OAAO;AAAA,MACL,MAAM,SAAS,4BAA4B;AAAA,MAC3C,SAAS,aAAa,IAAI;AAAA,IAC5B;AAAA,IACA,MAAM,EAAE,WAAW,MAAM,kBAAkB,CAAC,EAAE;AAAA,EAChD;AACF;;;ACzEA,SAAS,kBAAkB;AAEpB,IAAM,kBAAkB;AAAA,EAC7B,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,WAAW;AACb;AAOO,SAAS,qBACd,SACA,SACe;AACf,MAAI,CAAC,QAAQ,QAAQ,KAAK,EAAG,OAAM,IAAI,MAAM,oCAAoC;AACjF,MAAI,CAAC,OAAO,UAAU,QAAQ,SAAS,KAAK,QAAQ,YAAY,GAAG;AACjE,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AACA,MAAI,CAAC,QAAQ,OAAQ,OAAM,IAAI,MAAM,kCAAkC;AAGvE,QAAM,OAAO,KAAK,UAAU,OAAO;AACnC,QAAM,YAAY,OAAO,KAAK,MAAM,MAAM;AAC1C,QAAM,SAAS,OAAO,KAAK,GAAG,QAAQ,SAAS,IAAI,QAAQ,OAAO,KAAK,MAAM;AAC7E,QAAM,SAAS,WAAW,UAAU,QAAQ,MAAM,EAC/C,OAAO,MAAM,EACb,OAAO,SAAS,EAChB,OAAO,KAAK;AAEf,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,CAAC,gBAAgB,EAAE,GAAG,QAAQ;AAAA,MAC9B,CAAC,gBAAgB,SAAS,GAAG,OAAO,QAAQ,SAAS;AAAA,MACrD,CAAC,gBAAgB,SAAS,GAAG,KAAK,QAAQ,SAAS,OAAO,MAAM;AAAA,IAClE;AAAA,EACF;AACF;;;AHRA,SAAS,mBAAmB,MAAgB,OAAe,QAAwB;AACjF,QAAM,QAAQ,KAAK,QAAQ,CAAC;AAC5B,MAAI,CAAC,SAAS,MAAM,WAAW,IAAI,EAAG,OAAM,IAAI,WAAW,GAAG,MAAM,mBAAmB;AACvF,SAAO;AACT;AAEO,SAAS,sBAAsB,MAAkC;AACtE,QAAM,YAAY,KAAK,CAAC;AACxB,MAAI,CAAC,aAAa,UAAU,WAAW,IAAI,GAAG;AAC5C,UAAM,IAAI;AAAA,MACR,mCAAmC,yBAAyB,KAAK,IAAI,CAAC;AAAA,IACxE;AAAA,EACF;AACA,MAAI,CAAC,mBAAmB,SAAS,GAAG;AAClC,UAAM,IAAI;AAAA,MACR,6BAA6B,KAAK,UAAU,SAAS,CAAC,cAAc,yBAAyB,KAAK,IAAI,CAAC;AAAA,IACzG;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACJ,MAAI,iBAAiB;AACrB,MAAI;AACJ,MAAI;AACJ,MAAI,OAAO;AACX,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,SAAS,KAAK,KAAK;AACzB,QAAI,CAAC,OAAQ;AACb,QAAI,WAAW,qBAAqB;AAClC,uBAAiB;AACjB;AAAA,IACF;AACA,QAAI,WAAW,UAAU;AACvB,aAAO;AACP;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,iBAAiB,YAAY,cAAc,EAAE,SAAS,MAAM,GAAG;AAC1E,YAAM,QAAQ,mBAAmB,MAAM,OAAO,MAAM;AACpD,eAAS;AACT,UAAI,WAAW,OAAQ,MAAK;AAAA,eACnB,WAAW,gBAAiB,cAAa;AAAA,eACzC,WAAW,WAAY,SAAQ;AAAA,UACnC,aAAY;AACjB;AAAA,IACF;AACA,UAAM,IAAI,WAAW,2BAA2B,MAAM,EAAE;AAAA,EAC1D;AAEA,MAAI,CAAC,GAAI,OAAM,IAAI,WAAW,6CAA6C;AAC3E,MAAI,kBAAkB,YAAY;AAChC,UAAM,IAAI,WAAW,4DAA4D;AAAA,EACnF;AACA,MAAI,kBAAkB,MAAM;AAC1B,UAAM,IAAI,WAAW,qDAAqD;AAAA,EAC5E;AACA,MAAI,UAAU,UAAa,CAAC,MAAM,KAAK,EAAG,OAAM,IAAI,WAAW,4BAA4B;AAC3F,MAAI,cAAc,UAAa,CAAC,UAAU,KAAK,GAAG;AAChD,UAAM,IAAI,WAAW,gCAAgC;AAAA,EACvD;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa,sBAAsB,EAAE;AAAA,IACrC,GAAI,eAAe,SAAY,CAAC,IAAI,EAAE,WAAW;AAAA,IACjD;AAAA,IACA,GAAI,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,MAAM,KAAK,EAAE;AAAA,IACrD,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,WAAW,UAAU,KAAK,EAAE;AAAA,IACjE;AAAA,EACF;AACF;AAEA,eAAe,cACb,MACA,cAGiB;AACjB,MAAI,KAAK,gBAAgB;AACvB,UAAM,YAAY,OAAO,KAAK,aAAa,YAAY,EAAE,CAAC,EAAE,SAAS,WAAW;AAChF,iBAAa;AAAA,MACX,4EAA4E,SAAS;AAAA;AAAA,IACvF;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI,KAAK,YAAY;AACnB,QAAI;AACF,eAAS,MAAM,aAAa,SAAS,KAAK,YAAY,MAAM;AAAA,IAC9D,SAAS,OAAO;AACd,YAAM,IAAI,WAAW,uCAAuC,KAAK,UAAU,IAAI,EAAE,OAAO,MAAM,CAAC;AAAA,IACjG;AAAA,EACF,OAAO;AACL,aAAS,aAAa,IAAI;AAAA,EAC5B;AACA,QAAM,aAAa,QAAQ,KAAK;AAChC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,kBACpB,MACA,eAA2C,CAAC,GAC3B;AACjB,QAAM,SAAS,sBAAsB,IAAI;AACzC,QAAM,OAAO;AAAA,IACX,KAAK,aAAa,OAAO,QAAQ;AAAA,IACjC,OAAO,aAAa,SAAS,WAAW;AAAA,IACxC,KAAK,aAAa,OAAO,KAAK;AAAA,IAC9B,YAAY,aAAa,cAAc;AAAA,IACvC,aAAa,aAAa,eAAeC;AAAA,IACzC,UAAU,aAAa,YAAY;AAAA,IACnC,aAAa,aAAa,gBAAgB,CAAC,SAAiB,QAAQ,OAAO,MAAM,IAAI;AAAA,EACvF;AACA,QAAM,SAAS,MAAM,cAAc,QAAQ,IAAI;AAC/C,QAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC9C,QAAM,SAAS,KAAK,WAAW;AAC/B,QAAM,UAAU,WAAW,MAAM;AACjC,QAAM,QAAQ,OAAO,SAAS,WAAW,MAAM;AAC/C,QAAM,YAAY,OAAO,aAAa;AACtC,QAAM,UAAU,6BAA6B,OAAO,MAAM;AAAA,IACxD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,SAAS,qBAAqB,SAAS,EAAE,SAAS,WAAW,OAAO,CAAC;AAE3E,QAAM,WAAW,MAAM,KAAK,MAAM,OAAO,aAAa;AAAA,IACpD,QAAQ;AAAA,IACR,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO;AAAA,IACb,UAAU;AAAA,IACV,QAAQ,YAAY,QAAQ,GAAM;AAAA,EACpC,CAAC;AACD,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR,wCAAwC,SAAS,MAAM,GAAG,SAAS,aAAa,IAAI,SAAS,UAAU,KAAK,EAAE;AAAA,IAChH;AAAA,EACF;AACA,MAAI,OAAO,MAAM;AACf,SAAK,YAAY,GAAG,KAAK,UAAU;AAAA,MACjC;AAAA,MACA,MAAM,OAAO;AAAA,MACb;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,aAAa,OAAO,YAAY;AAAA,MAChC,YAAY,SAAS;AAAA,IACvB,CAAC,CAAC;AAAA,CAAI;AAAA,EACR,OAAO;AACL,SAAK;AAAA,MACH,kBAAkB,OAAO,IAAI,UAAU,OAAO,OAAO,OAAO,YAAY,IAAI,UAAU,SAAS,MAAM;AAAA;AAAA,IACvG;AAAA,EACF;AACA,SAAO;AACT;;;AR1IA,IAAM,UAAU;AAChB,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgDb,SAAS,kBAAkB,OAAyB;AAClD,SAAO,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS;AACrE;AAEA,eAAe,OAAO,MAAgC;AACpD,MAAI;AACF,UAAM,OAAO,MAAM,UAAU,IAAI;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,yBACpB,KACA,aACA,SAKe;AACf,QAAM,SAAS,QAAQ,WAAW;AAClC,MAAI,CAAC,QAAQ,SAAS,MAAM,OAAO,MAAM,GAAG;AAC1C,UAAM,IAAI,oBAAoB,wCAAwC,WAAW,EAAE;AAAA,EACrF;AACA,QAAM,YAAY;AAAA,IAChB,QAAQ,MAAM;AAAA,IACd,IAAI,SAAS,MAAM,CAAC,IAAIC,YAAW,CAAC;AAAA,EACtC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,GAAG;AAAA,IAC5B,SAAS,OAAO;AACd,YAAM,IAAI,oBAAoB,sDAAsD,EAAE,OAAO,MAAM,CAAC;AAAA,IACtG;AACA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,MAAM,OAAO;AAC5B,YAAM,IAAI,oBAAoB,oCAAoC,SAAS,MAAM,EAAE;AAAA,IACrF;AACA,QAAI,CAAC,SAAS,KAAM,OAAM,IAAI,oBAAoB,iDAAiD;AACnG,UAAM,OAAOC,YAAW,QAAQ;AAChC,QAAI,YAAY;AAChB,UAAM,YAAY,IAAI,UAAU;AAAA,MAC9B,UAAU,OAAe,WAAW,UAAU;AAC5C,qBAAa,MAAM;AACnB,aAAK,OAAO,KAAK;AACjB,iBAAS,MAAM,KAAK;AAAA,MACtB;AAAA,IACF,CAAC;AACD,UAAM;AAAA,MACJ,SAAS,QAAQ,SAAS,IAAiD;AAAA,MAC3E;AAAA,MACA,kBAAkB,WAAW,EAAE,OAAO,MAAM,MAAM,IAAM,CAAC;AAAA,IAC3D;AACA,UAAM,SAAS,KAAK,OAAO,KAAK;AAChC,QAAI,QAAQ,sBAAsB,UAAa,QAAQ,sBAAsB,QACzE,cAAc,QAAQ,mBAAmB;AAC3C,YAAM,IAAI;AAAA,QACR,kCAAkC,QAAQ,iBAAiB,oBAAoB,SAAS;AAAA,MAC1F;AAAA,IACF;AACA,QAAI,QAAQ,kBAAkB,WAAW,QAAQ,eAAe,YAAY,GAAG;AAC7E,YAAM,IAAI,oBAAoB,yBAAyB;AAAA,IACzD;AAEA,QAAI,QAAQ,OAAO;AACjB,YAAM,OAAO,WAAW,MAAM;AAAA,IAChC,OAAO;AACL,UAAI;AAGF,cAAM,KAAK,WAAW,MAAM;AAAA,MAC9B,SAAS,OAAO;AACd,YAAI,kBAAkB,KAAK,GAAG;AAC5B,gBAAM,IAAI,oBAAoB,wCAAwC,WAAW,EAAE;AAAA,QACrF;AACA,cAAM;AAAA,MACR;AACA,YAAM,GAAG,SAAS;AAAA,IACpB;AAAA,EACF,SAAS,OAAO;AACd,UAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AACnC,QAAI,iBAAiB,oBAAqB,OAAM;AAChD,UAAM,IAAI,oBAAoB,6BAA6B,WAAW,IAAI,EAAE,OAAO,MAAM,CAAC;AAAA,EAC5F;AACF;AAEA,SAAS,iBAAiB,KAAqB;AAC7C,MAAI;AACJ,MAAI;AACF,YAAQ,IAAI,IAAI,GAAG;AAAA,EACrB,QAAQ;AACN,UAAM,IAAI,WAAW,+CAA+C;AAAA,EACtE;AACA,MAAI,MAAM,YAAY,MAAM,YAAY,MAAM,UAAU,MAAM,QAAQ,MAAM,aAAa,KAAK;AAC5F,UAAM,IAAI,WAAW,4EAA4E;AAAA,EACnG;AACA,MAAI,MAAM,aAAa,YAAY,EAAE,MAAM,aAAa,WAAW,sBAAsB,KAAK,IAAI;AAChG,UAAM,IAAI,WAAW,wDAAwD;AAAA,EAC/E;AACA,SAAO,MAAM;AACf;AAEA,SAAS,qBAAqB,MAAsD;AAClF,QAAM,OAAO,CAAC,GAAG,IAAI;AACrB,MAAI;AACJ,SAAO,KAAK,CAAC,MAAM,cAAc;AAC/B,UAAM,QAAQ,KAAK,CAAC;AACpB,QAAI,CAAC,SAAS,MAAM,WAAW,IAAI,EAAG,OAAM,IAAI,WAAW,6BAA6B;AACxF,cAAU,iBAAiB,KAAK;AAChC,SAAK,OAAO,GAAG,CAAC;AAAA,EAClB;AACA,SAAO,EAAE,GAAI,YAAY,SAAY,CAAC,IAAI,EAAE,QAAQ,GAAI,KAAK;AAC/D;AAEA,SAAS,aAAa,OAAsD;AAC1E,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,SAAS,MAAM;AAAA,IACf,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM;AAAA,IACjB,WAAW,MAAM,aAAa;AAAA,IAC9B,OAAO,MAAM,SAAS;AAAA,EACxB;AACF;AAEA,SAAS,YAAY,OAAwB;AAC3C,MAAI,iBAAiB,SAAU,QAAO,MAAM;AAC5C,MAAI,iBAAiB,oBAAqB,QAAO;AACjD,MAAI,iBAAiB,4BAA6B,QAAO;AACzD,MAAI,iBAAiB,sBAAsB;AACzC,QAAI,CAAC,KAAK,KAAK,GAAG,EAAE,SAAS,MAAM,MAAM,EAAG,QAAO;AACnD,QAAI,MAAM,UAAU,IAAK,QAAO;AAChC,WAAO;AAAA,EACT;AACA,MAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAAc,QAAO;AACzE,MAAI,iBAAiB,aAAa,gCAAgC,KAAK,MAAM,OAAO,EAAG,QAAO;AAC9F,SAAO;AACT;AAEA,SAAS,UAAU,OAAgB,UAA2C;AAC5E,MAAI,iBAAiB,qBAAsB,QAAO,EAAE,GAAG,aAAa,KAAK,GAAG,SAAS;AACrF,SAAO;AAAA,IACL,MAAM,iBAAiB,WAAW,MAAM,OAAO;AAAA,IAC/C,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAClD;AAAA,EACF;AACF;AAEA,eAAsB,WACpB,MACA,eAAgC,CAAC,GAChB;AACjB,QAAM,cAAc,aAAa,gBAAgB,CAAC,SAAiB,QAAQ,OAAO,MAAM,IAAI;AAC5F,QAAM,cAAc,aAAa,gBAAgB,CAAC,SAAiB,QAAQ,OAAO,MAAM,IAAI;AAC5F,QAAM,OAAO,KAAK,SAAS,QAAQ;AACnC,MAAI;AACF,UAAM,SAAS,qBAAqB,IAAI;AACxC,UAAM,UAAU,OAAO,KAAK,CAAC;AAC7B,QAAI,CAAC,WAAW,YAAY,YAAY,YAAY,MAAM;AACxD,kBAAY,IAAI;AAChB,aAAO;AAAA,IACT;AACA,QAAI,YAAY,eAAe,YAAY,MAAM;AAC/C,kBAAY,GAAG,OAAO;AAAA,CAAI;AAC1B,aAAO;AAAA,IACT;AACA,QAAI,YAAY,WAAW;AACzB,aAAO,MAAM,kBAAkB,OAAO,KAAK,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC;AAAA,IACtE;AAEA,UAAM,oBAAoB,OAAO,WAAW,QAAQ,IAAI;AACxD,UAAM,UAAU,oBAAoB,iBAAiB,iBAAiB,IAAI;AAC1E,UAAM,cAAc,WAAW;AAC/B,UAAM,mBAA4C;AAAA,MAChD,GAAI,aAAa,kBAAkB,EAAE,iBAAiB,aAAa,gBAAgB,IAAI,CAAC;AAAA,MACxF,GAAI,aAAa,cAAc,EAAE,aAAa,aAAa,YAAY,IAAI,CAAC;AAAA,MAC5E,GAAI,aAAa,QAAQ,EAAE,OAAO,aAAa,MAAM,IAAI,CAAC;AAAA,MAC1D,GAAI,aAAa,MAAM,EAAE,KAAK,aAAa,IAAI,IAAI,CAAC;AAAA,MACpD,GAAI,aAAa,iBAAiB,EAAE,gBAAgB,aAAa,eAAe,IAAI,CAAC;AAAA,MACrF;AAAA,IACF;AACA,QAAI,YAAY,QAAS,QAAO,MAAM,gBAAgB,OAAO,KAAK,MAAM,CAAC,GAAG,aAAa,gBAAgB;AACzG,QAAI,YAAY,OAAQ,QAAO,MAAM,eAAe,OAAO,KAAK,MAAM,CAAC,GAAG,aAAa,gBAAgB;AACvG,QAAI,YAAY,SAAU,QAAO,MAAM,iBAAiB,OAAO,KAAK,MAAM,CAAC,GAAG,aAAa,gBAAgB;AAE3G,QAAI,SAAS,QAAQ,IAAI,sBAAsB,KAAK;AACpD,QAAI,CAAC,WAAW,CAAC,aAAa,gBAAgB,aAAa,kBAAkB;AAC3E,gBAAU,MAAM,kBAAkB,aAAa,gBAAgB,IAAI;AAAA,IACrE;AACA,QAAI,CAAC,aAAa,gBAAgB,CAAC,QAAQ;AACzC,YAAM,IAAI,WAAW,6EAA6E;AAAA,IACpG;AACA,UAAM,SAAS,aAAa,eAAe;AAAA,MACzC,GAAI,YAAY,SAAY,CAAC,IAAI,EAAE,QAAQ;AAAA,MAC3C,GAAI,WAAW,SAAY,CAAC,IAAI,EAAE,OAAO;AAAA,IAC3C,CAAC,KAAK,IAAI,aAAa;AAAA,MACrB,GAAI,YAAY,SAAY,CAAC,IAAI,EAAE,QAAQ;AAAA,MAC3C,GAAI,WAAW,SAAY,CAAC,IAAI,EAAE,OAAO;AAAA,IAC3C,CAAC;AACD,UAAM,iBAAiB,aAAa,kBAAkB;AACtD,UAAM,sBAAsB,EAAE,MAAM,OAAO,MAAM,aAAa,eAAe;AAC7E,QAAI,YAAY,MAAO,QAAO,MAAM,WAAW,OAAO,KAAK,MAAM,CAAC,GAAG,mBAAmB;AACxF,QAAI,YAAY,QAAQ;AACtB,aAAO,MAAM,eAAe,OAAO,KAAK,MAAM,CAAC,GAAG,mBAAmB;AAAA,IACvE;AACA,UAAM,IAAI,WAAW,oBAAoB,OAAO,EAAE;AAAA,EACpD,SAAS,OAAO;AACd,UAAM,WAAW,YAAY,KAAK;AAClC,QAAI,MAAM;AACR,kBAAY,GAAG,KAAK,UAAU,EAAE,OAAO,UAAU,OAAO,QAAQ,EAAE,CAAC,CAAC;AAAA,CAAI;AAAA,IAC1E,OAAO;AACL,YAAM,YAAY,iBAAiB,wBAAwB,MAAM,YAC7D,aAAa,MAAM,SAAS,MAC5B;AACJ,kBAAY,UAAU,iBAAiB,QAAQ,MAAM,UAAU,eAAe,GAAG,SAAS;AAAA,CAAI;AAAA,IAChG;AACA,WAAO;AAAA,EACT;AACF;AAEA,IAAI,eAAe;AACnB,IAAI,QAAQ,KAAK,CAAC,MAAM,QAAW;AACjC,MAAI;AAGF,mBAAe,aAAa,QAAQ,KAAK,CAAC,CAAC,MAAM,aAAa,cAAc,YAAY,GAAG,CAAC;AAAA,EAC9F,QAAQ;AACN,mBAAe;AAAA,EACjB;AACF;AACA,IAAI,aAAc,SAAQ,WAAW,MAAM,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;","names":["createHash","randomUUID","resolve","resolve","UNSUCCESSFUL_TERMINAL_STATUSES","optionValue","detailsProjection","randomBytes","randomBytes","randomUUID","createHash"]}
package/docs/AUTH.md ADDED
@@ -0,0 +1,85 @@
1
+ # CLI browser authentication
2
+
3
+ ## Outcome
4
+
5
+ `mediaruntime login` authenticates an interactive developer through the existing
6
+ MediaRuntime website and stores a dedicated, revocable CLI API key in the operating
7
+ system credential vault. `MEDIARUNTIME_API_KEY` remains permanently supported and takes
8
+ precedence over a stored login for CI, servers, containers, and intentional per-command
9
+ account selection.
10
+
11
+ The browser flow does not change gateway authentication. Once authorized, the CLI uses
12
+ the same `X-API-Key` data-plane contract as every other API-key client.
13
+
14
+ ## Protocol
15
+
16
+ 1. The CLI generates a PKCE verifier and sends only its SHA-256 challenge to
17
+ `POST /api/cli/auth/start`.
18
+ 2. Functions returns a signed, ten-minute stateless device request, a short display
19
+ code, a polling interval, and
20
+ `https://mediaruntime.com/cli/authorize?...`.
21
+ 3. The CLI opens that URL and also prints the URL and display code for headless use.
22
+ 4. The website uses the existing Firebase login. It shows the code and selected account,
23
+ and requires an explicit Approve action.
24
+ 5. The browser sends its Firebase ID token to `POST /api/cli/auth/approve`. Functions
25
+ verifies the signed device request and re-checks identity, active owner/admin
26
+ membership, account state, billing, and wallet access before persisting an approved
27
+ authorization session.
28
+ 6. The CLI polls `POST /api/cli/auth/token` with the device code and PKCE verifier.
29
+ Functions verifies both, creates one dedicated API key, and returns that key only to
30
+ the CLI. The browser never receives it and Firestore never stores it in plaintext.
31
+ 7. The CLI validates the credential and stores it in the platform credential vault.
32
+
33
+ Pending, denied, expired, consumed, and slow polling states use stable machine-readable
34
+ error codes. Authorization sessions expire after ten minutes. Successful exchange is
35
+ single-use; a short deterministic replay window makes a lost token response recoverable
36
+ without creating another key.
37
+
38
+ ## Commands
39
+
40
+ ```text
41
+ mediaruntime login [--no-browser]
42
+ mediaruntime auth status
43
+ mediaruntime logout [--local-only]
44
+ ```
45
+
46
+ `login` refuses `--json` because it is interactive. It never prints the API key.
47
+ `auth status` identifies the active credential source (`environment` or `keychain`) and
48
+ validates it without displaying secret material. `logout` revokes the stored CLI key
49
+ before deleting it; `--local-only` is an explicit recovery path when the service cannot
50
+ be reached.
51
+
52
+ ## Credential resolution
53
+
54
+ Authenticated commands resolve credentials in this order:
55
+
56
+ 1. non-empty `MEDIARUNTIME_API_KEY`;
57
+ 2. an OS-vault credential stored by `mediaruntime login`;
58
+ 3. an authentication-required error that recommends `mediaruntime login` or the
59
+ environment variable.
60
+
61
+ No command-line API-key flag exists. No plaintext credential-file fallback exists. A
62
+ machine without a supported credential vault must use `MEDIARUNTIME_API_KEY`.
63
+
64
+ ## Security invariants
65
+
66
+ - Device codes and PKCE verifiers carry at least 256 bits of randomness.
67
+ - Starting login is stateless and performs no database write. After browser approval,
68
+ Firestore stores the device-code hash, PKCE challenge, approval metadata, and key
69
+ identifiers, never the raw device code, API key, or PKCE verifier.
70
+ - Display codes are bound to the device session and expire after ten minutes.
71
+ - Approval requires a fresh verified Firebase identity and active owner/admin account
72
+ membership.
73
+ - A session cannot change accounts after approval.
74
+ - A token exchange cannot mint more than one API key.
75
+ - CLI keys are separately labeled and revocable; login never revokes unrelated keys.
76
+ - Logout revokes only the key created for that stored CLI login.
77
+ - Secrets are excluded from logs, JSON output, URLs, errors, analytics, and shell history.
78
+ - Production uses HTTPS. Development overrides may use loopback HTTP only.
79
+
80
+ ## Deployment and compatibility
81
+
82
+ The hosted approval route and Functions endpoints must deploy before publishing the CLI
83
+ version that exposes `login`. Older CLI versions and all SDKs continue to use
84
+ `MEDIARUNTIME_API_KEY` unchanged. If browser authorization is unavailable, automation and
85
+ interactive users can still use the environment variable.
package/docs/CLI_SDD.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MediaRuntime CLI software design
2
2
 
3
- Status: implementation contract for the first public release
3
+ Status: implementation contract through browser-auth release 0.2
4
4
 
5
5
  Package: `@mediaruntime/cli`
6
6
 
@@ -10,18 +10,22 @@ Runtime: Node.js 20 or newer
10
10
 
11
11
  ## 1. Decision summary
12
12
 
13
- The first CLI release is a thin, script-safe layer over the published
14
- `@mediaruntime/node` SDK. It ships four independently useful surfaces:
13
+ The CLI is a thin, script-safe layer over the published `@mediaruntime/node` SDK. It
14
+ ships the media commands plus an interactive authentication surface:
15
15
 
16
16
  ```text
17
17
  mediaruntime run <source> --output <alias>
18
18
  mediaruntime jobs list
19
19
  mediaruntime jobs get <job_id>
20
20
  mediaruntime trigger job.completed --to http://127.0.0.1:3000/webhooks/mediaruntime
21
+ mediaruntime login
22
+ mediaruntime auth status
23
+ mediaruntime logout
21
24
  ```
22
25
 
23
26
  `run` and `jobs` use the same production API, authentication, local-file upload,
24
- idempotency, retry, error, and polling behavior as the Node SDK. `trigger` is entirely
27
+ idempotency, retry, error, and polling behavior as the Node SDK. `login` authorizes a
28
+ dedicated API key through the hosted browser flow defined in `docs/AUTH.md`. `trigger` is entirely
25
29
  local: it creates and signs a synthetic terminal webhook without contacting the
26
30
  MediaRuntime API.
27
31
 
@@ -38,6 +42,8 @@ engine directory layout.
38
42
  - Download the canonical ZIP safely without leaking credentials to the storage host.
39
43
  - Let a developer exercise their local webhook receiver with the production signing
40
44
  protocol and realistic terminal payloads.
45
+ - Let interactive developers authorize in the browser and keep the resulting dedicated
46
+ credential in the operating-system vault without weakening environment-key automation.
41
47
  - Preserve the SDK and gateway as the authorities for request serialization, uploads,
42
48
  retries, aliases, status parsing, and API errors.
43
49
 
@@ -45,8 +51,8 @@ engine directory layout.
45
51
 
46
52
  - `mediaruntime listen` is not a v1 command. It requires a multi-tenant relay,
47
53
  authenticated connections, event ownership, expiry, rate limits, and an abuse model.
48
- - `mediaruntime login`, browser authentication, credential persistence, and profiles are
49
- not v1 features. Authentication is non-interactive and environment-based.
54
+ - Named multi-account profiles are deferred. Browser login stores one credential per API
55
+ origin; an explicit environment key remains the account override.
50
56
  - Batch submission, explicit output recipe editing, moderation, watermark management,
51
57
  webhook registration, media-report retrieval, and moderation-result retrieval remain
52
58
  available through the SDK/API but are not CLI v1 commands.
@@ -79,13 +85,14 @@ Authenticated commands resolve configuration once before doing file or network w
79
85
 
80
86
  | Setting | Highest precedence | Environment | Default |
81
87
  |---|---|---|---|
82
- | API key | none | `MEDIARUNTIME_API_KEY` | required for `run` and `jobs` |
88
+ | API key | `MEDIARUNTIME_API_KEY` | OS-vault browser login | required for `run` and `jobs` |
83
89
  | API base URL | `--base-url <url>` | `MEDIARUNTIME_API_URL` | `https://mediaruntime.com` |
84
90
  | Webhook signing secret | `--secret-file <path>` | `MEDIARUNTIME_WEBHOOK_SECRET` | required by `trigger`, unless explicitly generated |
85
91
 
86
92
  There is deliberately no `--api-key` or `--secret` option: command-line arguments are
87
93
  visible in shell history and process listings. The CLI does not load `.env` files, write
88
- credentials, use an OS keychain, or search parent directories for configuration.
94
+ plaintext credentials, or search parent directories for configuration. Browser login is
95
+ stored only in the platform credential vault; machines without one use the environment.
89
96
 
90
97
  `--base-url` exists for a local gateway, staging, and deterministic tests; ordinary users
91
98
  must not need it. It must be an absolute HTTP(S) origin with no credentials, query, or
@@ -341,7 +348,7 @@ results and must not invent conflicting mappings.
341
348
  | `4` | Non-retryable API outcome such as `400`, `404`, `409`, `410`, `413`, or `422`, or exhausted `429` |
342
349
  | `5` | Connection/HTTP timeout or exhausted retryable `5xx` response |
343
350
  | `6` | A submitted or inspected job is terminal `FAILED`, `REJECTED`, or `PARTIAL` |
344
- | `7` | `run --wait` exceeded its wait timeout |
351
+ | `7` | Job waiting or browser authorization exceeded its bounded timeout |
345
352
  | `8` | Local trigger endpoint returned a redirect or non-`2xx` response |
346
353
  | `9` | Bundle availability, redemption, integrity, or filesystem failure |
347
354
  | `130` | Interrupted by `SIGINT`; in-flight fetch/poll/download is aborted and temporary files are removed |
@@ -356,7 +363,8 @@ endpoint that refuses the event is `8`.
356
363
  ## 10. Security rules
357
364
 
358
365
  - Never accept API keys or webhook secrets as ordinary command arguments.
359
- - Never persist credentials or generated secrets.
366
+ - Persist browser-login credentials only in the operating-system credential vault; never
367
+ fall back to a plaintext file.
360
368
  - Redact credentials and signed query strings from errors and debug output.
361
369
  - Treat JSON output containing metadata as caller-sensitive even though signed bundle URLs
362
370
  are deliberately omitted.
@@ -382,6 +390,10 @@ src/cli.ts argv dispatch, configuration, signals, exit/error ren
382
390
  src/commands/run.ts run parsing and SDK orchestration
383
391
  src/commands/jobs.ts list/get parsing and projections
384
392
  src/commands/trigger.ts local event orchestration
393
+ src/commands/auth.ts login/status/logout orchestration and precedence
394
+ src/auth/api.ts one-time browser authorization protocol
395
+ src/auth/credential-store.ts OS credential-vault boundary
396
+ src/auth/browser.ts shell-free browser launcher
385
397
  src/download.ts streamed atomic bundle redemption
386
398
  src/trigger/event.ts synthetic public payloads
387
399
  src/trigger/signature.ts byte signing and headers
@@ -398,10 +410,24 @@ production credentials, billable jobs, real storage, or listening ports.
398
410
 
399
411
  - Every documented command/flag combination parses; unknown flags and extra positionals
400
412
  exit `2` before network access.
401
- - Missing/blank API key fails `run` and `jobs`; `trigger` never reads it.
413
+ - Missing/blank environment key falls back to the OS-vault login; when both are absent,
414
+ `run` and `jobs` fail while `trigger` remains independent.
402
415
  - `--base-url` overrides the environment, which overrides production; invalid or insecure
403
416
  non-loopback HTTP URLs fail closed.
404
- - No command accepts `--api-key`, `--secret`, a per-job webhook URL, `listen`, or `login`.
417
+ - No command accepts `--api-key`, `--secret`, a per-job webhook URL, or `listen`.
418
+
419
+ ### Browser authentication
420
+
421
+ - `login` sends only a PKCE challenge, opens the returned MediaRuntime authorization URL,
422
+ polls at the server-provided interval, and never prints the issued API key.
423
+ - Approval requires the existing verified Firebase identity, active owner/admin account
424
+ membership, active billing, available funds, and an explicit matching-code consent.
425
+ - The browser and Firestore never receive the raw CLI API key or PKCE verifier.
426
+ - A successful exchange creates exactly one dedicated CLI key across concurrent polls and
427
+ lost responses, then stores it only in the OS credential vault.
428
+ - `auth status` reports source/account/key metadata without secret material.
429
+ - `logout` revokes the dedicated key before deleting it; `--local-only` is explicit.
430
+ - `MEDIARUNTIME_API_KEY` takes precedence and remains supported for automation.
405
431
 
406
432
  ### Run
407
433
 
@@ -463,4 +489,5 @@ The v1 package is ready only when:
463
489
  account and produces a valid ZIP.
464
490
  5. A local receiver using `@mediaruntime/node` verification accepts every trigger type and
465
491
  rejects a changed body, stale timestamp, and wrong secret.
466
- 6. Documentation labels `listen` and `login` as deferred rather than advertising stubs.
492
+ 6. Browser login passes a production create/approve/exchange/status/logout smoke test;
493
+ documentation labels only `listen` as deferred.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediaruntime/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Official command-line client for MediaRuntime",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,7 @@
31
31
  "prepack": "npm run check && npm test"
32
32
  },
33
33
  "dependencies": {
34
+ "@napi-rs/keyring": "^1.3.0",
34
35
  "@mediaruntime/node": "^0.2.5"
35
36
  },
36
37
  "devDependencies": {