@mrgoonie/multix 0.12.0 → 0.13.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/dist/cli.js CHANGED
@@ -862,13 +862,13 @@ var AtlasCloudClient = class {
862
862
  }
863
863
  /** Submit a generation job. `body` holds `model` plus the model's own fields, flattened. */
864
864
  async submit(kind, body) {
865
- const path54 = SUBMIT_PATHS[kind];
866
- if (!path54) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
865
+ const path57 = SUBMIT_PATHS[kind];
866
+ if (!path57) throw new ValidationError(`Unknown Atlas Cloud media type: ${String(kind)}`);
867
867
  if (typeof body.model !== "string" || !body.model.trim()) {
868
868
  throw new ValidationError("Atlas Cloud request body needs a non-empty `model`");
869
869
  }
870
870
  const env = await fetchJson({
871
- url: `${this.baseUrl}${path54}`,
871
+ url: `${this.baseUrl}${path57}`,
872
872
  method: "POST",
873
873
  headers: this.authHeaders(),
874
874
  body,
@@ -2182,9 +2182,9 @@ var BytePlusClient = class {
2182
2182
  }
2183
2183
  apiKey;
2184
2184
  baseUrl;
2185
- async request(path54, opts = {}) {
2185
+ async request(path57, opts = {}) {
2186
2186
  const base = this.baseUrl.replace(/\/$/, "");
2187
- const url = new URL(base + path54);
2187
+ const url = new URL(base + path57);
2188
2188
  if (opts.query) {
2189
2189
  for (const [k, v] of Object.entries(opts.query)) {
2190
2190
  if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
@@ -2202,14 +2202,14 @@ var BytePlusClient = class {
2202
2202
  timeoutMs: opts.timeoutMs ?? 12e4
2203
2203
  });
2204
2204
  }
2205
- get(path54, query, logger) {
2206
- return this.request(path54, { method: "GET", query, logger });
2205
+ get(path57, query, logger) {
2206
+ return this.request(path57, { method: "GET", query, logger });
2207
2207
  }
2208
- post(path54, body, logger) {
2209
- return this.request(path54, { method: "POST", body, logger });
2208
+ post(path57, body, logger) {
2209
+ return this.request(path57, { method: "POST", body, logger });
2210
2210
  }
2211
- delete(path54, logger) {
2212
- return this.request(path54, { method: "DELETE", logger });
2211
+ delete(path57, logger) {
2212
+ return this.request(path57, { method: "DELETE", logger });
2213
2213
  }
2214
2214
  };
2215
2215
  function createBytePlusClient() {
@@ -2678,10 +2678,10 @@ function toRef(kind, resolved, role) {
2678
2678
  return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
2679
2679
  }
2680
2680
  async function resolveRefSpec(kind, spec, logger) {
2681
- const { path: path54, role } = parseRefSpec(spec);
2682
- if (kind === "image") return toRef(kind, await resolveImageInput(path54, logger), role);
2683
- if (kind === "video") return toRef(kind, await resolveVideoInput(path54, logger), role);
2684
- return toRef(kind, await resolveAudioInput(path54, logger), role);
2681
+ const { path: path57, role } = parseRefSpec(spec);
2682
+ if (kind === "image") return toRef(kind, await resolveImageInput(path57, logger), role);
2683
+ if (kind === "video") return toRef(kind, await resolveVideoInput(path57, logger), role);
2684
+ return toRef(kind, await resolveAudioInput(path57, logger), role);
2685
2685
  }
2686
2686
  async function resolveRefSpecs(specs, logger) {
2687
2687
  const out = [];
@@ -4108,9 +4108,9 @@ var FalClient = class {
4108
4108
  apiKey;
4109
4109
  baseUrl;
4110
4110
  /** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
4111
- async request(path54, opts = {}) {
4111
+ async request(path57, opts = {}) {
4112
4112
  const base = this.baseUrl.replace(/\/$/, "");
4113
- const suffix = path54.startsWith("/") ? path54 : `/${path54}`;
4113
+ const suffix = path57.startsWith("/") ? path57 : `/${path57}`;
4114
4114
  return this.requestAbsolute(base + suffix, opts);
4115
4115
  }
4116
4116
  /** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
@@ -4127,11 +4127,11 @@ var FalClient = class {
4127
4127
  timeoutMs: opts.timeoutMs ?? 12e4
4128
4128
  });
4129
4129
  }
4130
- get(path54, logger) {
4131
- return this.request(path54, { method: "GET", logger });
4130
+ get(path57, logger) {
4131
+ return this.request(path57, { method: "GET", logger });
4132
4132
  }
4133
- post(path54, body, logger) {
4134
- return this.request(path54, { method: "POST", body, logger });
4133
+ post(path57, body, logger) {
4134
+ return this.request(path57, { method: "POST", body, logger });
4135
4135
  }
4136
4136
  getAbsolute(url, logger) {
4137
4137
  return this.requestAbsolute(url, { method: "GET", logger });
@@ -5964,9 +5964,1792 @@ function registerGeminiCommands(program2) {
5964
5964
  registerGeminiGenerateSpeechCommand(gemini);
5965
5965
  }
5966
5966
 
5967
+ // src/providers/heygen/models.ts
5968
+ var DEFAULT_HEYGEN_BASE_URL = "https://api.heygen.com";
5969
+ var HEYGEN_API_SETTINGS_URL = "https://app.heygen.com/developers/api";
5970
+ var HEYGEN_USAGE_URL = "https://app.heygen.com/developers/usage";
5971
+ var AVATAR_VIDEO_RESOLUTIONS = ["4k", "1080p", "720p"];
5972
+ var AVATAR_VIDEO_ASPECT_RATIOS = ["16:9", "9:16", "4:5", "5:4", "1:1", "auto"];
5973
+ var AVATAR_ENGINES = ["avatar_iv", "avatar_v", "avatar_iii"];
5974
+ var EXPRESSIVENESS_LEVELS = ["high", "medium", "low"];
5975
+ var AVATAR_TYPES = ["studio_avatar", "digital_twin", "photo_avatar"];
5976
+ var OWNERSHIP_VALUES = ["public", "private"];
5977
+ var AGENT_ORIENTATIONS = ["landscape", "portrait"];
5978
+ var QUALITY_MODES = ["speed", "precision"];
5979
+ var SPEECH_ENGINES = ["starfish", "orca", "elevenlabs"];
5980
+ var HEYGEN_VIDEO_MODEL = "heygen-video-1";
5981
+ var MODEL_VIDEO_MODES = ["text_to_video", "image_to_video", "reference_to_video"];
5982
+ var MODEL_VIDEO_RESOLUTIONS = ["480p", "768p", "1080p", "2k"];
5983
+ var MODEL_VIDEO_ASPECT_RATIOS = [
5984
+ "21:9",
5985
+ "16:9",
5986
+ "4:3",
5987
+ "1:1",
5988
+ "3:4",
5989
+ "9:16",
5990
+ "adaptive"
5991
+ ];
5992
+ var MODEL_VIDEO_PROMPT_ENHANCEMENT = ["turbo", "quality", "disabled"];
5993
+ var LIMITS = {
5994
+ agentPromptMax: 1e4,
5995
+ agentFilesMax: 20,
5996
+ scriptMax: 5e3,
5997
+ speechTextMax: 5e3,
5998
+ /** POST /v3/assets single-request upload cap (32 MB). */
5999
+ assetUploadMaxBytes: 32 * 1024 * 1024,
6000
+ modelVideoPromptMax: 32e3,
6001
+ modelVideoMinDuration: 5,
6002
+ modelVideoMaxDuration: 15,
6003
+ modelVideoReferenceImagesMax: 9,
6004
+ modelVideoReferenceVideosMax: 3,
6005
+ modelVideoReferenceAudioMax: 3,
6006
+ /** Combined cap across reference images, videos and audio. */
6007
+ modelVideoReferenceTotalMax: 12,
6008
+ avatarListMax: 50,
6009
+ voiceListMax: 100,
6010
+ stylesListMax: 100
6011
+ };
6012
+ var ASSET_MIME_BY_EXT = {
6013
+ png: "image/png",
6014
+ jpg: "image/jpeg",
6015
+ jpeg: "image/jpeg",
6016
+ mp4: "video/mp4",
6017
+ webm: "video/webm",
6018
+ mp3: "audio/mpeg",
6019
+ wav: "audio/wav",
6020
+ pdf: "application/pdf",
6021
+ srt: "application/x-subrip"
6022
+ };
6023
+ var JOB_KINDS = [
6024
+ "agent",
6025
+ "video",
6026
+ "translation",
6027
+ "lipsync",
6028
+ "model-video"
6029
+ ];
6030
+ var HEYGEN_OPERATIONS = {
6031
+ "video-agent": {
6032
+ description: "POST /v3/video-agents (prompt-to-video session)",
6033
+ path: "/v3/video-agents",
6034
+ kind: "agent",
6035
+ idField: "session_id",
6036
+ idempotent: false
6037
+ },
6038
+ video: {
6039
+ description: "POST /v3/videos (avatar | image | cinematic_avatar | studio)",
6040
+ path: "/v3/videos",
6041
+ kind: "video",
6042
+ idField: "video_id",
6043
+ idempotent: true
6044
+ },
6045
+ "video-translation": {
6046
+ description: "POST /v3/video-translations",
6047
+ path: "/v3/video-translations",
6048
+ kind: "translation",
6049
+ idsField: "video_translation_ids",
6050
+ idempotent: true
6051
+ },
6052
+ lipsync: {
6053
+ description: "POST /v3/lipsyncs",
6054
+ path: "/v3/lipsyncs",
6055
+ kind: "lipsync",
6056
+ idField: "lipsync_id",
6057
+ idempotent: true
6058
+ },
6059
+ "model-video": {
6060
+ description: "POST /v3/models/videos (HeyGen Video model)",
6061
+ path: "/v3/models/videos",
6062
+ kind: "model-video",
6063
+ idField: "video_id",
6064
+ idempotent: true
6065
+ },
6066
+ speech: {
6067
+ description: "POST /v3/voices/speech (synchronous text-to-speech)",
6068
+ path: "/v3/voices/speech",
6069
+ kind: null,
6070
+ idempotent: false
6071
+ }
6072
+ };
6073
+
6074
+ // src/providers/heygen/node-helpers.ts
6075
+ import fs29 from "fs";
6076
+ import path32 from "path";
6077
+ init_errors();
6078
+ init_redact();
6079
+
6080
+ // src/providers/heygen/client.ts
6081
+ init_errors();
6082
+ init_redact();
6083
+
6084
+ // src/providers/heygen/heygen-errors.ts
6085
+ init_errors();
6086
+ var HeygenApiError = class extends HttpError {
6087
+ constructor(status, snippet, url, retryAfterMs, apiCode, apiMessage, param) {
6088
+ super(status, snippet, url, retryAfterMs);
6089
+ this.apiCode = apiCode;
6090
+ this.apiMessage = apiMessage;
6091
+ this.param = param;
6092
+ this.name = "HeygenApiError";
6093
+ this.message = describeHeygenError(status, apiCode, apiMessage, param, retryAfterMs);
6094
+ }
6095
+ apiCode;
6096
+ apiMessage;
6097
+ param;
6098
+ };
6099
+ var HeygenJobFailedError = class extends ProviderError {
6100
+ constructor(message, failureCode) {
6101
+ super(message, "heygen");
6102
+ this.failureCode = failureCode;
6103
+ this.name = "HeygenJobFailedError";
6104
+ }
6105
+ failureCode;
6106
+ };
6107
+ function parseHeygenErrorBody(snippet) {
6108
+ try {
6109
+ const parsed = JSON.parse(snippet);
6110
+ if (parsed && typeof parsed.error === "object" && parsed.error !== null) return parsed.error;
6111
+ if (parsed && typeof parsed.error === "string") return { message: parsed.error };
6112
+ } catch {
6113
+ }
6114
+ const field = (name) => snippet.match(new RegExp(`"${name}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`))?.[1];
6115
+ const out = {};
6116
+ const code = field("code");
6117
+ const message = field("message");
6118
+ const param = field("param");
6119
+ if (code) out.code = code;
6120
+ if (message) out.message = message;
6121
+ if (param) out.param = param;
6122
+ return out;
6123
+ }
6124
+ function toHeygenApiError(error) {
6125
+ if (error instanceof HeygenApiError) return error;
6126
+ const body = error.status === 0 ? {} : parseHeygenErrorBody(error.snippet);
6127
+ return new HeygenApiError(
6128
+ error.status,
6129
+ error.snippet,
6130
+ error.url,
6131
+ error.retryAfterMs,
6132
+ body.code,
6133
+ body.message ?? (error.status === 0 ? error.snippet : void 0),
6134
+ body.param ?? void 0
6135
+ );
6136
+ }
6137
+ function hintFor(status, code) {
6138
+ if (status === 0) return "Network error reaching HeyGen \u2014 check connectivity and retry.";
6139
+ if (status === 401)
6140
+ return `Check HEYGEN_API_KEY (sent as the X-Api-Key header) or create a new key at ${HEYGEN_API_SETTINGS_URL}.`;
6141
+ if (status === 402)
6142
+ return `Add API credits or upgrade the API plan at ${HEYGEN_USAGE_URL}. HeyGen API usage is billed separately from web/app subscriptions.`;
6143
+ if (status === 403) {
6144
+ if (code === "insufficient_api_key_scope")
6145
+ return "The API key lacks the scope this operation needs \u2014 create a key with the required scopes.";
6146
+ return "The API key, workspace, or plan is not entitled to this feature (some avatar and digital-twin features require Enterprise access).";
6147
+ }
6148
+ if (status === 404) return "Check the ID. For `heygen status`, make sure --type matches the ID.";
6149
+ if (status === 409 && code === "request_in_progress")
6150
+ return "An identical request is still being processed \u2014 wait and retry.";
6151
+ if (status === 409) return "The resource is not ready or conflicts with existing state.";
6152
+ if (status === 400 || status === 422) return "Fix the request parameters and retry.";
6153
+ if (status === 429)
6154
+ return code === "quota_exceeded" ? `Usage quota exceeded \u2014 see ${HEYGEN_USAGE_URL}.` : "Rate or concurrency limit reached \u2014 wait and retry, or reduce parallel jobs.";
6155
+ if (status >= 500) return "HeyGen server error \u2014 retry later.";
6156
+ return "";
6157
+ }
6158
+ function describeHeygenError(status, code, message, param, retryAfterMs) {
6159
+ const label = status === 0 ? "HeyGen request failed" : `HeyGen API error ${status}`;
6160
+ const codePart = code ? ` (${code})` : "";
6161
+ const msgPart = message ? `: ${message}` : "";
6162
+ const paramPart = param ? ` (param: ${param})` : "";
6163
+ const retryPart = retryAfterMs !== void 0 ? ` Retry after ${Math.ceil(retryAfterMs / 1e3)}s.` : "";
6164
+ const hint = hintFor(status, code);
6165
+ const base = `${label}${codePart}${msgPart}${paramPart}`;
6166
+ const sentence = /[.!?]$/.test(base) ? base : `${base}.`;
6167
+ return `${sentence}${retryPart}${hint ? ` ${hint}` : ""}`;
6168
+ }
6169
+
6170
+ // src/providers/heygen/client.ts
6171
+ var MAX_RETRY_WAIT_MS = 6e4;
6172
+ var defaultSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6173
+ function seg(id, label) {
6174
+ if (typeof id !== "string") throw new ValidationError(`${label} must be a string`);
6175
+ const trimmed = id.trim();
6176
+ if (!trimmed) throw new ValidationError(`${label} must not be empty`);
6177
+ if (trimmed === "." || trimmed === "..") {
6178
+ throw new ValidationError(`Invalid ${label}: "${trimmed}"`);
6179
+ }
6180
+ return encodeURIComponent(trimmed);
6181
+ }
6182
+ function describePayload(data) {
6183
+ const text = JSON.stringify(
6184
+ data,
6185
+ (_k, v) => typeof v === "string" && /^https?:\/\//i.test(v) ? redactUrl(v) : v
6186
+ );
6187
+ return (text ?? String(data)).slice(0, 500);
6188
+ }
6189
+ function requireJobId(data, field, path57) {
6190
+ const value = data?.[field];
6191
+ const ok2 = Array.isArray(value) ? value.length > 0 && value.every((v) => typeof v === "string" && v.length > 0) : typeof value === "string" && value.length > 0;
6192
+ if (!ok2) {
6193
+ throw new ProviderError(
6194
+ `HeyGen ${path57} returned no ${field}; a job may still have been created, so check the HeyGen dashboard. Response: ${describePayload(data)}`,
6195
+ "heygen"
6196
+ );
6197
+ }
6198
+ return data;
6199
+ }
6200
+ var HeygenClient = class {
6201
+ apiKey;
6202
+ baseUrl;
6203
+ fetchImpl;
6204
+ timeoutMs;
6205
+ maxAttempts;
6206
+ sleep;
6207
+ newIdempotencyKey;
6208
+ constructor(opts) {
6209
+ if (!opts.apiKey?.trim()) throw new ValidationError("HeyGen apiKey is required");
6210
+ this.apiKey = opts.apiKey.trim();
6211
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_HEYGEN_BASE_URL).replace(/\/+$/, "");
6212
+ this.fetchImpl = opts.fetch;
6213
+ this.timeoutMs = opts.timeoutMs ?? 12e4;
6214
+ this.maxAttempts = Math.max(1, opts.maxAttempts ?? 3);
6215
+ this.sleep = opts.sleep ?? defaultSleep2;
6216
+ this.newIdempotencyKey = opts.newIdempotencyKey ?? (() => crypto.randomUUID());
6217
+ }
6218
+ /** Build an absolute URL for a v3 path plus query parameters. */
6219
+ url(path57, query) {
6220
+ const suffix = path57.startsWith("/") ? path57 : `/${path57}`;
6221
+ const params = new URLSearchParams();
6222
+ for (const [k, v] of Object.entries(query ?? {})) {
6223
+ if (v !== void 0 && v !== "") params.set(k, String(v));
6224
+ }
6225
+ const qs = params.toString();
6226
+ return `${this.baseUrl}${suffix}${qs ? `?${qs}` : ""}`;
6227
+ }
6228
+ headers(idempotencyKey) {
6229
+ return {
6230
+ Accept: "application/json",
6231
+ "X-Api-Key": this.apiKey,
6232
+ ...idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}
6233
+ };
6234
+ }
6235
+ /**
6236
+ * Send a request and return the raw parsed JSON (envelope included).
6237
+ * Retries are bounded and only where safe: a 429 carrying Retry-After was
6238
+ * rejected before processing; 5xx/network failures and 409
6239
+ * `request_in_progress` are retried only when an Idempotency-Key makes the
6240
+ * replay safe. GET requests are left to the caller's polling loop.
6241
+ */
6242
+ async send(path57, opts = {}) {
6243
+ const method = opts.method ?? "GET";
6244
+ const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : opts.idempotencyKey ? this.newIdempotencyKey() : void 0;
6245
+ const url = this.url(path57, opts.query);
6246
+ for (let attempt = 1; ; attempt++) {
6247
+ try {
6248
+ return await fetchJson({
6249
+ url,
6250
+ method,
6251
+ headers: this.headers(key),
6252
+ body: opts.body,
6253
+ timeoutMs: Math.max(1, Math.min(this.timeoutMs, opts.timeoutMs ?? this.timeoutMs)),
6254
+ fetch: this.fetchImpl
6255
+ });
6256
+ } catch (error) {
6257
+ const apiError = error instanceof HttpError ? toHeygenApiError(error) : error;
6258
+ if (method === "GET" || attempt >= this.maxAttempts || !(apiError instanceof HttpError)) {
6259
+ throw apiError;
6260
+ }
6261
+ const wait = this.retryDelay(apiError, key, attempt);
6262
+ if (wait === void 0) throw apiError;
6263
+ await this.sleep(wait);
6264
+ }
6265
+ }
6266
+ }
6267
+ retryDelay(error, key, attempt) {
6268
+ const code = error instanceof HeygenApiError ? error.apiCode : void 0;
6269
+ if (error.status === 429 && error.retryAfterMs !== void 0 && code !== "quota_exceeded") {
6270
+ return Math.min(error.retryAfterMs, MAX_RETRY_WAIT_MS);
6271
+ }
6272
+ if (!key) return void 0;
6273
+ if (error.status === 0 || error.status >= 500 || code === "request_in_progress") {
6274
+ return Math.min(1e3 * 2 ** (attempt - 1), MAX_RETRY_WAIT_MS);
6275
+ }
6276
+ return void 0;
6277
+ }
6278
+ /** Send and unwrap `{ data }`. A 2xx body carrying `error` is treated as a failure. */
6279
+ async request(path57, opts = {}) {
6280
+ const json = await this.send(path57, opts);
6281
+ return unwrapData(json, path57);
6282
+ }
6283
+ async page(path57, query) {
6284
+ const json = await this.send(path57, {
6285
+ query
6286
+ });
6287
+ const items = unwrapData(json, path57);
6288
+ if (!Array.isArray(items)) {
6289
+ throw new ProviderError(
6290
+ `Unexpected HeyGen response from ${path57}: data is not a list`,
6291
+ "heygen"
6292
+ );
6293
+ }
6294
+ const next = json.next_token;
6295
+ return {
6296
+ items,
6297
+ hasMore: json.has_more === true,
6298
+ nextToken: typeof next === "string" && next ? next : null
6299
+ };
6300
+ }
6301
+ // ── Account ───────────────────────────────────────────────────────────
6302
+ getCurrentUser() {
6303
+ return this.request("/v3/users/me");
6304
+ }
6305
+ // ── Video Agent ───────────────────────────────────────────────────────
6306
+ /** POST /v3/video-agents. No Idempotency-Key: the endpoint does not document one. */
6307
+ async createVideoAgent(body) {
6308
+ const path57 = "/v3/video-agents";
6309
+ const data = await this.request(path57, { method: "POST", body });
6310
+ return requireJobId(data, "session_id", path57);
6311
+ }
6312
+ async getVideoAgentSession(sessionId, opts = {}) {
6313
+ return this.request(`/v3/video-agents/${seg(sessionId, "session id")}`, opts);
6314
+ }
6315
+ listVideoAgentStyles(query = {}) {
6316
+ return this.page("/v3/video-agents/styles", { ...query });
6317
+ }
6318
+ // ── Videos ────────────────────────────────────────────────────────────
6319
+ async createVideo(body, idempotencyKey = true) {
6320
+ const path57 = "/v3/videos";
6321
+ const data = await this.request(path57, {
6322
+ method: "POST",
6323
+ body,
6324
+ idempotencyKey
6325
+ });
6326
+ return requireJobId(data, "video_id", path57);
6327
+ }
6328
+ async getVideo(videoId, opts = {}) {
6329
+ return this.request(`/v3/videos/${seg(videoId, "video id")}`, opts);
6330
+ }
6331
+ /** POST /v3/templates/{id}. Returns the created video's `VideoDetail`. */
6332
+ async generateFromTemplate(templateId, body, idempotencyKey = true) {
6333
+ const path57 = `/v3/templates/${seg(templateId, "template id")}`;
6334
+ const data = await this.request(path57, {
6335
+ method: "POST",
6336
+ body,
6337
+ idempotencyKey
6338
+ });
6339
+ return requireJobId(data, "id", path57);
6340
+ }
6341
+ // ── HeyGen Video model ────────────────────────────────────────────────
6342
+ async createModelVideo(body, idempotencyKey = true) {
6343
+ const path57 = "/v3/models/videos";
6344
+ const data = await this.request(path57, {
6345
+ method: "POST",
6346
+ body,
6347
+ idempotencyKey
6348
+ });
6349
+ return requireJobId(data, "video_id", path57);
6350
+ }
6351
+ async getModelVideo(videoId, opts = {}) {
6352
+ return this.request(`/v3/models/videos/${seg(videoId, "video id")}`, opts);
6353
+ }
6354
+ // ── Catalog ───────────────────────────────────────────────────────────
6355
+ listAvatarGroups(query = {}) {
6356
+ return this.page("/v3/avatars", { ...query });
6357
+ }
6358
+ /** Looks are what video generation needs: a look `id` is the `avatar_id`. */
6359
+ listAvatarLooks(query = {}) {
6360
+ return this.page("/v3/avatars/looks", { ...query });
6361
+ }
6362
+ listVoices(query = {}) {
6363
+ return this.page("/v3/voices", { ...query });
6364
+ }
6365
+ // ── Speech ────────────────────────────────────────────────────────────
6366
+ createSpeech(body) {
6367
+ return this.request("/v3/voices/speech", { method: "POST", body });
6368
+ }
6369
+ // ── Translation & lipsync ─────────────────────────────────────────────
6370
+ async createVideoTranslation(body, idempotencyKey = true) {
6371
+ const path57 = "/v3/video-translations";
6372
+ const data = await this.request(path57, {
6373
+ method: "POST",
6374
+ body,
6375
+ idempotencyKey
6376
+ });
6377
+ return requireJobId(data, "video_translation_ids", path57);
6378
+ }
6379
+ async getVideoTranslation(id, opts = {}) {
6380
+ return this.request(`/v3/video-translations/${seg(id, "video translation id")}`, opts);
6381
+ }
6382
+ async listTranslationLanguages() {
6383
+ const data = await this.request("/v3/video-translations/languages");
6384
+ if (!Array.isArray(data.languages)) {
6385
+ throw new ProviderError("Unexpected HeyGen response: languages is not a list", "heygen");
6386
+ }
6387
+ return data.languages.filter((l) => typeof l === "string");
6388
+ }
6389
+ async createLipsync(body, idempotencyKey = true) {
6390
+ const path57 = "/v3/lipsyncs";
6391
+ const data = await this.request(path57, {
6392
+ method: "POST",
6393
+ body,
6394
+ idempotencyKey
6395
+ });
6396
+ return requireJobId(data, "lipsync_id", path57);
6397
+ }
6398
+ async getLipsync(id, opts = {}) {
6399
+ return this.request(`/v3/lipsyncs/${seg(id, "lipsync id")}`, opts);
6400
+ }
6401
+ // ── Assets ────────────────────────────────────────────────────────────
6402
+ /**
6403
+ * POST /v3/assets (multipart, max 32 MB). Accepts bytes or a Blob so it works
6404
+ * without a filesystem; for remote files prefer passing `{type:"url"}` inputs.
6405
+ */
6406
+ async uploadAsset(input) {
6407
+ const blob = input.data instanceof Blob ? input.data : new Blob([input.data], { type: input.mimeType });
6408
+ if (blob.size === 0) throw new ValidationError(`Cannot upload empty file ${input.filename}`);
6409
+ if (blob.size > LIMITS.assetUploadMaxBytes) {
6410
+ throw new ValidationError(
6411
+ `${input.filename} is ${(blob.size / 1024 / 1024).toFixed(1)} MB; POST /v3/assets accepts at most 32 MB. Host the file at a public HTTPS URL and pass the URL instead.`
6412
+ );
6413
+ }
6414
+ const typed = blob.type ? blob : new Blob([blob], { type: input.mimeType });
6415
+ const key = this.newIdempotencyKey();
6416
+ const url = this.url("/v3/assets");
6417
+ const doFetch = this.fetchImpl ?? ((u, init) => globalThis.fetch(u, init));
6418
+ for (let attempt = 1; ; attempt++) {
6419
+ const form = new FormData();
6420
+ form.append("file", typed, input.filename);
6421
+ let response;
6422
+ try {
6423
+ response = await doFetch(url, {
6424
+ method: "POST",
6425
+ headers: this.headers(key),
6426
+ body: form,
6427
+ signal: AbortSignal.timeout(this.timeoutMs)
6428
+ });
6429
+ } catch (cause) {
6430
+ const msg = cause instanceof Error ? cause.message : String(cause);
6431
+ const err3 = toHeygenApiError(new HttpError(0, msg, url));
6432
+ if (attempt >= this.maxAttempts) throw err3;
6433
+ await this.sleep(this.retryDelay(err3, key, attempt) ?? 1e3);
6434
+ continue;
6435
+ }
6436
+ if (response.ok) {
6437
+ let json;
6438
+ try {
6439
+ json = await response.json();
6440
+ } catch {
6441
+ throw new ProviderError(
6442
+ `HeyGen /v3/assets returned a non-JSON response (HTTP ${response.status}) for ${input.filename}`,
6443
+ "heygen"
6444
+ );
6445
+ }
6446
+ const data = unwrapData(json, "/v3/assets");
6447
+ return requireJobId(data, "asset_id", "/v3/assets");
6448
+ }
6449
+ const err2 = toHeygenApiError(await httpErrorFromResponse(response, url));
6450
+ const wait = attempt < this.maxAttempts ? this.retryDelay(err2, key, attempt) : void 0;
6451
+ if (wait === void 0) throw err2;
6452
+ await this.sleep(wait);
6453
+ }
6454
+ }
6455
+ };
6456
+ function unwrapData(json, path57) {
6457
+ if (json === null || typeof json !== "object") {
6458
+ throw new ProviderError(`Unexpected HeyGen response from ${path57}: not a JSON object`, "heygen");
6459
+ }
6460
+ const record = json;
6461
+ const error = record.error;
6462
+ if (error && typeof error === "object") {
6463
+ const e = error;
6464
+ throw new ProviderError(
6465
+ `HeyGen API error${e.code ? ` (${e.code})` : ""}: ${e.message ?? "unknown error"}`,
6466
+ "heygen"
6467
+ );
6468
+ }
6469
+ if (!("data" in record) || record.data === null || record.data === void 0) {
6470
+ throw new ProviderError(`Unexpected HeyGen response from ${path57}: missing data`, "heygen");
6471
+ }
6472
+ return record.data;
6473
+ }
6474
+
6475
+ // src/providers/heygen/jobs.ts
6476
+ var str = (v) => typeof v === "string" && v ? v : void 0;
6477
+ function videoState(video, kind = "video", id = video.id, opts = {}) {
6478
+ const done = video.status === "completed" || video.status === "failed";
6479
+ const captioned = str(video.captioned_video_url);
6480
+ const state = {
6481
+ kind,
6482
+ id,
6483
+ status: video.status,
6484
+ done,
6485
+ succeeded: video.status === "completed",
6486
+ videoId: video.id,
6487
+ outputUrl: opts.burnCaptions ? captioned : str(video.video_url) ?? captioned,
6488
+ captionedVideoUrl: captioned,
6489
+ thumbnailUrl: str(video.thumbnail_url),
6490
+ captionUrl: str(video.subtitle_url),
6491
+ failureCode: str(video.failure_code),
6492
+ failureMessage: str(video.failure_message),
6493
+ raw: { video }
6494
+ };
6495
+ if (opts.burnCaptions && state.succeeded && !captioned) {
6496
+ return {
6497
+ ...state,
6498
+ succeeded: false,
6499
+ failureCode: "captioned_video_missing",
6500
+ failureMessage: "Burned-in captions were requested but HeyGen returned no captioned_video_url. The uncaptioned render is available via `heygen status --raw`."
6501
+ };
6502
+ }
6503
+ return state;
6504
+ }
6505
+ function translationState(t) {
6506
+ return {
6507
+ kind: "translation",
6508
+ id: t.id,
6509
+ status: t.status,
6510
+ done: t.status === "completed" || t.status === "failed",
6511
+ succeeded: t.status === "completed",
6512
+ outputUrl: str(t.video_url) ?? str(t.audio_url),
6513
+ captionUrl: str(t.srt_caption_url) ?? str(t.vtt_caption_url),
6514
+ outputLanguage: str(t.output_language),
6515
+ failureMessage: str(t.failure_message),
6516
+ raw: { translation: t }
6517
+ };
6518
+ }
6519
+ function lipsyncState(l) {
6520
+ return {
6521
+ kind: "lipsync",
6522
+ id: l.id,
6523
+ status: l.status,
6524
+ done: l.status === "completed" || l.status === "failed",
6525
+ succeeded: l.status === "completed",
6526
+ outputUrl: str(l.video_url),
6527
+ captionUrl: str(l.caption_url),
6528
+ failureMessage: str(l.failure_message),
6529
+ raw: { lipsync: l }
6530
+ };
6531
+ }
6532
+ function modelVideoState(v) {
6533
+ const done = v.status === "completed" || v.status === "failed" || v.status === "cancelled";
6534
+ return {
6535
+ kind: "model-video",
6536
+ id: v.video_id,
6537
+ status: v.status,
6538
+ done,
6539
+ succeeded: v.status === "completed",
6540
+ videoId: v.video_id,
6541
+ outputUrl: str(v.video_url),
6542
+ failureCode: str(v.failure_code),
6543
+ failureMessage: str(v.failure_message),
6544
+ raw: { video: v }
6545
+ };
6546
+ }
6547
+ function agentState(session, video) {
6548
+ const base = {
6549
+ kind: "agent",
6550
+ id: session.session_id,
6551
+ sessionId: session.session_id,
6552
+ sessionStatus: session.status,
6553
+ progress: session.progress
6554
+ };
6555
+ const sessionError = session.error ?? void 0;
6556
+ if (video) {
6557
+ const v = videoState(video, "agent", session.session_id);
6558
+ if (!v.done && session.status === "failed" && sessionError?.code !== "context_limit_exceeded") {
6559
+ return {
6560
+ ...v,
6561
+ ...base,
6562
+ status: "failed",
6563
+ done: true,
6564
+ succeeded: false,
6565
+ failureCode: sessionError?.code,
6566
+ failureMessage: sessionError?.message ?? "Video Agent session failed",
6567
+ raw: { session, video }
6568
+ };
6569
+ }
6570
+ if (v.done && !v.succeeded && session.status === "waiting_for_input") {
6571
+ return {
6572
+ ...v,
6573
+ ...base,
6574
+ failureCode: v.failureCode ?? "waiting_for_input",
6575
+ failureMessage: v.failureMessage ?? "Video Agent paused for input; continue the session in the HeyGen app or start a new prompt.",
6576
+ raw: { session, video }
6577
+ };
6578
+ }
6579
+ return { ...v, ...base, raw: { session, video } };
6580
+ }
6581
+ const pending2 = { ...base, status: session.status, raw: { session } };
6582
+ if (session.status === "failed") {
6583
+ return {
6584
+ ...pending2,
6585
+ done: true,
6586
+ succeeded: false,
6587
+ failureCode: sessionError?.code,
6588
+ failureMessage: sessionError?.message ?? "Video Agent session failed"
6589
+ };
6590
+ }
6591
+ if (session.status === "waiting_for_input") {
6592
+ return {
6593
+ ...pending2,
6594
+ done: true,
6595
+ succeeded: false,
6596
+ failureCode: "waiting_for_input",
6597
+ failureMessage: "Video Agent paused for input before rendering; continue the session in the HeyGen app or start a new prompt."
6598
+ };
6599
+ }
6600
+ if (session.status === "completed") {
6601
+ return {
6602
+ ...pending2,
6603
+ done: true,
6604
+ succeeded: false,
6605
+ failureCode: "no_video",
6606
+ failureMessage: "Video Agent session completed without a video_id."
6607
+ };
6608
+ }
6609
+ return { ...pending2, done: false, succeeded: false };
6610
+ }
6611
+ function createJobStatusReader(client, kind, id, opts = {}) {
6612
+ return async (ctx) => {
6613
+ const call = { timeoutMs: ctx?.remainingMs };
6614
+ switch (kind) {
6615
+ case "video":
6616
+ return videoState(await client.getVideo(id, call), "video", id, opts);
6617
+ case "translation":
6618
+ return translationState(await client.getVideoTranslation(id, call));
6619
+ case "lipsync":
6620
+ return lipsyncState(await client.getLipsync(id, call));
6621
+ case "model-video":
6622
+ return modelVideoState(await client.getModelVideo(id, call));
6623
+ case "agent": {
6624
+ const session = await client.getVideoAgentSession(id, call);
6625
+ if (!session.video_id) return agentState(session);
6626
+ return agentState(session, await client.getVideo(session.video_id, call));
6627
+ }
6628
+ }
6629
+ };
6630
+ }
6631
+ function getJobState(client, kind, id, opts = {}) {
6632
+ return createJobStatusReader(client, kind, id, opts)();
6633
+ }
6634
+ var DEFAULT_POLL_INTERVAL_MS = 1e4;
6635
+ function waitForJob(client, kind, id, opts) {
6636
+ const { burnCaptions, ...poll2 } = opts;
6637
+ return pollUntil({
6638
+ intervalMs: DEFAULT_POLL_INTERVAL_MS,
6639
+ maxIntervalMs: 3e4,
6640
+ ...poll2,
6641
+ fetchStatus: createJobStatusReader(client, kind, id, { burnCaptions }),
6642
+ isTerminal: (s) => s.done
6643
+ });
6644
+ }
6645
+ function assertJobSucceeded(state) {
6646
+ if (state.succeeded) return;
6647
+ const label = state.kind === "agent" ? `Video Agent session ${state.id}` : `${state.kind} ${state.id}`;
6648
+ const code = state.failureCode ? ` (${state.failureCode})` : "";
6649
+ const msg = state.failureMessage ? `: ${state.failureMessage}` : "";
6650
+ throw new HeygenJobFailedError(
6651
+ `HeyGen ${label} ended with status "${state.status}"${code}${msg}`,
6652
+ state.failureCode
6653
+ );
6654
+ }
6655
+ function shellArg(value) {
6656
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
6657
+ }
6658
+ function resumeCommand2(kind, id, opts = {}) {
6659
+ const captions = opts.burnCaptions ? " --burn-captions" : "";
6660
+ return `multix heygen status ${shellArg(id)} --type ${kind} --wait --download${captions}`;
6661
+ }
6662
+
6663
+ // src/providers/heygen/request-builders.ts
6664
+ init_errors();
6665
+ function oneOf(value, allowed, flag) {
6666
+ if (value === void 0) return void 0;
6667
+ if (!allowed.includes(value)) {
6668
+ throw new ValidationError(`${flag} must be one of: ${allowed.join(", ")} (got "${value}")`);
6669
+ }
6670
+ return value;
6671
+ }
6672
+ function numberInRange(value, flag, min, max, integer = false) {
6673
+ if (value === void 0 || value === "") return void 0;
6674
+ const n = typeof value === "number" ? value : Number(value);
6675
+ if (!Number.isFinite(n) || n < min || n > max || integer && !Number.isInteger(n)) {
6676
+ throw new ValidationError(
6677
+ `${flag} must be ${integer ? "an integer" : "a number"} between ${min} and ${max} (got "${value}")`
6678
+ );
6679
+ }
6680
+ return n;
6681
+ }
6682
+ function requireHttps(url, flag) {
6683
+ if (!/^https:\/\//i.test(url)) throw new ValidationError(`${flag} must be an https:// URL`);
6684
+ return url;
6685
+ }
6686
+ function classifyMediaRef(value) {
6687
+ const v = value.trim();
6688
+ if (!v) throw new ValidationError("Media reference must not be empty");
6689
+ if (/^https?:\/\//i.test(v)) return { kind: "url", url: requireHttps(v, "Media URL") };
6690
+ const asset = v.match(/^asset(?:_id)?:(.+)$/i);
6691
+ if (asset?.[1]) return { kind: "asset", assetId: asset[1].trim() };
6692
+ return { kind: "path", path: v };
6693
+ }
6694
+ function splitLanguages(raw) {
6695
+ const out = [];
6696
+ let depth = 0;
6697
+ let current = "";
6698
+ for (const ch of raw) {
6699
+ if (ch === "(") depth++;
6700
+ if (ch === ")") depth = Math.max(0, depth - 1);
6701
+ if (ch === "," && depth === 0) {
6702
+ out.push(current);
6703
+ current = "";
6704
+ } else {
6705
+ current += ch;
6706
+ }
6707
+ }
6708
+ out.push(current);
6709
+ const langs = out.map((s) => s.trim()).filter(Boolean);
6710
+ if (langs.length === 0) throw new ValidationError("--languages must list at least one language");
6711
+ return [...new Set(langs)];
6712
+ }
6713
+ function checkCallback(url) {
6714
+ return url === void 0 ? void 0 : requireHttps(url, "--callback-url");
6715
+ }
6716
+ function buildVideoAgentRequest(input) {
6717
+ const prompt = input.prompt?.trim() ?? "";
6718
+ if (prompt.length < 1 || prompt.length > LIMITS.agentPromptMax) {
6719
+ throw new ValidationError(`Prompt must be 1-${LIMITS.agentPromptMax} characters`);
6720
+ }
6721
+ const files = input.files ?? [];
6722
+ if (files.length > LIMITS.agentFilesMax) {
6723
+ throw new ValidationError(`Video Agent accepts at most ${LIMITS.agentFilesMax} files`);
6724
+ }
6725
+ const body = { prompt, mode: "generate" };
6726
+ if (input.avatarId) body.avatar_id = input.avatarId;
6727
+ if (input.voiceId) body.voice_id = input.voiceId;
6728
+ if (input.styleId) body.style_id = input.styleId;
6729
+ if (input.brandKitId) body.brand_kit_id = input.brandKitId;
6730
+ if (input.brandGlossaryId) body.brand_glossary_id = input.brandGlossaryId;
6731
+ const orientation = oneOf(input.orientation, AGENT_ORIENTATIONS, "--orientation");
6732
+ if (orientation) body.orientation = orientation;
6733
+ if (files.length > 0) body.files = files;
6734
+ const callbackUrl = checkCallback(input.callbackUrl);
6735
+ if (callbackUrl) body.callback_url = callbackUrl;
6736
+ if (input.callbackId) body.callback_id = input.callbackId;
6737
+ return body;
6738
+ }
6739
+ function buildAvatarVideoRequest(input) {
6740
+ if (!input.avatarId?.trim()) throw new ValidationError("--avatar <look-id> is required");
6741
+ const script = input.script?.trim();
6742
+ if (script && input.audio) {
6743
+ throw new ValidationError("Provide either a script or --audio, not both");
6744
+ }
6745
+ if (!script && !input.audio) {
6746
+ throw new ValidationError("Provide a script argument or --audio <url|asset:id|file>");
6747
+ }
6748
+ if (script && script.length > LIMITS.scriptMax) {
6749
+ throw new ValidationError(`Script must be at most ${LIMITS.scriptMax} characters`);
6750
+ }
6751
+ const body = { type: "avatar", avatar_id: input.avatarId.trim() };
6752
+ if (script) {
6753
+ body.script = script;
6754
+ if (input.voiceId) body.voice_id = input.voiceId;
6755
+ } else if (input.audio) {
6756
+ if (input.voiceId) throw new ValidationError("--voice applies to scripts, not --audio");
6757
+ if (input.audio.type === "url") body.audio_url = input.audio.url;
6758
+ else if (input.audio.type === "asset_id") body.audio_asset_id = input.audio.asset_id;
6759
+ else throw new ValidationError("--audio must be a URL, asset id, or uploaded file");
6760
+ }
6761
+ const resolution = oneOf(input.resolution, AVATAR_VIDEO_RESOLUTIONS, "--resolution");
6762
+ if (resolution) body.resolution = resolution;
6763
+ const aspect = oneOf(input.aspectRatio, AVATAR_VIDEO_ASPECT_RATIOS, "--aspect-ratio");
6764
+ if (aspect) body.aspect_ratio = aspect;
6765
+ const fit = oneOf(input.fit, ["contain", "cover"], "--fit");
6766
+ if (fit) body.fit = fit;
6767
+ const outputFormat = oneOf(input.outputFormat, ["mp4", "webm"], "--output-format");
6768
+ if (outputFormat) body.output_format = outputFormat;
6769
+ if (input.backgroundColor && input.backgroundImage) {
6770
+ throw new ValidationError("Use either --background-color or --background-image, not both");
6771
+ }
6772
+ if (outputFormat === "webm" && (input.backgroundColor || input.backgroundImage)) {
6773
+ throw new ValidationError(
6774
+ "--output-format webm renders a transparent background; drop --background-*"
6775
+ );
6776
+ }
6777
+ if (input.backgroundColor) {
6778
+ if (!/^#[0-9a-f]{6}$/i.test(input.backgroundColor)) {
6779
+ throw new ValidationError("--background-color must be a hex color like #112233");
6780
+ }
6781
+ body.background = { type: "color", value: input.backgroundColor };
6782
+ } else if (input.backgroundImage) {
6783
+ const bg = input.backgroundImage;
6784
+ if (bg.type === "url") body.background = { type: "image", url: bg.url };
6785
+ else if (bg.type === "asset_id") body.background = { type: "image", asset_id: bg.asset_id };
6786
+ }
6787
+ if (input.removeBackground) body.remove_background = true;
6788
+ if (input.captions || input.burnCaptions) {
6789
+ body.caption = {
6790
+ file_format: "srt",
6791
+ ...input.burnCaptions ? { style: "default" } : {}
6792
+ };
6793
+ }
6794
+ const speed = numberInRange(input.speed, "--speed", 0.5, 1.5);
6795
+ const pitch = numberInRange(input.pitch, "--pitch", -50, 50);
6796
+ const volume = numberInRange(input.volume, "--volume", 0, 1);
6797
+ if (speed !== void 0 || pitch !== void 0 || volume !== void 0 || input.locale) {
6798
+ if (!script) throw new ValidationError("Voice settings apply only to scripted videos");
6799
+ body.voice_settings = {
6800
+ ...speed !== void 0 ? { speed } : {},
6801
+ ...pitch !== void 0 ? { pitch } : {},
6802
+ ...volume !== void 0 ? { volume } : {},
6803
+ ...input.locale ? { locale: input.locale } : {}
6804
+ };
6805
+ }
6806
+ const engine = oneOf(input.engine, AVATAR_ENGINES, "--engine");
6807
+ if (input.referenceLookId && engine !== "avatar_v") {
6808
+ throw new ValidationError("--reference-look requires --engine avatar_v");
6809
+ }
6810
+ if (engine) {
6811
+ body.engine = {
6812
+ type: engine,
6813
+ ...input.referenceLookId ? { reference_look_id: input.referenceLookId } : {}
6814
+ };
6815
+ }
6816
+ if (input.motionPrompt) {
6817
+ if (engine === "avatar_iii") {
6818
+ throw new ValidationError("--motion-prompt is not supported with --engine avatar_iii");
6819
+ }
6820
+ body.motion_prompt = input.motionPrompt;
6821
+ }
6822
+ const expressiveness = oneOf(input.expressiveness, EXPRESSIVENESS_LEVELS, "--expressiveness");
6823
+ if (expressiveness) {
6824
+ if (engine === "avatar_v" || engine === "avatar_iii") {
6825
+ throw new ValidationError("--expressiveness is Avatar IV only (photo avatars)");
6826
+ }
6827
+ body.expressiveness = expressiveness;
6828
+ }
6829
+ if (resolution === "4k" && engine !== "avatar_iii") {
6830
+ throw new ValidationError(
6831
+ "--resolution 4k requires --engine avatar_iii (digital twins and studio avatars)"
6832
+ );
6833
+ }
6834
+ if (input.title) body.title = input.title;
6835
+ if (input.folderId) body.folder_id = input.folderId;
6836
+ if (input.brandGlossaryId) body.brand_glossary_id = input.brandGlossaryId;
6837
+ const callbackUrl = checkCallback(input.callbackUrl);
6838
+ if (callbackUrl) body.callback_url = callbackUrl;
6839
+ if (input.callbackId) body.callback_id = input.callbackId;
6840
+ return body;
6841
+ }
6842
+ function buildSpeechRequest(input) {
6843
+ const text = input.text ?? "";
6844
+ if (text.trim().length < 1 || text.length > LIMITS.speechTextMax) {
6845
+ throw new ValidationError(`Text must be 1-${LIMITS.speechTextMax} characters`);
6846
+ }
6847
+ if (!input.voiceId?.trim()) throw new ValidationError("--voice <voice-id> is required");
6848
+ const body = { text, voice_id: input.voiceId.trim() };
6849
+ if (input.ssml) body.input_type = "ssml";
6850
+ const speed = numberInRange(input.speed, "--speed", 0.5, 2);
6851
+ if (speed !== void 0) body.speed = speed;
6852
+ if (input.language) body.language = input.language;
6853
+ if (input.locale) body.locale = input.locale;
6854
+ const engine = oneOf(input.engine, SPEECH_ENGINES, "--engine");
6855
+ if (engine) body.engine = engine;
6856
+ return body;
6857
+ }
6858
+ function buildModelVideoRequest(input) {
6859
+ const prompt = input.prompt?.trim() ?? "";
6860
+ if (prompt.length < 1 || prompt.length > LIMITS.modelVideoPromptMax) {
6861
+ throw new ValidationError(`Prompt must be 1-${LIMITS.modelVideoPromptMax} characters`);
6862
+ }
6863
+ const refImages = input.referenceImages ?? [];
6864
+ const refVideos = input.referenceVideos ?? [];
6865
+ const refAudio = input.referenceAudio ?? [];
6866
+ const hasRefs = refImages.length + refVideos.length + refAudio.length > 0;
6867
+ const inferred = input.image ? "image_to_video" : hasRefs ? "reference_to_video" : "text_to_video";
6868
+ const mode = oneOf(input.mode, MODEL_VIDEO_MODES, "--mode") ?? inferred;
6869
+ if (mode === "image_to_video" && !input.image) {
6870
+ throw new ValidationError("image_to_video requires --image");
6871
+ }
6872
+ if (mode !== "image_to_video" && input.image) {
6873
+ throw new ValidationError("--image is only valid with --mode image_to_video");
6874
+ }
6875
+ if (mode === "reference_to_video" && refImages.length + refVideos.length === 0) {
6876
+ throw new ValidationError(
6877
+ "reference_to_video needs at least one --reference-image or --reference-video"
6878
+ );
6879
+ }
6880
+ if (mode !== "reference_to_video" && hasRefs) {
6881
+ throw new ValidationError("--reference-* inputs are only valid with --mode reference_to_video");
6882
+ }
6883
+ if (refImages.length > LIMITS.modelVideoReferenceImagesMax) {
6884
+ throw new ValidationError(`At most ${LIMITS.modelVideoReferenceImagesMax} reference images`);
6885
+ }
6886
+ if (refVideos.length > LIMITS.modelVideoReferenceVideosMax) {
6887
+ throw new ValidationError(`At most ${LIMITS.modelVideoReferenceVideosMax} reference videos`);
6888
+ }
6889
+ if (refAudio.length > LIMITS.modelVideoReferenceAudioMax) {
6890
+ throw new ValidationError(
6891
+ `At most ${LIMITS.modelVideoReferenceAudioMax} reference audio files`
6892
+ );
6893
+ }
6894
+ const refTotal = refImages.length + refVideos.length + refAudio.length;
6895
+ if (refTotal > LIMITS.modelVideoReferenceTotalMax) {
6896
+ throw new ValidationError(
6897
+ `At most ${LIMITS.modelVideoReferenceTotalMax} reference files in total (got ${refTotal})`
6898
+ );
6899
+ }
6900
+ const body = { model: HEYGEN_VIDEO_MODEL, mode, prompt };
6901
+ const duration = numberInRange(
6902
+ input.duration,
6903
+ "--duration",
6904
+ LIMITS.modelVideoMinDuration,
6905
+ LIMITS.modelVideoMaxDuration,
6906
+ true
6907
+ );
6908
+ if (duration !== void 0) body.duration = duration;
6909
+ const resolution = oneOf(input.resolution, MODEL_VIDEO_RESOLUTIONS, "--resolution");
6910
+ if (resolution) body.resolution = resolution;
6911
+ const aspect = oneOf(input.aspectRatio, MODEL_VIDEO_ASPECT_RATIOS, "--aspect-ratio");
6912
+ if (aspect === "adaptive" && mode !== "reference_to_video") {
6913
+ throw new ValidationError("--aspect-ratio adaptive is only valid for reference_to_video");
6914
+ }
6915
+ if (aspect && mode === "image_to_video") {
6916
+ throw new ValidationError("image_to_video follows the first frame; drop --aspect-ratio");
6917
+ }
6918
+ if (aspect) body.aspect_ratio = aspect;
6919
+ const enhancement = oneOf(
6920
+ input.promptEnhancement,
6921
+ MODEL_VIDEO_PROMPT_ENHANCEMENT,
6922
+ "--prompt-enhancement"
6923
+ );
6924
+ if (enhancement) body.prompt_enhancement = enhancement;
6925
+ const seed = numberInRange(input.seed, "--seed", 0, 4294967295, true);
6926
+ if (seed !== void 0) body.seed = seed;
6927
+ if (input.image) body.image = input.image;
6928
+ if (refImages.length) body.reference_images = refImages;
6929
+ if (refVideos.length) body.reference_videos = refVideos;
6930
+ if (refAudio.length) body.reference_audio = refAudio;
6931
+ const callbackUrl = checkCallback(input.callbackUrl);
6932
+ if (callbackUrl) body.callback_url = callbackUrl;
6933
+ if (input.callbackId) body.callback_id = input.callbackId;
6934
+ return body;
6935
+ }
6936
+ function qualityMode(value) {
6937
+ return oneOf(value, QUALITY_MODES, "--mode");
6938
+ }
6939
+
6940
+ // src/providers/heygen/node-helpers.ts
6941
+ function requireHeygenKey() {
6942
+ const key = resolveKey("HEYGEN_API_KEY")?.trim();
6943
+ if (!key) {
6944
+ throw new ConfigError(
6945
+ `HEYGEN_API_KEY is not set. Create an API key at ${HEYGEN_API_SETTINGS_URL} (API usage is billed separately from HeyGen web plans).`
6946
+ );
6947
+ }
6948
+ return key;
6949
+ }
6950
+ function createHeygenClientFromEnv() {
6951
+ return new HeygenClient({
6952
+ apiKey: requireHeygenKey(),
6953
+ baseUrl: resolveKey("HEYGEN_BASE_URL") ?? DEFAULT_HEYGEN_BASE_URL
6954
+ });
6955
+ }
6956
+ async function uploadLocalFile(client, filePath, logger) {
6957
+ const abs = path32.resolve(filePath);
6958
+ let stat;
6959
+ try {
6960
+ stat = fs29.statSync(abs);
6961
+ } catch {
6962
+ throw new ValidationError(
6963
+ `File not found: ${filePath}. Use an https:// URL, asset:<asset_id>, or an existing file.`
6964
+ );
6965
+ }
6966
+ if (!stat.isFile()) throw new ValidationError(`Not a file: ${filePath}`);
6967
+ const ext = path32.extname(abs).slice(1).toLowerCase();
6968
+ const mimeType = ASSET_MIME_BY_EXT[ext];
6969
+ if (!mimeType) {
6970
+ throw new ValidationError(
6971
+ `Unsupported file type ".${ext}" for HeyGen upload. Supported: ${Object.keys(ASSET_MIME_BY_EXT).join(", ")}`
6972
+ );
6973
+ }
6974
+ if (stat.size > LIMITS.assetUploadMaxBytes) {
6975
+ throw new ValidationError(
6976
+ `${filePath} is ${(stat.size / 1024 / 1024).toFixed(1)} MB; HeyGen asset upload accepts at most 32 MB. Host it at a public https:// URL instead.`
6977
+ );
6978
+ }
6979
+ logger.info(`Uploading ${path32.basename(abs)} to HeyGen assets...`);
6980
+ const res = await client.uploadAsset({
6981
+ data: new Uint8Array(fs29.readFileSync(abs)),
6982
+ filename: path32.basename(abs),
6983
+ mimeType
6984
+ });
6985
+ logger.debug(`Uploaded asset ${res.asset_id} (${res.mime_type}, ${res.size_bytes} bytes)`);
6986
+ return { type: "asset_id", asset_id: res.asset_id };
6987
+ }
6988
+ async function resolveMediaInput(client, value, logger) {
6989
+ const ref = classifyMediaRef(value);
6990
+ if (ref.kind === "url") return { type: "url", url: ref.url };
6991
+ if (ref.kind === "asset") return { type: "asset_id", asset_id: ref.assetId };
6992
+ return uploadLocalFile(client, ref.path, logger);
6993
+ }
6994
+ async function resolveMediaInputs(client, values, logger) {
6995
+ const out = [];
6996
+ for (const v of values ?? []) out.push(await resolveMediaInput(client, v, logger));
6997
+ return out;
6998
+ }
6999
+ function collect5(value, previous = []) {
7000
+ return [...previous, value];
7001
+ }
7002
+ function parsePositiveInt(raw, flag, fallback) {
7003
+ if (raw === void 0) return fallback;
7004
+ const n = Number(raw);
7005
+ if (!Number.isInteger(n) || n <= 0)
7006
+ throw new ValidationError(`${flag} must be a positive integer`);
7007
+ return n;
7008
+ }
7009
+ var DEFAULT_WAIT_TIMEOUT_MS = 18e5;
7010
+ function addWaitOptions(cmd, defaultTimeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
7011
+ return cmd.option("--async", "Return job IDs and a resume command immediately (no polling)").option("--wait-timeout <ms>", "Max time to wait for completion", String(defaultTimeoutMs)).option(
7012
+ "--poll-interval <ms>",
7013
+ "Initial poll interval (backs off to 30s)",
7014
+ String(DEFAULT_POLL_INTERVAL_MS)
7015
+ ).option("--no-download", "Do not download outputs; print output URLs instead").option("--output <path>", "Output file (or directory when several outputs are produced)").option("--captions", "Also download the caption/subtitle file when one is available").option("--no-thumb", "Skip downloading the video thumbnail").option("--json", "Print a JSON summary instead of human-readable output").option("-v, --verbose", "Verbose logging");
7016
+ }
7017
+ var OUTPUT_EXTS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "mp3", "wav", "m4a", "aac", "srt", "vtt"]);
7018
+ function inferExtension(url, fallback) {
7019
+ try {
7020
+ const ext = path32.extname(new URL(url).pathname).slice(1).toLowerCase();
7021
+ return OUTPUT_EXTS.has(ext) ? ext : fallback;
7022
+ } catch {
7023
+ return fallback;
7024
+ }
7025
+ }
7026
+ function safeSlug(value) {
7027
+ return value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "out";
7028
+ }
7029
+ async function downloadOutput(url, dest, resumeHint) {
7030
+ if (!url) {
7031
+ throw new ValidationError(`HeyGen reported success but returned no output URL. ${resumeHint}`);
7032
+ }
7033
+ if (!/^https?:\/\//i.test(url)) {
7034
+ throw new ValidationError(`HeyGen returned an invalid output URL (${redactUrl(url)})`);
7035
+ }
7036
+ const tmp = `${dest}.${process.pid}-${Date.now()}.part`;
7037
+ try {
7038
+ await downloadFile(url, tmp, 6e5);
7039
+ fs29.renameSync(tmp, dest);
7040
+ } catch (e) {
7041
+ fs29.rmSync(tmp, { force: true });
7042
+ const status = e instanceof HttpError ? e.status : 0;
7043
+ const expired = status === 403 || status === 404 || status === 410;
7044
+ const reason = e instanceof Error ? e.message : String(e);
7045
+ throw new ValidationError(
7046
+ `Failed to download HeyGen output from ${redactUrl(url)}: ${reason}.${expired ? " The signed URL may have expired." : ""} ${resumeHint}`
7047
+ );
7048
+ }
7049
+ return dest;
7050
+ }
7051
+ function defaultOutputPath(state, ext) {
7052
+ const lang = state.outputLanguage ? `-${safeSlug(state.outputLanguage)}` : "";
7053
+ return path32.join(getOutputDir(), `heygen-${state.kind}${lang}-${safeSlug(state.id)}.${ext}`);
7054
+ }
7055
+ function outputPathFor(state, ext, output, count) {
7056
+ if (!output) return defaultOutputPath(state, ext);
7057
+ if (count > 1) {
7058
+ const dir = path32.resolve(output);
7059
+ fs29.mkdirSync(dir, { recursive: true });
7060
+ return path32.join(dir, path32.basename(defaultOutputPath(state, ext)));
7061
+ }
7062
+ return path32.resolve(output);
7063
+ }
7064
+ function summarizeState(state) {
7065
+ const out = {
7066
+ kind: state.kind,
7067
+ id: state.id,
7068
+ status: state.status
7069
+ };
7070
+ if (state.sessionId) out.session_id = state.sessionId;
7071
+ if (state.sessionStatus) out.session_status = state.sessionStatus;
7072
+ if (state.videoId) out.video_id = state.videoId;
7073
+ if (state.progress !== void 0) out.progress = state.progress;
7074
+ if (state.outputLanguage) out.output_language = state.outputLanguage;
7075
+ if (state.outputUrl) out.output_url = redactUrl(state.outputUrl);
7076
+ if (state.captionedVideoUrl) out.captioned_video_url = redactUrl(state.captionedVideoUrl);
7077
+ if (state.failureCode) out.failure_code = state.failureCode;
7078
+ if (state.failureMessage) out.failure_message = state.failureMessage;
7079
+ return out;
7080
+ }
7081
+ async function deliverOne(state, opts, count, logger, files) {
7082
+ const hint = `Refresh with: ${resumeCommand2(state.kind, state.id, opts)}`;
7083
+ if (opts.download === false) {
7084
+ if (!state.outputUrl) {
7085
+ throw new ValidationError(`No output URL for ${state.kind} ${state.id}. ${hint}`);
7086
+ }
7087
+ if (!opts.json) console.log(state.outputUrl);
7088
+ const urls = {
7089
+ output_url: state.outputUrl,
7090
+ ...state.captionUrl ? { caption_url: state.captionUrl } : {}
7091
+ };
7092
+ return { state, files, urls };
7093
+ }
7094
+ const ext = inferExtension(state.outputUrl ?? "", "mp4");
7095
+ const dest = outputPathFor(state, ext, opts.output, count);
7096
+ await downloadOutput(state.outputUrl, dest, hint);
7097
+ logger.success(`Saved ${dest}`);
7098
+ files.push(dest);
7099
+ if (opts.captions && state.captionUrl) {
7100
+ const capExt = inferExtension(state.captionUrl, "srt");
7101
+ const capDest = path32.join(
7102
+ path32.dirname(dest),
7103
+ `${path32.basename(dest, path32.extname(dest))}.${capExt}`
7104
+ );
7105
+ await downloadOutput(state.captionUrl, capDest, hint);
7106
+ logger.success(`Saved ${capDest}`);
7107
+ files.push(capDest);
7108
+ } else if (opts.captions) {
7109
+ logger.warn(`No caption file available for ${state.kind} ${state.id}`);
7110
+ }
7111
+ if (state.kind === "video" || state.kind === "agent") {
7112
+ const thumb = await maybeDownloadThumb(state.raw, dest, {
7113
+ skip: opts.thumb === false,
7114
+ logger
7115
+ });
7116
+ if (thumb) files.push(thumb);
7117
+ }
7118
+ return { state, files };
7119
+ }
7120
+ async function deliverOutputs(states, opts, logger) {
7121
+ const finished = [];
7122
+ const failures = [];
7123
+ for (const state of states) {
7124
+ const files = [];
7125
+ try {
7126
+ finished.push(await deliverOne(state, opts, states.length, logger, files));
7127
+ } catch (e) {
7128
+ failures.push(`${state.kind} ${state.id}: ${e instanceof Error ? e.message : String(e)}`);
7129
+ if (files.length > 0) finished.push({ state, files });
7130
+ }
7131
+ }
7132
+ return { finished, failures };
7133
+ }
7134
+ function printFinished(results, opts) {
7135
+ if (opts.json) {
7136
+ const jobs = results.map((r) => ({ ...summarizeState(r.state), ...r.urls, files: r.files }));
7137
+ if (jobs.length > 0) console.log(JSON.stringify(jobs.length === 1 ? jobs[0] : jobs, null, 2));
7138
+ return;
7139
+ }
7140
+ for (const r of results) {
7141
+ for (const f of r.files) console.log(`Saved: ${f}`);
7142
+ }
7143
+ }
7144
+ function printSubmitted(kind, ids, opts) {
7145
+ if (opts.json) {
7146
+ const jobs = ids.map((id) => ({
7147
+ kind,
7148
+ id,
7149
+ status: "submitted",
7150
+ resume: resumeCommand2(kind, id, opts)
7151
+ }));
7152
+ console.log(JSON.stringify(jobs.length === 1 ? jobs[0] : jobs, null, 2));
7153
+ return;
7154
+ }
7155
+ for (const id of ids) {
7156
+ console.log(`Submitted ${kind} job: ${id}`);
7157
+ console.log(` Resume with: ${resumeCommand2(kind, id, opts)}`);
7158
+ }
7159
+ }
7160
+ async function completeJobs(client, kind, ids, opts, logger) {
7161
+ if (!Array.isArray(ids) || ids.length === 0 || ids.some((id) => typeof id !== "string" || !id)) {
7162
+ throw new ValidationError(`HeyGen returned no usable ${kind} job ID`);
7163
+ }
7164
+ if (opts.async) {
7165
+ printSubmitted(kind, ids, opts);
7166
+ return [];
7167
+ }
7168
+ const timeoutMs = parsePositiveInt(opts.waitTimeout, "--wait-timeout", DEFAULT_WAIT_TIMEOUT_MS);
7169
+ const intervalMs = parsePositiveInt(
7170
+ opts.pollInterval,
7171
+ "--poll-interval",
7172
+ DEFAULT_POLL_INTERVAL_MS
7173
+ );
7174
+ logger.info(
7175
+ `Waiting for ${ids.length > 1 ? `${ids.length} ${kind} jobs` : `${kind} ${ids[0]}`}...`
7176
+ );
7177
+ const settled = await Promise.allSettled(
7178
+ ids.map(
7179
+ (id) => waitForJob(client, kind, id, {
7180
+ timeoutMs,
7181
+ intervalMs,
7182
+ maxIntervalMs: Math.max(intervalMs, 3e4),
7183
+ burnCaptions: opts.burnCaptions,
7184
+ resumeHint: `Resume with: ${resumeCommand2(kind, id, opts)}`,
7185
+ onPoll: (s, attempt) => logger.debug(
7186
+ `${kind} ${id} poll #${attempt}: ${s.status}${s.videoId ? ` (video ${s.videoId})` : ""}`
7187
+ )
7188
+ })
7189
+ )
7190
+ );
7191
+ const states = [];
7192
+ const failures = [];
7193
+ for (const [i, r] of settled.entries()) {
7194
+ if (r.status === "fulfilled") {
7195
+ if (r.value.succeeded) states.push(r.value);
7196
+ else {
7197
+ try {
7198
+ assertJobSucceeded(r.value);
7199
+ } catch (e) {
7200
+ failures.push(e instanceof Error ? e.message : String(e));
7201
+ }
7202
+ }
7203
+ } else {
7204
+ const err2 = r.reason;
7205
+ const msg = err2 instanceof Error ? err2.message : String(err2);
7206
+ failures.push(err2 instanceof PollTimeoutError ? msg : `${kind} ${ids[i]}: ${msg}`);
7207
+ }
7208
+ }
7209
+ const delivery = states.length > 0 ? await deliverOutputs(states, opts, logger) : { finished: [], failures: [] };
7210
+ failures.push(...delivery.failures);
7211
+ printFinished(delivery.finished, opts);
7212
+ if (failures.length > 0) {
7213
+ const total = ids.length;
7214
+ const ok2 = total - failures.length;
7215
+ const header = total > 1 ? `${ok2} of ${total} ${kind} jobs delivered; ${failures.length} failed:
7216
+ ` : "";
7217
+ throw new HeygenJobFailedError(`${header}${failures.join("\n")}`);
7218
+ }
7219
+ return delivery.finished;
7220
+ }
7221
+
7222
+ // src/providers/heygen/commands/catalog.ts
7223
+ function pageLimit(raw, max) {
7224
+ return numberInRange(raw, "--limit", 1, max, true);
7225
+ }
7226
+ function printTable(page, columns) {
7227
+ if (page.items.length === 0) {
7228
+ console.log("No results.");
7229
+ } else {
7230
+ const cells = page.items.map(
7231
+ (row) => columns.map(([, get]) => {
7232
+ const v = get(row);
7233
+ return Array.isArray(v) ? v.join(",") : v === null || v === void 0 ? "-" : String(v);
7234
+ })
7235
+ );
7236
+ const widths = columns.map(
7237
+ ([h], i) => Math.min(48, Math.max(h.length, ...cells.map((c2) => (c2[i] ?? "").length)))
7238
+ );
7239
+ const fmt = (vals) => vals.map((v, i) => v.padEnd(widths[i] ?? 0)).join(" ").trimEnd();
7240
+ console.log(fmt(columns.map(([h]) => h)));
7241
+ for (const c2 of cells) console.log(fmt(c2));
7242
+ }
7243
+ if (page.hasMore && page.nextToken) console.log(`
7244
+ More results: --cursor ${page.nextToken}`);
7245
+ }
7246
+ function printPage(page, json, columns) {
7247
+ if (json) {
7248
+ console.log(
7249
+ JSON.stringify(
7250
+ { data: page.items, has_more: page.hasMore, next_token: page.nextToken },
7251
+ null,
7252
+ 2
7253
+ )
7254
+ );
7255
+ } else {
7256
+ printTable(page, columns);
7257
+ }
7258
+ }
7259
+ function registerHeygenCatalogCommands(parent) {
7260
+ parent.command("avatars").description(
7261
+ "List avatar looks (the look ID is the --avatar for videos); --groups lists characters instead"
7262
+ ).option("--groups", "List avatar groups (characters) instead of looks").option("--group <group-id>", "Only looks in this avatar group").option("--type <t>", `Look type: ${AVATAR_TYPES.join(" | ")}`).option("--ownership <o>", "public (presets) | private (yours)").option("--limit <n>", `Page size (1-${LIMITS.avatarListMax})`).option("--cursor <token>", "Next-page cursor from a previous call").option("--json", "Print JSON").action(
7263
+ async (opts) => {
7264
+ const ownership = oneOf(opts.ownership, OWNERSHIP_VALUES, "--ownership");
7265
+ const avatarType = oneOf(opts.type, AVATAR_TYPES, "--type");
7266
+ const limit = pageLimit(opts.limit, LIMITS.avatarListMax);
7267
+ const client = createHeygenClientFromEnv();
7268
+ if (opts.groups) {
7269
+ const page2 = await client.listAvatarGroups({ ownership, limit, token: opts.cursor });
7270
+ printPage(page2, opts.json, [
7271
+ ["GROUP_ID", (g) => g.id],
7272
+ ["NAME", (g) => g.name],
7273
+ ["LOOKS", (g) => g.looks_count],
7274
+ ["GENDER", (g) => g.gender],
7275
+ ["DEFAULT_VOICE", (g) => g.default_voice_id]
7276
+ ]);
7277
+ if (!opts.json)
7278
+ console.log("\nUse `heygen avatars --group <GROUP_ID>` to list its looks.");
7279
+ return;
7280
+ }
7281
+ const page = await client.listAvatarLooks({
7282
+ group_id: opts.group,
7283
+ avatar_type: avatarType,
7284
+ ownership,
7285
+ limit,
7286
+ token: opts.cursor
7287
+ });
7288
+ printPage(page, opts.json, [
7289
+ ["LOOK_ID", (l) => l.id],
7290
+ ["NAME", (l) => l.name],
7291
+ ["TYPE", (l) => l.avatar_type],
7292
+ ["GROUP_ID", (l) => l.group_id],
7293
+ ["ENGINES", (l) => l.supported_api_engines],
7294
+ ["DEFAULT_VOICE", (l) => l.default_voice_id]
7295
+ ]);
7296
+ }
7297
+ );
7298
+ parent.command("voices").description("List voices (voice_id for `heygen video`, `video-agent`, and `tts`)").option("--language <name>", "Filter by language, e.g. English").option("--gender <g>", "male | female").option("--engine <e>", "Filter by engine, e.g. starfish | orca | elevenlabs").option("--type <t>", "public (default) | private (your cloned voices)").option("--limit <n>", `Page size (1-${LIMITS.voiceListMax})`).option("--cursor <token>", "Next-page cursor from a previous call").option("--json", "Print JSON").action(
7299
+ async (opts) => {
7300
+ const type = oneOf(opts.type, OWNERSHIP_VALUES, "--type");
7301
+ const limit = pageLimit(opts.limit, LIMITS.voiceListMax);
7302
+ const client = createHeygenClientFromEnv();
7303
+ const page = await client.listVoices({
7304
+ language: opts.language,
7305
+ gender: opts.gender,
7306
+ engine: opts.engine,
7307
+ type,
7308
+ limit,
7309
+ token: opts.cursor
7310
+ });
7311
+ printPage(page, opts.json, [
7312
+ ["VOICE_ID", (v) => v.voice_id],
7313
+ ["NAME", (v) => v.name],
7314
+ ["LANGUAGE", (v) => v.language],
7315
+ ["GENDER", (v) => v.gender],
7316
+ ["ENGINES", (v) => v.available_engines]
7317
+ ]);
7318
+ }
7319
+ );
7320
+ parent.command("styles").description("List Video Agent visual styles (style_id for `heygen video-agent --style`)").option("--tag <tag>", "Filter by tag, e.g. cinematic").option("--limit <n>", `Page size (1-${LIMITS.stylesListMax})`).option("--cursor <token>", "Next-page cursor from a previous call").option("--json", "Print JSON").action(async (opts) => {
7321
+ const limit = pageLimit(opts.limit, LIMITS.stylesListMax);
7322
+ const client = createHeygenClientFromEnv();
7323
+ const page = await client.listVideoAgentStyles({ tag: opts.tag, limit, token: opts.cursor });
7324
+ printPage(page, opts.json, [
7325
+ ["STYLE_ID", (s) => s.style_id],
7326
+ ["NAME", (s) => s.name],
7327
+ ["TAGS", (s) => s.tags],
7328
+ ["ASPECT", (s) => s.aspect_ratio]
7329
+ ]);
7330
+ });
7331
+ parent.command("languages").description("List supported video translation target languages").option("--json", "Print JSON").action(async (opts) => {
7332
+ const client = createHeygenClientFromEnv();
7333
+ const languages = await client.listTranslationLanguages();
7334
+ if (opts.json) console.log(JSON.stringify(languages, null, 2));
7335
+ else for (const l of languages) console.log(l);
7336
+ });
7337
+ parent.command("me").description("Show the HeyGen account for the API key (billing type, API credits/balance)").option("--json", "Print JSON").action(async (opts) => {
7338
+ const client = createHeygenClientFromEnv();
7339
+ const me = await client.getCurrentUser();
7340
+ if (opts.json) {
7341
+ console.log(JSON.stringify(me, null, 2));
7342
+ return;
7343
+ }
7344
+ const name = [me.first_name, me.last_name].filter(Boolean).join(" ") || me.username || "-";
7345
+ console.log(`User: ${name}`);
7346
+ if (me.billing_type) console.log(`Billing type: ${me.billing_type}`);
7347
+ for (const key of ["wallet", "subscription", "usage_based"]) {
7348
+ if (me[key] !== void 0 && me[key] !== null) {
7349
+ console.log(`${key}: ${JSON.stringify(me[key])}`);
7350
+ }
7351
+ }
7352
+ });
7353
+ }
7354
+
7355
+ // src/providers/heygen/commands/lipsync.ts
7356
+ init_errors();
7357
+ function registerHeygenLipsyncCommand(parent) {
7358
+ const cmd = parent.command("lipsync").description(
7359
+ "Lip-sync an existing video to new audio (refs: https:// URL, asset:<id>, or file)"
7360
+ ).requiredOption("--video <ref>", "Source video").requiredOption("--audio <ref>", "Replacement audio").option("--mode <m>", "speed (default) | precision").option("--keep-format", "Preserve source resolution/bitrate").option("--no-dynamic-duration", "Disable dynamic duration adjustment").option("--disable-music", "Remove the background music track").option("--enhance-speech", "Enhance speech quality").option("--start <s>", "Start time in seconds").option("--end <s>", "End time in seconds").option("--title <text>", "Job title").option("--folder <id>", "Destination folder ID").option("--callback-url <url>", "HTTPS webhook URL notified on completion").option("--callback-id <id>", "Caller ID echoed in the webhook payload");
7361
+ addWaitOptions(cmd).action(async (opts) => {
7362
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7363
+ const mode = qualityMode(opts.mode);
7364
+ const start = numberInRange(opts.start, "--start", 0, Number.MAX_SAFE_INTEGER);
7365
+ const end = numberInRange(opts.end, "--end", 0, Number.MAX_SAFE_INTEGER);
7366
+ if (start !== void 0 && end !== void 0 && end <= start) {
7367
+ throw new ValidationError("--end must be greater than --start");
7368
+ }
7369
+ const callbackUrl = checkCallback(opts.callbackUrl);
7370
+ const client = createHeygenClientFromEnv();
7371
+ const body = {
7372
+ video: await resolveMediaInput(client, opts.video, logger),
7373
+ audio: await resolveMediaInput(client, opts.audio, logger)
7374
+ };
7375
+ if (mode) body.mode = mode;
7376
+ if (opts.keepFormat) body.keep_the_same_format = true;
7377
+ if (opts.dynamicDuration === false) body.enable_dynamic_duration = false;
7378
+ if (opts.disableMusic) body.disable_music_track = true;
7379
+ if (opts.enhanceSpeech) body.enable_speech_enhancement = true;
7380
+ if (start !== void 0) body.start_time = start;
7381
+ if (end !== void 0) body.end_time = end;
7382
+ if (opts.title) body.title = opts.title;
7383
+ if (opts.folder) body.folder_id = opts.folder;
7384
+ if (callbackUrl) body.callback_url = callbackUrl;
7385
+ if (opts.callbackId) body.callback_id = opts.callbackId;
7386
+ logger.info("Submitting lipsync...");
7387
+ const created = await client.createLipsync(body);
7388
+ await completeJobs(client, "lipsync", [created.lipsync_id], opts, logger);
7389
+ });
7390
+ }
7391
+
7392
+ // src/providers/heygen/commands/model-video.ts
7393
+ var PENDING = { type: "asset_id", asset_id: "pending" };
7394
+ var pending = (refs) => (refs ?? []).map(() => PENDING);
7395
+ function registerHeygenModelVideoCommand(parent) {
7396
+ const cmd = parent.command("model-video <prompt>").description(
7397
+ "HeyGen Video model (heygen-video-1): 5-15 s scene with audio from a prompt (not an avatar video)"
7398
+ ).option("--mode <m>", "text_to_video | image_to_video | reference_to_video (inferred)").option("--image <ref>", "First frame for image_to_video: https:// URL, asset:<id>, or file").option("--reference-image <ref>", "Reference image (repeatable, max 9)", collect5).option("--reference-video <ref>", "Reference video (repeatable, max 3)", collect5).option("--reference-audio <ref>", "Reference audio (repeatable, max 3)", collect5).option("--duration <s>", "Clip length 5-15 seconds").option("--resolution <r>", "480p | 768p | 1080p | 2k").option("--aspect-ratio <r>", "21:9 | 16:9 | 4:3 | 1:1 | 3:4 | 9:16 | adaptive").option("--prompt-enhancement <p>", "turbo | quality | disabled").option("--seed <n>", "Seed for repeatable output").option("--callback-url <url>", "HTTPS webhook URL notified on completion").option("--callback-id <id>", "Caller ID echoed in the webhook payload");
7399
+ addWaitOptions(cmd).action(async (prompt, opts) => {
7400
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7401
+ const input = {
7402
+ prompt,
7403
+ mode: opts.mode,
7404
+ duration: opts.duration,
7405
+ resolution: opts.resolution,
7406
+ aspectRatio: opts.aspectRatio,
7407
+ promptEnhancement: opts.promptEnhancement,
7408
+ seed: opts.seed,
7409
+ callbackUrl: opts.callbackUrl,
7410
+ callbackId: opts.callbackId
7411
+ };
7412
+ buildModelVideoRequest({
7413
+ ...input,
7414
+ image: opts.image ? PENDING : void 0,
7415
+ referenceImages: pending(opts.referenceImage),
7416
+ referenceVideos: pending(opts.referenceVideo),
7417
+ referenceAudio: pending(opts.referenceAudio)
7418
+ });
7419
+ const client = createHeygenClientFromEnv();
7420
+ if (opts.image) input.image = await resolveMediaInput(client, opts.image, logger);
7421
+ input.referenceImages = await resolveMediaInputs(client, opts.referenceImage, logger);
7422
+ input.referenceVideos = await resolveMediaInputs(client, opts.referenceVideo, logger);
7423
+ input.referenceAudio = await resolveMediaInputs(client, opts.referenceAudio, logger);
7424
+ const body = buildModelVideoRequest(input);
7425
+ logger.info(`Submitting HeyGen Video (${body.mode})...`);
7426
+ const created = await client.createModelVideo(body);
7427
+ await completeJobs(client, "model-video", [created.video_id], opts, logger);
7428
+ });
7429
+ }
7430
+
7431
+ // src/providers/heygen/commands/run.ts
7432
+ init_errors();
7433
+ import path33 from "path";
7434
+ function extractJobIds(operation, data) {
7435
+ const op = HEYGEN_OPERATIONS[operation];
7436
+ if (!op) return [];
7437
+ if (op.idsField) {
7438
+ const ids = data[op.idsField];
7439
+ return Array.isArray(ids) ? ids.filter((x) => typeof x === "string" && !!x) : [];
7440
+ }
7441
+ const id = op.idField ? data[op.idField] : void 0;
7442
+ return typeof id === "string" && id ? [id] : [];
7443
+ }
7444
+ function registerHeygenRunCommand(parent) {
7445
+ const names = Object.keys(HEYGEN_OPERATIONS);
7446
+ const cmd = parent.command("run <operation>").description(`Send a raw v3 payload to a documented create operation: ${names.join(", ")}`).requiredOption("--input <json>", "Request body, inline JSON or @path/to/payload.json");
7447
+ addWaitOptions(cmd).action(async (operation, opts) => {
7448
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7449
+ const op = HEYGEN_OPERATIONS[operation];
7450
+ if (!op) {
7451
+ throw new ValidationError(
7452
+ `Unknown operation "${operation}". Supported: ${names.map((n) => `${n} (${HEYGEN_OPERATIONS[n]?.description})`).join("; ")}`
7453
+ );
7454
+ }
7455
+ const body = parseJsonInput(opts.input);
7456
+ const client = createHeygenClientFromEnv();
7457
+ logger.info(`Calling ${op.description}...`);
7458
+ const json = await client.send(op.path, {
7459
+ method: "POST",
7460
+ body,
7461
+ idempotencyKey: op.idempotent
7462
+ });
7463
+ const data = unwrapData(json, op.path);
7464
+ if (op.kind === null) {
7465
+ const audioUrl = typeof data.audio_url === "string" ? data.audio_url : void 0;
7466
+ if (opts.download === false || !audioUrl) {
7467
+ console.log(JSON.stringify(data, null, 2));
7468
+ return;
7469
+ }
7470
+ const ext = inferExtension(audioUrl, "mp3");
7471
+ const dest = opts.output ? path33.resolve(opts.output) : path33.join(getOutputDir(), `heygen-speech-${Date.now()}.${ext}`);
7472
+ await downloadOutput(audioUrl, dest, "Re-run the command to generate a fresh URL.");
7473
+ console.log(`Saved: ${dest}`);
7474
+ return;
7475
+ }
7476
+ const ids = extractJobIds(operation, data);
7477
+ if (ids.length === 0) {
7478
+ throw new ValidationError(
7479
+ `HeyGen response for ${operation} had no job ID: ${JSON.stringify(data).slice(0, 300)}`
7480
+ );
7481
+ }
7482
+ await completeJobs(client, op.kind, ids, opts, logger);
7483
+ });
7484
+ }
7485
+
7486
+ // src/providers/heygen/commands/status.ts
7487
+ function registerHeygenStatusCommand(parent) {
7488
+ parent.command("status <id>").description("Check (or wait for and download) a HeyGen job by ID").option(
7489
+ "--type <type>",
7490
+ "agent (session ID) | video | translation | lipsync | model-video",
7491
+ "video"
7492
+ ).option("--wait", "Poll until the job finishes").option("--download", "Download the output when complete (implies --wait)").option("--wait-timeout <ms>", "Max time to wait", "1800000").option(
7493
+ "--poll-interval <ms>",
7494
+ "Initial poll interval (backs off to 30s)",
7495
+ String(DEFAULT_POLL_INTERVAL_MS)
7496
+ ).option("--output <path>", "Output file path (with --download)").option("--captions", "Also download the caption/subtitle file when available").option(
7497
+ "--burn-captions",
7498
+ "Video: use the burned-in caption render (captioned_video_url); fails if absent"
7499
+ ).option("--no-thumb", "Skip downloading the video thumbnail").option("--json", "Print a JSON summary").option("--raw", "Print the raw API payload (includes signed URLs) instead of the summary").option("-v, --verbose", "Verbose logging").action(async (id, opts) => {
7500
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7501
+ const kind = oneOf(opts.type, JOB_KINDS, "--type") ?? "video";
7502
+ const client = createHeygenClientFromEnv();
7503
+ if (opts.download) {
7504
+ await completeJobs(client, kind, [id], { ...opts, async: false, download: true }, logger);
7505
+ return;
7506
+ }
7507
+ const read = { burnCaptions: opts.burnCaptions };
7508
+ const state = opts.wait ? await waitForJob(client, kind, id, {
7509
+ ...read,
7510
+ timeoutMs: parsePositiveInt(
7511
+ opts.waitTimeout,
7512
+ "--wait-timeout",
7513
+ DEFAULT_WAIT_TIMEOUT_MS
7514
+ ),
7515
+ intervalMs: parsePositiveInt(
7516
+ opts.pollInterval,
7517
+ "--poll-interval",
7518
+ DEFAULT_POLL_INTERVAL_MS
7519
+ ),
7520
+ resumeHint: `Resume with: ${resumeCommand2(kind, id, read)}`
7521
+ }) : await getJobState(client, kind, id, read);
7522
+ console.log(JSON.stringify(opts.raw ? state.raw : summarizeState(state), null, 2));
7523
+ if (state.done) assertJobSucceeded(state);
7524
+ else if (!opts.json) logger.info(`Still ${state.status}. Re-run with --wait to poll.`);
7525
+ });
7526
+ }
7527
+
7528
+ // src/providers/heygen/commands/template.ts
7529
+ init_errors();
7530
+ function templateBody(input, title) {
7531
+ const body = "variables" in input ? { ...input } : { variables: input };
7532
+ const vars = body.variables;
7533
+ if (vars === null || typeof vars !== "object" || Array.isArray(vars)) {
7534
+ throw new ValidationError("Template input must contain a `variables` object");
7535
+ }
7536
+ if (title) body.title = title;
7537
+ return body;
7538
+ }
7539
+ function registerHeygenTemplateCommand(parent) {
7540
+ const cmd = parent.command("template <template-id>").description("Generate a video from a HeyGen Studio template by filling its variables").requiredOption(
7541
+ "--input <json>",
7542
+ "Variables map or full request body, inline JSON or @path/to/vars.json"
7543
+ ).option("--title <text>", "Video title");
7544
+ addWaitOptions(cmd).action(async (templateId, opts) => {
7545
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7546
+ const body = templateBody(parseJsonInput(opts.input), opts.title);
7547
+ const client = createHeygenClientFromEnv();
7548
+ logger.info(`Generating from template ${templateId}...`);
7549
+ const video = await client.generateFromTemplate(templateId, body);
7550
+ await completeJobs(client, "video", [video.id], opts, logger);
7551
+ });
7552
+ }
7553
+
7554
+ // src/providers/heygen/commands/translate.ts
7555
+ init_errors();
7556
+ function registerHeygenTranslateCommand(parent) {
7557
+ const cmd = parent.command("translate <source>").description(
7558
+ "Translate a video (voice clone + lip-sync). Source: https:// URL, asset:<id>, or local file"
7559
+ ).requiredOption(
7560
+ "--languages <list>",
7561
+ 'Comma-separated target language names from `heygen languages`, e.g. "Spanish (Spain),English"'
7562
+ ).option("--mode <m>", "speed (default) | precision (higher lip-sync quality)").option("--input-language <code>", "Source language (auto-detected when omitted)").option("--audio-only", "Translate audio only and keep the original video frames").option("--audio <ref>", "Custom dubbing audio: https:// URL, asset:<id>, or file").option("--speakers <n>", "Number of speakers (improves separation)").option("--keep-format", "Preserve source resolution/bitrate").option("--no-dynamic-duration", "Disable dynamic duration adjustment").option("--disable-music", "Remove the background music track").option("--enhance-speech", "Enhance speech quality").option("--start <s>", "Start time in seconds (partial translation)").option("--end <s>", "End time in seconds (partial translation)").option("--brand-glossary <id>", "Brand glossary ID (term translations)").option("--title <text>", "Job title").option("--folder <id>", "Destination folder ID").option("--callback-url <url>", "HTTPS webhook URL notified on completion").option("--callback-id <id>", "Caller ID echoed in the webhook payload");
7563
+ addWaitOptions(cmd).action(async (source, opts) => {
7564
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7565
+ const languages = splitLanguages(opts.languages);
7566
+ const mode = qualityMode(opts.mode);
7567
+ const speakers = numberInRange(opts.speakers, "--speakers", 1, 100, true);
7568
+ const start = numberInRange(opts.start, "--start", 0, Number.MAX_SAFE_INTEGER);
7569
+ const end = numberInRange(opts.end, "--end", 0, Number.MAX_SAFE_INTEGER);
7570
+ if (start !== void 0 && end !== void 0 && end <= start) {
7571
+ throw new ValidationError("--end must be greater than --start");
7572
+ }
7573
+ const callbackUrl = checkCallback(opts.callbackUrl);
7574
+ const client = createHeygenClientFromEnv();
7575
+ const video = await resolveMediaInput(client, source, logger);
7576
+ const audio = opts.audio ? await resolveMediaInput(client, opts.audio, logger) : void 0;
7577
+ const body = { video, output_languages: languages };
7578
+ if (audio) body.audio = audio;
7579
+ if (mode) body.mode = mode;
7580
+ if (opts.inputLanguage) body.input_language = opts.inputLanguage;
7581
+ if (opts.audioOnly) body.translate_audio_only = true;
7582
+ if (speakers !== void 0) body.speaker_num = speakers;
7583
+ if (opts.keepFormat) body.keep_the_same_format = true;
7584
+ if (opts.dynamicDuration === false) body.enable_dynamic_duration = false;
7585
+ if (opts.disableMusic) body.disable_music_track = true;
7586
+ if (opts.enhanceSpeech) body.enable_speech_enhancement = true;
7587
+ if (start !== void 0) body.start_time = start;
7588
+ if (end !== void 0) body.end_time = end;
7589
+ if (opts.brandGlossary) body.brand_glossary_id = opts.brandGlossary;
7590
+ if (opts.title) body.title = opts.title;
7591
+ if (opts.folder) body.folder_id = opts.folder;
7592
+ if (callbackUrl) body.callback_url = callbackUrl;
7593
+ if (opts.callbackId) body.callback_id = opts.callbackId;
7594
+ logger.info(`Submitting translation into ${languages.length} language(s)...`);
7595
+ const created = await client.createVideoTranslation(body);
7596
+ await completeJobs(client, "translation", created.video_translation_ids, opts, logger);
7597
+ });
7598
+ }
7599
+
7600
+ // src/providers/heygen/commands/tts.ts
7601
+ import path34 from "path";
7602
+ init_redact();
7603
+ function registerHeygenTtsCommand(parent) {
7604
+ parent.command("tts <text>").description("Text-to-speech with a HeyGen catalog voice (1-5000 characters)").requiredOption("--voice <voice-id>", "Voice ID from `heygen voices`").option("--ssml", "Treat the text as SSML").option("--speed <n>", "Speed multiplier 0.5-2.0").option("--language <code>", "Base language code, e.g. en (auto-detected when omitted)").option("--locale <tag>", "Locale tag for Starfish voices, e.g. zh-HK").option("--engine <e>", "starfish | orca | elevenlabs (defaults to the voice's engine)").option("--output <path>", "Output audio path").option("--no-download", "Print the audio URL instead of downloading").option("--json", "Print a JSON summary").option("-v, --verbose", "Verbose logging").action(async (text, opts) => {
7605
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7606
+ const body = buildSpeechRequest({
7607
+ text,
7608
+ voiceId: opts.voice,
7609
+ ssml: opts.ssml,
7610
+ speed: opts.speed,
7611
+ language: opts.language,
7612
+ locale: opts.locale,
7613
+ engine: opts.engine
7614
+ });
7615
+ const client = createHeygenClientFromEnv();
7616
+ logger.info("Generating speech...");
7617
+ const res = await client.createSpeech(body);
7618
+ const summary = {
7619
+ duration: res.duration,
7620
+ engine: res.engine,
7621
+ request_id: res.request_id ?? null,
7622
+ audio_url: redactUrl(res.audio_url)
7623
+ };
7624
+ if (opts.download === false) {
7625
+ if (opts.json)
7626
+ console.log(JSON.stringify({ ...summary, audio_url: res.audio_url }, null, 2));
7627
+ else console.log(res.audio_url);
7628
+ return;
7629
+ }
7630
+ const ext = inferExtension(res.audio_url, "mp3");
7631
+ const name = `heygen-tts-${res.request_id ?? Date.now()}.${ext}`.replace(/[^\w.-]+/g, "-");
7632
+ const dest = opts.output ? path34.resolve(opts.output) : path34.join(getOutputDir(), name);
7633
+ await downloadOutput(res.audio_url, dest, "Re-run the command to generate a fresh URL.");
7634
+ if (opts.json) console.log(JSON.stringify({ ...summary, file: dest }, null, 2));
7635
+ else console.log(`Saved: ${dest} (${res.duration}s, ${res.engine})`);
7636
+ });
7637
+ }
7638
+
7639
+ // src/providers/heygen/commands/video-agent.ts
7640
+ function registerHeygenVideoAgentCommand(parent) {
7641
+ const cmd = parent.command("video-agent <prompt>").description(
7642
+ "Prompt-to-video with HeyGen Video Agent (agent writes the script and picks avatar/voice/scenes)"
7643
+ ).option("--avatar <look-id>", "Avatar look ID to feature (see `heygen avatars`)").option("--voice <voice-id>", "Narration voice ID (see `heygen voices`)").option("--style <style-id>", "Visual style ID (see `heygen styles`)").option("--brand-kit <id>", "Brand kit ID (colors, fonts, logos)").option("--brand-glossary <id>", "Brand glossary ID (pronunciations)").option("--orientation <o>", "landscape | portrait (auto-detected when omitted)").option(
7644
+ "--file <ref>",
7645
+ "Attachment: https:// URL, asset:<asset_id>, or local file to upload (repeatable, max 20)",
7646
+ collect5
7647
+ ).option("--callback-url <url>", "HTTPS webhook URL notified on completion/failure").option("--callback-id <id>", "Caller ID echoed in the webhook payload");
7648
+ addWaitOptions(cmd).action(async (prompt, opts) => {
7649
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7650
+ buildVideoAgentRequest({
7651
+ prompt,
7652
+ orientation: opts.orientation,
7653
+ callbackUrl: opts.callbackUrl
7654
+ });
7655
+ const client = createHeygenClientFromEnv();
7656
+ const files = await resolveMediaInputs(client, opts.file, logger);
7657
+ const body = buildVideoAgentRequest({
7658
+ prompt,
7659
+ avatarId: opts.avatar,
7660
+ voiceId: opts.voice,
7661
+ styleId: opts.style,
7662
+ brandKitId: opts.brandKit,
7663
+ brandGlossaryId: opts.brandGlossary,
7664
+ orientation: opts.orientation,
7665
+ files,
7666
+ callbackUrl: opts.callbackUrl,
7667
+ callbackId: opts.callbackId
7668
+ });
7669
+ logger.info("Creating Video Agent session...");
7670
+ const created = await client.createVideoAgent(body);
7671
+ logger.debug(`session ${created.session_id} status=${created.status}`);
7672
+ await completeJobs(client, "agent", [created.session_id], opts, logger);
7673
+ });
7674
+ }
7675
+
7676
+ // src/providers/heygen/commands/video.ts
7677
+ function toInput(script, opts) {
7678
+ return {
7679
+ avatarId: opts.avatar,
7680
+ script,
7681
+ voiceId: opts.voice,
7682
+ resolution: opts.resolution,
7683
+ aspectRatio: opts.aspectRatio,
7684
+ engine: opts.engine,
7685
+ referenceLookId: opts.referenceLook,
7686
+ motionPrompt: opts.motionPrompt,
7687
+ expressiveness: opts.expressiveness,
7688
+ fit: opts.fit,
7689
+ backgroundColor: opts.backgroundColor,
7690
+ removeBackground: opts.removeBackground,
7691
+ outputFormat: opts.outputFormat,
7692
+ captions: opts.captions,
7693
+ burnCaptions: opts.burnCaptions,
7694
+ speed: opts.speed,
7695
+ pitch: opts.pitch,
7696
+ volume: opts.volume,
7697
+ locale: opts.locale,
7698
+ title: opts.title,
7699
+ folderId: opts.folder,
7700
+ brandGlossaryId: opts.brandGlossary,
7701
+ callbackUrl: opts.callbackUrl,
7702
+ callbackId: opts.callbackId
7703
+ };
7704
+ }
7705
+ var PENDING_UPLOAD = { type: "asset_id", asset_id: "pending" };
7706
+ function registerHeygenVideoCommand(parent) {
7707
+ const cmd = parent.command("video [script]").description(
7708
+ "Scripted avatar video: your avatar look + voice speak your exact script (or lip-sync --audio)"
7709
+ ).requiredOption("--avatar <look-id>", "Avatar look ID (from `heygen avatars`, not a group ID)").option("--voice <voice-id>", "Voice ID (defaults to the look's default voice)").option(
7710
+ "--audio <ref>",
7711
+ "Recorded audio instead of a script: https:// URL, asset:<id>, or file"
7712
+ ).option("--resolution <r>", "4k | 1080p | 720p (4k: Avatar III digital twins/studio avatars)").option("--aspect-ratio <r>", "16:9 | 9:16 | 4:5 | 5:4 | 1:1 | auto", "auto").option("--engine <e>", "avatar_iv (default) | avatar_v | avatar_iii").option("--reference-look <look-id>", "Avatar V animation reference (digital_twin look)").option("--motion-prompt <text>", "Body motion/gesture prompt (photo avatars, or Avatar V)").option("--expressiveness <level>", "high | medium | low (photo avatars, Avatar IV)").option("--fit <f>", "contain | cover").option("--background-color <hex>", "Solid background color, e.g. #ffffff").option("--background-image <ref>", "Background image: https:// URL, asset:<id>, or file").option("--remove-background", "Remove the avatar background (matting-enabled avatars)").option("--output-format <f>", "mp4 (default) | webm (transparent background)").option("--burn-captions", "Burn captions into the video (subtitle file is always produced)").option("--speed <n>", "Voice speed 0.5-1.5").option("--pitch <n>", "Voice pitch -50..50 semitones").option("--volume <n>", "Voice volume 0-1").option("--locale <code>", "Voice locale hint, e.g. en-US").option("--title <text>", "Video title in the HeyGen dashboard").option("--folder <id>", "Destination folder ID").option("--brand-glossary <id>", "Brand glossary ID (pronunciations)").option("--callback-url <url>", "HTTPS webhook URL notified on completion/failure").option("--callback-id <id>", "Caller ID echoed in the webhook payload");
7713
+ addWaitOptions(cmd).action(async (script, opts) => {
7714
+ const logger = createLogger({ verbose: opts.verbose ?? false });
7715
+ const input = toInput(script, opts);
7716
+ buildAvatarVideoRequest({
7717
+ ...input,
7718
+ audio: opts.audio ? PENDING_UPLOAD : void 0,
7719
+ backgroundImage: opts.backgroundImage ? PENDING_UPLOAD : void 0
7720
+ });
7721
+ const client = createHeygenClientFromEnv();
7722
+ if (opts.audio) input.audio = await resolveMediaInput(client, opts.audio, logger);
7723
+ if (opts.backgroundImage) {
7724
+ input.backgroundImage = await resolveMediaInput(client, opts.backgroundImage, logger);
7725
+ }
7726
+ const body = buildAvatarVideoRequest(input);
7727
+ logger.info("Submitting avatar video...");
7728
+ const created = await client.createVideo(body);
7729
+ await completeJobs(client, "video", [created.video_id], opts, logger);
7730
+ });
7731
+ }
7732
+
7733
+ // src/providers/heygen/commands/index.ts
7734
+ function registerHeygenCommands(program2) {
7735
+ const heygen = program2.command("heygen").description(
7736
+ "HeyGen v3: Video Agent prompt-to-video, scripted avatar videos, translation, lipsync, TTS"
7737
+ );
7738
+ registerHeygenVideoAgentCommand(heygen);
7739
+ registerHeygenVideoCommand(heygen);
7740
+ registerHeygenStatusCommand(heygen);
7741
+ registerHeygenCatalogCommands(heygen);
7742
+ registerHeygenTranslateCommand(heygen);
7743
+ registerHeygenLipsyncCommand(heygen);
7744
+ registerHeygenTtsCommand(heygen);
7745
+ registerHeygenTemplateCommand(heygen);
7746
+ registerHeygenModelVideoCommand(heygen);
7747
+ registerHeygenRunCommand(heygen);
7748
+ }
7749
+
5967
7750
  // src/providers/leonardo/commands/generate.ts
5968
- import fs29 from "fs";
5969
- import path32 from "path";
7751
+ import fs30 from "fs";
7752
+ import path35 from "path";
5970
7753
 
5971
7754
  // src/providers/leonardo/client.ts
5972
7755
  init_errors();
@@ -6039,11 +7822,11 @@ var LeonardoClient = class {
6039
7822
  }
6040
7823
  apiKey;
6041
7824
  baseUrl;
6042
- async request(path54, opts = {}) {
7825
+ async request(path57, opts = {}) {
6043
7826
  const base = this.baseUrl.replace(/\/$/, "");
6044
- const v2Override = path54.startsWith("v2:");
7827
+ const v2Override = path57.startsWith("v2:");
6045
7828
  const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
6046
- const finalPath = v2Override ? path54.slice(3) : path54;
7829
+ const finalPath = v2Override ? path57.slice(3) : path57;
6047
7830
  const url = new URL(finalBase + finalPath);
6048
7831
  if (opts.query) {
6049
7832
  for (const [k, v] of Object.entries(opts.query)) {
@@ -6062,14 +7845,14 @@ var LeonardoClient = class {
6062
7845
  timeoutMs: opts.timeoutMs ?? 12e4
6063
7846
  });
6064
7847
  }
6065
- get(path54, query, logger) {
6066
- return this.request(path54, { method: "GET", query, logger });
7848
+ get(path57, query, logger) {
7849
+ return this.request(path57, { method: "GET", query, logger });
6067
7850
  }
6068
- post(path54, body, logger) {
6069
- return this.request(path54, { method: "POST", body, logger });
7851
+ post(path57, body, logger) {
7852
+ return this.request(path57, { method: "POST", body, logger });
6070
7853
  }
6071
- delete(path54, logger) {
6072
- return this.request(path54, { method: "DELETE", logger });
7854
+ delete(path57, logger) {
7855
+ return this.request(path57, { method: "DELETE", logger });
6073
7856
  }
6074
7857
  };
6075
7858
  function createLeonardoClient() {
@@ -6080,7 +7863,7 @@ function createLeonardoClient() {
6080
7863
  function extFromUrl2(url, fallback = ".png") {
6081
7864
  try {
6082
7865
  const u = new URL(url);
6083
- const e = path32.extname(u.pathname);
7866
+ const e = path35.extname(u.pathname);
6084
7867
  return e.length > 0 ? e : fallback;
6085
7868
  } catch {
6086
7869
  return fallback;
@@ -6165,7 +7948,7 @@ function registerLeonardoGenerateCommand(parent) {
6165
7948
  if (!img) continue;
6166
7949
  const ext = extFromUrl2(img.url);
6167
7950
  const filename = autoFilename(generationId, i + 1, ext);
6168
- const filepath = path32.join(outDir, filename);
7951
+ const filepath = path35.join(outDir, filename);
6169
7952
  try {
6170
7953
  await downloadFile(img.url, filepath);
6171
7954
  saved.push(filepath);
@@ -6175,9 +7958,9 @@ function registerLeonardoGenerateCommand(parent) {
6175
7958
  }
6176
7959
  }
6177
7960
  if (opts.output && saved[0]) {
6178
- const dest = path32.resolve(opts.output);
6179
- fs29.mkdirSync(path32.dirname(dest), { recursive: true });
6180
- fs29.copyFileSync(saved[0], dest);
7961
+ const dest = path35.resolve(opts.output);
7962
+ fs30.mkdirSync(path35.dirname(dest), { recursive: true });
7963
+ fs30.copyFileSync(saved[0], dest);
6181
7964
  logger.success(`Copied to ${dest}`);
6182
7965
  }
6183
7966
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -6189,11 +7972,11 @@ Generated ${finalImages.length} image(s):`);
6189
7972
  }
6190
7973
 
6191
7974
  // src/providers/leonardo/commands/image-to-image.ts
6192
- import fs30 from "fs";
6193
- import path33 from "path";
7975
+ import fs31 from "fs";
7976
+ import path36 from "path";
6194
7977
  function extFromUrl3(url, fallback = ".png") {
6195
7978
  try {
6196
- return path33.extname(new URL(url).pathname) || fallback;
7979
+ return path36.extname(new URL(url).pathname) || fallback;
6197
7980
  } catch {
6198
7981
  return fallback;
6199
7982
  }
@@ -6255,7 +8038,7 @@ function registerLeonardoImageToImageCommand(parent) {
6255
8038
  if (!img) continue;
6256
8039
  const ext = extFromUrl3(img.url);
6257
8040
  const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
6258
- const filepath = path33.join(outDir, filename);
8041
+ const filepath = path36.join(outDir, filename);
6259
8042
  try {
6260
8043
  await downloadFile(img.url, filepath);
6261
8044
  saved.push(filepath);
@@ -6265,9 +8048,9 @@ function registerLeonardoImageToImageCommand(parent) {
6265
8048
  }
6266
8049
  }
6267
8050
  if (opts.output && saved[0]) {
6268
- const dest = path33.resolve(opts.output);
6269
- fs30.mkdirSync(path33.dirname(dest), { recursive: true });
6270
- fs30.copyFileSync(saved[0], dest);
8051
+ const dest = path36.resolve(opts.output);
8052
+ fs31.mkdirSync(path36.dirname(dest), { recursive: true });
8053
+ fs31.copyFileSync(saved[0], dest);
6271
8054
  logger.success(`Copied to ${dest}`);
6272
8055
  }
6273
8056
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -6279,7 +8062,7 @@ Generated ${finalImages.length} image(s):`);
6279
8062
  }
6280
8063
 
6281
8064
  // src/providers/leonardo/commands/video.ts
6282
- import path34 from "path";
8065
+ import path37 from "path";
6283
8066
  function v2DimsFor(resolution) {
6284
8067
  if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
6285
8068
  if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
@@ -6390,7 +8173,7 @@ async function pollAndDownload(client, jobId, opts) {
6390
8173
  logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
6391
8174
  process.exit(1);
6392
8175
  }
6393
- const outPath = opts.output ?? path34.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
8176
+ const outPath = opts.output ?? path37.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
6394
8177
  await downloadFile(videoUrl2, outPath);
6395
8178
  logger.success(`Saved ${outPath}`);
6396
8179
  await maybeDownloadThumb(gen, outPath, {
@@ -6496,7 +8279,7 @@ function registerLeonardoModelsCommand(parent) {
6496
8279
  }
6497
8280
 
6498
8281
  // src/providers/leonardo/commands/status.ts
6499
- import path35 from "path";
8282
+ import path38 from "path";
6500
8283
  var VIDEO_KEYS = ["motionMP4URL", "url"];
6501
8284
  var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
6502
8285
  var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
@@ -6561,7 +8344,7 @@ function registerLeonardoStatusCommand(parent) {
6561
8344
  const outDir = getOutputDir();
6562
8345
  let idx = 0;
6563
8346
  for (const url of videos) {
6564
- const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path35.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
8347
+ const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path38.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
6565
8348
  await downloadFile(url, dest);
6566
8349
  logger.success(`Saved ${dest}`);
6567
8350
  await maybeDownloadThumb(gen, dest, {
@@ -6574,7 +8357,7 @@ function registerLeonardoStatusCommand(parent) {
6574
8357
  for (const url of images) {
6575
8358
  const m = url.match(IMAGE_EXT_RE2);
6576
8359
  const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
6577
- const dest = path35.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
8360
+ const dest = path38.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
6578
8361
  await downloadFile(url, dest);
6579
8362
  logger.success(`Saved ${dest}`);
6580
8363
  idx++;
@@ -6731,8 +8514,8 @@ function sleep3(ms) {
6731
8514
  }
6732
8515
 
6733
8516
  // src/providers/minimax/generators/music.ts
6734
- import fs31 from "fs";
6735
- import path36 from "path";
8517
+ import fs32 from "fs";
8518
+ import path39 from "path";
6736
8519
  async function generateMinimaxMusic(opts) {
6737
8520
  const {
6738
8521
  apiKey,
@@ -6777,14 +8560,14 @@ async function generateMinimaxMusic(opts) {
6777
8560
  }
6778
8561
  const durationMs = resp.extra_info?.music_duration ?? 0;
6779
8562
  const outDir = getOutputDir();
6780
- const dest = path36.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
8563
+ const dest = path39.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
6781
8564
  try {
6782
8565
  if (audioData.startsWith("http")) {
6783
8566
  const bytes = await fetchBytes(audioData);
6784
- fs31.writeFileSync(dest, bytes);
8567
+ fs32.writeFileSync(dest, bytes);
6785
8568
  } else {
6786
8569
  const audioBytes = Buffer.from(audioData, "hex");
6787
- fs31.writeFileSync(dest, audioBytes);
8570
+ fs32.writeFileSync(dest, audioBytes);
6788
8571
  }
6789
8572
  } catch (e) {
6790
8573
  return {
@@ -6795,8 +8578,8 @@ async function generateMinimaxMusic(opts) {
6795
8578
  const durSec = durationMs / 1e3;
6796
8579
  logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
6797
8580
  if (output) {
6798
- fs31.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
6799
- fs31.copyFileSync(dest, output);
8581
+ fs32.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
8582
+ fs32.copyFileSync(dest, output);
6800
8583
  }
6801
8584
  return { status: "success", generatedAudio: dest, durationMs, model };
6802
8585
  }
@@ -6870,8 +8653,8 @@ Generated music: ${result.generatedAudio}`);
6870
8653
  init_errors();
6871
8654
 
6872
8655
  // src/providers/minimax/generators/speech.ts
6873
- import fs32 from "fs";
6874
- import path37 from "path";
8656
+ import fs33 from "fs";
8657
+ import path40 from "path";
6875
8658
  async function generateMinimaxSpeech(opts) {
6876
8659
  const {
6877
8660
  apiKey,
@@ -6918,12 +8701,12 @@ async function generateMinimaxSpeech(opts) {
6918
8701
  const audioBytes = Buffer.from(audioHex, "hex");
6919
8702
  const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
6920
8703
  const outDir = getOutputDir();
6921
- const dest = path37.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
6922
- fs32.writeFileSync(dest, audioBytes);
8704
+ const dest = path40.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
8705
+ fs33.writeFileSync(dest, audioBytes);
6923
8706
  logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
6924
8707
  if (output) {
6925
- fs32.mkdirSync(path37.dirname(path37.resolve(output)), { recursive: true });
6926
- fs32.copyFileSync(dest, output);
8708
+ fs33.mkdirSync(path40.dirname(path40.resolve(output)), { recursive: true });
8709
+ fs33.copyFileSync(dest, output);
6927
8710
  }
6928
8711
  return { status: "success", generatedAudio: dest, model };
6929
8712
  }
@@ -6970,8 +8753,8 @@ Generated audio: ${result.generatedAudio}`);
6970
8753
  init_errors();
6971
8754
 
6972
8755
  // src/providers/minimax/generators/video.ts
6973
- import fs33 from "fs";
6974
- import path38 from "path";
8756
+ import fs34 from "fs";
8757
+ import path41 from "path";
6975
8758
  async function generateMinimaxVideo(opts) {
6976
8759
  const {
6977
8760
  apiKey,
@@ -7017,18 +8800,18 @@ async function generateMinimaxVideo(opts) {
7017
8800
  return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
7018
8801
  }
7019
8802
  const outDir = getOutputDir();
7020
- const dest = path38.join(outDir, `minimax_video_${Date.now()}.mp4`);
8803
+ const dest = path41.join(outDir, `minimax_video_${Date.now()}.mp4`);
7021
8804
  try {
7022
8805
  await downloadMinimaxFile(fileId, apiKey, dest, logger);
7023
8806
  } catch (e) {
7024
8807
  return { status: "error", error: e instanceof Error ? e.message : String(e) };
7025
8808
  }
7026
8809
  const elapsed = (Date.now() - start) / 1e3;
7027
- const stat = fs33.statSync(dest);
8810
+ const stat = fs34.statSync(dest);
7028
8811
  const fileSizeMb = stat.size / (1024 * 1024);
7029
8812
  if (output) {
7030
- fs33.mkdirSync(path38.dirname(path38.resolve(output)), { recursive: true });
7031
- fs33.copyFileSync(dest, output);
8813
+ fs34.mkdirSync(path41.dirname(path41.resolve(output)), { recursive: true });
8814
+ fs34.copyFileSync(dest, output);
7032
8815
  }
7033
8816
  await maybeDownloadThumb(pollResult, dest, {
7034
8817
  skip: thumb === false,
@@ -7090,8 +8873,8 @@ Generated video: ${result.generatedVideo}`);
7090
8873
 
7091
8874
  // src/providers/minimax/generators/image.ts
7092
8875
  init_errors();
7093
- import fs34 from "fs";
7094
- import path39 from "path";
8876
+ import fs35 from "fs";
8877
+ import path42 from "path";
7095
8878
  async function generateMinimaxImage(opts) {
7096
8879
  const {
7097
8880
  apiKey,
@@ -7129,8 +8912,8 @@ async function generateMinimaxImage(opts) {
7129
8912
  try {
7130
8913
  const bytes = await fetchBytes(url);
7131
8914
  const fname = `minimax_image_${Date.now()}_${i}.png`;
7132
- const dest = path39.join(outDir, fname);
7133
- fs34.writeFileSync(dest, bytes);
8915
+ const dest = path42.join(outDir, fname);
8916
+ fs35.writeFileSync(dest, bytes);
7134
8917
  savedFiles.push(dest);
7135
8918
  logger?.success(`Saved: ${dest}`);
7136
8919
  } catch (e) {
@@ -7141,8 +8924,8 @@ async function generateMinimaxImage(opts) {
7141
8924
  }
7142
8925
  }
7143
8926
  if (output && savedFiles[0]) {
7144
- fs34.mkdirSync(path39.dirname(path39.resolve(output)), { recursive: true });
7145
- fs34.copyFileSync(savedFiles[0], output);
8927
+ fs35.mkdirSync(path42.dirname(path42.resolve(output)), { recursive: true });
8928
+ fs35.copyFileSync(savedFiles[0], output);
7146
8929
  }
7147
8930
  return { status: "success", generatedImages: savedFiles, model };
7148
8931
  }
@@ -7181,8 +8964,8 @@ Generated ${finalImages.length} image(s):`);
7181
8964
 
7182
8965
  // src/providers/minimax/commands/image-to-image.ts
7183
8966
  init_errors();
7184
- import fs35 from "fs";
7185
- import path40 from "path";
8967
+ import fs36 from "fs";
8968
+ import path43 from "path";
7186
8969
  function registerMinimaxImageToImageCommand(parent) {
7187
8970
  parent.command("image-to-image").alias("i2i").description(
7188
8971
  "MiniMax character/subject preservation. CAVEAT: NOT free-form image editing \u2014 the --ref image is used only as a character/face reference for a NEW scene described by --prompt."
@@ -7237,8 +9020,8 @@ function registerMinimaxImageToImageCommand(parent) {
7237
9020
  if (!url) continue;
7238
9021
  try {
7239
9022
  const bytes = await fetchBytes(url);
7240
- const dest = path40.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
7241
- fs35.writeFileSync(dest, bytes);
9023
+ const dest = path43.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
9024
+ fs36.writeFileSync(dest, bytes);
7242
9025
  saved.push(dest);
7243
9026
  logger.success(`Saved: ${dest}`);
7244
9027
  } catch (e) {
@@ -7249,8 +9032,8 @@ function registerMinimaxImageToImageCommand(parent) {
7249
9032
  }
7250
9033
  }
7251
9034
  if (opts.output && saved[0]) {
7252
- fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
7253
- fs35.copyFileSync(saved[0], opts.output);
9035
+ fs36.mkdirSync(path43.dirname(path43.resolve(opts.output)), { recursive: true });
9036
+ fs36.copyFileSync(saved[0], opts.output);
7254
9037
  logger.success(`Copied to: ${opts.output}`);
7255
9038
  }
7256
9039
  const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
@@ -7384,12 +9167,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
7384
9167
  }
7385
9168
 
7386
9169
  // src/providers/openai/openai-audio.ts
7387
- import fs37 from "fs";
7388
- import path42 from "path";
9170
+ import fs38 from "fs";
9171
+ import path45 from "path";
7389
9172
 
7390
9173
  // src/providers/openai/client.ts
7391
- import fs36 from "fs";
7392
- import path41 from "path";
9174
+ import fs37 from "fs";
9175
+ import path44 from "path";
7393
9176
  init_errors();
7394
9177
  var DEFAULT_TIMEOUT_MS3 = 24e4;
7395
9178
  function requireOpenAIKey() {
@@ -7455,10 +9238,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
7455
9238
  return Buffer.from(await res.arrayBuffer());
7456
9239
  }
7457
9240
  function fileFromPath(filePath) {
7458
- const abs = path41.resolve(filePath);
7459
- if (!fs36.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
7460
- const bytes = fs36.readFileSync(abs);
7461
- return new File([new Uint8Array(bytes)], path41.basename(abs));
9241
+ const abs = path44.resolve(filePath);
9242
+ if (!fs37.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
9243
+ const bytes = fs37.readFileSync(abs);
9244
+ return new File([new Uint8Array(bytes)], path44.basename(abs));
7462
9245
  }
7463
9246
 
7464
9247
  // src/providers/openai/openai-audio.ts
@@ -7497,7 +9280,7 @@ async function resolveAudioReferenceDataUrl(ref) {
7497
9280
  }
7498
9281
  function audioMimeFromReference(ref) {
7499
9282
  const pathname = new URL(ref).pathname;
7500
- const ext = path42.extname(pathname).toLowerCase();
9283
+ const ext = path45.extname(pathname).toLowerCase();
7501
9284
  switch (ext) {
7502
9285
  case ".mp3":
7503
9286
  return "audio/mpeg";
@@ -7516,12 +9299,12 @@ function audioMimeFromReference(ref) {
7516
9299
  async function generateOpenAISpeech(opts) {
7517
9300
  const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
7518
9301
  const ext = extFromAudioFormat(opts.outputFormat);
7519
- const dest = path42.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
7520
- fs37.writeFileSync(dest, bytes);
9302
+ const dest = path45.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
9303
+ fs38.writeFileSync(dest, bytes);
7521
9304
  opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
7522
9305
  if (opts.output) {
7523
- fs37.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
7524
- fs37.copyFileSync(dest, opts.output);
9306
+ fs38.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
9307
+ fs38.copyFileSync(dest, opts.output);
7525
9308
  }
7526
9309
  return dest;
7527
9310
  }
@@ -7543,9 +9326,9 @@ async function transcribeOpenAIAudio(opts) {
7543
9326
  );
7544
9327
  const text = typeof data.text === "string" ? data.text : void 0;
7545
9328
  const ext = opts.format === "text" ? "txt" : "json";
7546
- const dest = opts.output ? path42.resolve(opts.output) : path42.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
7547
- fs37.mkdirSync(path42.dirname(dest), { recursive: true });
7548
- fs37.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
9329
+ const dest = opts.output ? path45.resolve(opts.output) : path45.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
9330
+ fs38.mkdirSync(path45.dirname(dest), { recursive: true });
9331
+ fs38.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
7549
9332
  opts.logger?.success(`Saved transcript: ${dest}`);
7550
9333
  return { path: dest, text };
7551
9334
  }
@@ -7572,9 +9355,9 @@ Generated audio: ${dest}`);
7572
9355
  }
7573
9356
 
7574
9357
  // src/providers/openai/codex-image-driver.ts
7575
- import fs38 from "fs";
9358
+ import fs39 from "fs";
7576
9359
  import os3 from "os";
7577
- import path43 from "path";
9360
+ import path46 from "path";
7578
9361
  import { execa } from "execa";
7579
9362
  init_errors();
7580
9363
  var defaultRunner = async (cmd, args, opts) => {
@@ -7623,12 +9406,12 @@ function sanitizeCodexEnv() {
7623
9406
  async function runCodexImageGeneration(opts) {
7624
9407
  const runner = opts.runner ?? defaultRunner;
7625
9408
  const ext = extFromImageFormat(opts.outputFormat);
7626
- const dest = opts.output ? path43.resolve(opts.output) : path43.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
7627
- fs38.mkdirSync(path43.dirname(dest), { recursive: true });
7628
- const tmp = fs38.mkdtempSync(path43.join(os3.tmpdir(), "multix-codex-image-"));
7629
- const schema = path43.join(tmp, "schema.json");
7630
- const result = path43.join(tmp, "result.json");
7631
- fs38.writeFileSync(
9409
+ const dest = opts.output ? path46.resolve(opts.output) : path46.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
9410
+ fs39.mkdirSync(path46.dirname(dest), { recursive: true });
9411
+ const tmp = fs39.mkdtempSync(path46.join(os3.tmpdir(), "multix-codex-image-"));
9412
+ const schema = path46.join(tmp, "schema.json");
9413
+ const result = path46.join(tmp, "result.json");
9414
+ fs39.writeFileSync(
7632
9415
  schema,
7633
9416
  JSON.stringify({
7634
9417
  type: "object",
@@ -7658,7 +9441,7 @@ async function runCodexImageGeneration(opts) {
7658
9441
  if (res.exitCode !== 0) {
7659
9442
  throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
7660
9443
  }
7661
- if (!fs38.existsSync(dest) || fs38.statSync(dest).size === 0) {
9444
+ if (!fs39.existsSync(dest) || fs39.statSync(dest).size === 0) {
7662
9445
  throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
7663
9446
  }
7664
9447
  return { path: dest };
@@ -7675,8 +9458,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
7675
9458
 
7676
9459
  // src/providers/openai/openai-image.ts
7677
9460
  init_errors();
7678
- import fs39 from "fs";
7679
- import path44 from "path";
9461
+ import fs40 from "fs";
9462
+ import path47 from "path";
7680
9463
  function buildImageGenerationBody(opts) {
7681
9464
  return {
7682
9465
  model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
@@ -7709,14 +9492,14 @@ function saveOpenAIImages(opts) {
7709
9492
  for (let i = 0; i < items.length; i++) {
7710
9493
  const b64 = items[i]?.b64_json;
7711
9494
  if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
7712
- const dest = path44.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
7713
- fs39.writeFileSync(dest, Buffer.from(b64, "base64"));
9495
+ const dest = path47.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
9496
+ fs40.writeFileSync(dest, Buffer.from(b64, "base64"));
7714
9497
  saved.push(dest);
7715
9498
  opts.logger?.success(`Saved: ${dest}`);
7716
9499
  }
7717
9500
  if (opts.output && saved[0]) {
7718
- fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
7719
- fs39.copyFileSync(saved[0], opts.output);
9501
+ fs40.mkdirSync(path47.dirname(path47.resolve(opts.output)), { recursive: true });
9502
+ fs40.copyFileSync(saved[0], opts.output);
7720
9503
  opts.logger?.success(`Copied to: ${opts.output}`);
7721
9504
  }
7722
9505
  return saved;
@@ -7725,7 +9508,7 @@ async function resolveImageRefBytes(ref) {
7725
9508
  if (isHttpUrl(ref)) {
7726
9509
  const bytes2 = await fetchBytes(ref);
7727
9510
  const pathname = new URL(ref).pathname;
7728
- const filename = path44.basename(pathname) || "reference.png";
9511
+ const filename = path47.basename(pathname) || "reference.png";
7729
9512
  return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
7730
9513
  }
7731
9514
  const file = fileFromPath(ref);
@@ -7770,7 +9553,7 @@ function sniffImageMime2(buf, name) {
7770
9553
  if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
7771
9554
  if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
7772
9555
  return "image/webp";
7773
- const ext = path44.extname(name).toLowerCase();
9556
+ const ext = path47.extname(name).toLowerCase();
7774
9557
  if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
7775
9558
  if (ext === ".webp") return "image/webp";
7776
9559
  return "image/png";
@@ -7822,7 +9605,7 @@ function registerOpenAIImageToImageCommand(parent) {
7822
9605
  parent.command("image-to-image").alias("i2i").description("Edit images with OpenAI Image API or experimental Codex driver").requiredOption("--prompt <text>", "Text prompt describing the edit").requiredOption(
7823
9606
  "--ref <path>",
7824
9607
  "Reference image path or URL; repeatable",
7825
- collect5,
9608
+ collect6,
7826
9609
  []
7827
9610
  ).option("--driver <driver>", "api|codex|auto", "auto").option(
7828
9611
  "-m, --model <id>",
@@ -7865,7 +9648,7 @@ Generated ${finalImages.length} image(s):`);
7865
9648
  for (const f of finalImages) console.log(` ${f}`);
7866
9649
  });
7867
9650
  }
7868
- function collect5(value, prev) {
9651
+ function collect6(value, prev) {
7869
9652
  return [...prev, value];
7870
9653
  }
7871
9654
 
@@ -7874,12 +9657,12 @@ function registerOpenAITranscribeCommand(parent) {
7874
9657
  parent.command("transcribe").description("Transcribe audio/video with OpenAI STT").requiredOption("--input <path>", "Audio or video file").option("--model <id>", "STT model", process.env.OPENAI_STT_MODEL ?? DEFAULT_OPENAI_STT_MODEL).option("--format <fmt>", "json|text|diarized_json", "text").option("--language <code>", "ISO language code").option("--chunking-strategy <strategy>", "e.g. auto").option(
7875
9658
  "--known-speaker-name <name>",
7876
9659
  "Known speaker name; repeatable",
7877
- collect6,
9660
+ collect7,
7878
9661
  []
7879
9662
  ).option(
7880
9663
  "--known-speaker-reference <path>",
7881
9664
  "2-10s reference audio; repeatable",
7882
- collect6,
9665
+ collect7,
7883
9666
  []
7884
9667
  ).option("--output <path>", "Save transcript to this path").option("-v, --verbose", "Verbose logging").action(async (opts) => {
7885
9668
  const logger = createLogger({ verbose: opts.verbose ?? false });
@@ -7898,7 +9681,7 @@ function registerOpenAITranscribeCommand(parent) {
7898
9681
  ${result.text.trim()}`);
7899
9682
  });
7900
9683
  }
7901
- function collect6(value, prev) {
9684
+ function collect7(value, prev) {
7902
9685
  return [...prev, value];
7903
9686
  }
7904
9687
 
@@ -7914,8 +9697,8 @@ function registerOpenAICommands(program2) {
7914
9697
  }
7915
9698
 
7916
9699
  // src/providers/openrouter/client.ts
7917
- import fs40 from "fs";
7918
- import path45 from "path";
9700
+ import fs41 from "fs";
9701
+ import path48 from "path";
7919
9702
  init_errors();
7920
9703
 
7921
9704
  // src/providers/openrouter/payload.ts
@@ -8079,8 +9862,8 @@ async function generateOpenRouterImage(opts) {
8079
9862
  if (!url) continue;
8080
9863
  try {
8081
9864
  const bytes = await fetchBytes(url);
8082
- const dest = path45.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
8083
- fs40.writeFileSync(dest, bytes);
9865
+ const dest = path48.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
9866
+ fs41.writeFileSync(dest, bytes);
8084
9867
  savedFiles.push(dest);
8085
9868
  logger?.success(`Saved: ${dest}`);
8086
9869
  } catch (e) {
@@ -8091,8 +9874,8 @@ async function generateOpenRouterImage(opts) {
8091
9874
  }
8092
9875
  }
8093
9876
  if (output && savedFiles[0]) {
8094
- fs40.mkdirSync(path45.dirname(path45.resolve(output)), { recursive: true });
8095
- fs40.writeFileSync(output, fs40.readFileSync(savedFiles[0]));
9877
+ fs41.mkdirSync(path48.dirname(path48.resolve(output)), { recursive: true });
9878
+ fs41.writeFileSync(output, fs41.readFileSync(savedFiles[0]));
8096
9879
  }
8097
9880
  return { status: "success", generatedImages: savedFiles, model: usedModel };
8098
9881
  }
@@ -8133,8 +9916,8 @@ Generated ${finalImages.length} image(s):`);
8133
9916
  }
8134
9917
 
8135
9918
  // src/providers/openrouter/commands/image-to-image.ts
8136
- import fs41 from "fs";
8137
- import path46 from "path";
9919
+ import fs42 from "fs";
9920
+ import path49 from "path";
8138
9921
  init_errors();
8139
9922
  var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
8140
9923
  var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
@@ -8145,7 +9928,7 @@ function registerOpenRouterImageToImageCommand(parent) {
8145
9928
  ).requiredOption("--prompt <text>", "Text prompt describing the edit").requiredOption(
8146
9929
  "--ref <path>",
8147
9930
  "Reference image (URL or local path); repeatable (most models accept 1)",
8148
- collect7,
9931
+ collect8,
8149
9932
  []
8150
9933
  ).option("-m, --model <id>", `OpenRouter model id (default ${DEFAULT_MODEL})`).option("--strength <n>", "Recraft init-image strength 0..1 (Recraft models only)").option("--output <path>", "Save first generated image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
8151
9934
  async (opts) => {
@@ -8187,7 +9970,7 @@ Generated ${finalImages.length} image(s):`);
8187
9970
  }
8188
9971
  );
8189
9972
  }
8190
- function collect7(value, prev) {
9973
+ function collect8(value, prev) {
8191
9974
  return [...prev, value];
8192
9975
  }
8193
9976
  function parseStrength(raw) {
@@ -8226,14 +10009,14 @@ async function runImageToImage(opts) {
8226
10009
  const url = parsed.urls[i];
8227
10010
  if (!url) continue;
8228
10011
  const bytes = await fetchBytes(url);
8229
- const dest = path46.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
8230
- fs41.writeFileSync(dest, bytes);
10012
+ const dest = path49.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
10013
+ fs42.writeFileSync(dest, bytes);
8231
10014
  saved.push(dest);
8232
10015
  opts.logger.success(`Saved: ${dest}`);
8233
10016
  }
8234
10017
  if (opts.output && saved[0]) {
8235
- fs41.mkdirSync(path46.dirname(path46.resolve(opts.output)), { recursive: true });
8236
- fs41.copyFileSync(saved[0], opts.output);
10018
+ fs42.mkdirSync(path49.dirname(path49.resolve(opts.output)), { recursive: true });
10019
+ fs42.copyFileSync(saved[0], opts.output);
8237
10020
  opts.logger.success(`Copied to: ${opts.output}`);
8238
10021
  }
8239
10022
  return saved;
@@ -8278,13 +10061,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
8278
10061
  }
8279
10062
 
8280
10063
  // src/providers/openrouter/commands/video-status.ts
8281
- import fs42 from "fs";
8282
- import path47 from "path";
10064
+ import fs43 from "fs";
10065
+ import path50 from "path";
8283
10066
  async function downloadOpenRouterVideo(opts) {
8284
10067
  const { jobId, logger, source } = opts;
8285
10068
  const apiKey = requireOpenRouterKey();
8286
10069
  const url = videoContentUrl(jobId);
8287
- const dest = path47.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
10070
+ const dest = path50.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
8288
10071
  const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
8289
10072
  if (!r.ok) {
8290
10073
  logger.error(`Download HTTP ${r.status}`);
@@ -8294,14 +10077,14 @@ async function downloadOpenRouterVideo(opts) {
8294
10077
  logger.error("Empty response body");
8295
10078
  process.exit(1);
8296
10079
  }
8297
- fs42.mkdirSync(path47.dirname(dest), { recursive: true });
8298
- const ws = fs42.createWriteStream(dest);
10080
+ fs43.mkdirSync(path50.dirname(dest), { recursive: true });
10081
+ const ws = fs43.createWriteStream(dest);
8299
10082
  const { pipeline: pipeline3 } = await import("stream/promises");
8300
10083
  await pipeline3(r.body, ws);
8301
10084
  logger.success(`Saved ${dest}`);
8302
10085
  if (opts.output) {
8303
- fs42.mkdirSync(path47.dirname(path47.resolve(opts.output)), { recursive: true });
8304
- fs42.copyFileSync(dest, opts.output);
10086
+ fs43.mkdirSync(path50.dirname(path50.resolve(opts.output)), { recursive: true });
10087
+ fs43.copyFileSync(dest, opts.output);
8305
10088
  logger.success(`Copied to ${opts.output}`);
8306
10089
  }
8307
10090
  await maybeDownloadThumb(source, dest, {
@@ -8494,8 +10277,8 @@ function parseModelRef(ref, explicitVersion) {
8494
10277
  }
8495
10278
  return { kind: "version", version: trimmed };
8496
10279
  }
8497
- const [path54, inlineVersion, ...rest] = trimmed.split(":");
8498
- const [owner, name, ...extra] = (path54 ?? "").split("/");
10280
+ const [path57, inlineVersion, ...rest] = trimmed.split(":");
10281
+ const [owner, name, ...extra] = (path57 ?? "").split("/");
8499
10282
  if (rest.length > 0 || extra.length > 0 || !owner || !name) {
8500
10283
  throw new ValidationError(
8501
10284
  `Invalid Replicate model "${ref}" \u2014 expected owner/name, owner/name:version, or a 64-char version id`
@@ -8625,7 +10408,7 @@ function assertSucceeded(prediction2) {
8625
10408
  PROVIDER
8626
10409
  );
8627
10410
  }
8628
- var defaultSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10411
+ var defaultSleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8629
10412
  var ReplicateClient = class {
8630
10413
  apiKey;
8631
10414
  baseUrl;
@@ -8636,15 +10419,15 @@ var ReplicateClient = class {
8636
10419
  this.apiKey = opts.apiKey;
8637
10420
  this.baseUrl = (opts.baseUrl || DEFAULT_REPLICATE_BASE_URL).replace(/\/+$/, "");
8638
10421
  this.doFetch = opts.fetch ?? ((input, init) => globalThis.fetch(input, init));
8639
- this.sleep = opts.sleep ?? defaultSleep2;
10422
+ this.sleep = opts.sleep ?? defaultSleep3;
8640
10423
  }
8641
10424
  headers(extra = {}) {
8642
10425
  return { accept: "application/json", authorization: `Bearer ${this.apiKey}`, ...extra };
8643
10426
  }
8644
10427
  /** Raw request: throws HttpError so polling can classify transient failures. */
8645
- raw(path54, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
10428
+ raw(path57, method, body, headers2, timeoutMs = REQUEST_TIMEOUT_MS) {
8646
10429
  return fetchJson({
8647
- url: `${this.baseUrl}${path54}`,
10430
+ url: `${this.baseUrl}${path57}`,
8648
10431
  method,
8649
10432
  headers: this.headers(headers2),
8650
10433
  body,
@@ -8656,7 +10439,7 @@ var ReplicateClient = class {
8656
10439
  async createPrediction(opts) {
8657
10440
  const { model, input } = opts;
8658
10441
  const isOfficial = model.kind === "official";
8659
- const path54 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
10442
+ const path57 = isOfficial ? `/models/${model.owner}/${model.name}/predictions` : "/predictions";
8660
10443
  const body = isOfficial ? { input } : { version: model.version, input };
8661
10444
  const subject = isOfficial ? `model ${model.owner}/${model.name}` : `model version ${model.version}`;
8662
10445
  const headers2 = {};
@@ -8668,7 +10451,7 @@ var ReplicateClient = class {
8668
10451
  }
8669
10452
  for (let attempt = 0; ; attempt++) {
8670
10453
  try {
8671
- const res = await this.raw(path54, "POST", body, headers2, timeoutMs);
10454
+ const res = await this.raw(path57, "POST", body, headers2, timeoutMs);
8672
10455
  return validatePrediction(res);
8673
10456
  } catch (error) {
8674
10457
  if (error instanceof HttpError && error.status === 429 && attempt < CREATE_MAX_RETRIES) {
@@ -8828,8 +10611,8 @@ init_errors();
8828
10611
  init_errors();
8829
10612
  init_redact();
8830
10613
  import { once as once2 } from "events";
8831
- import fs43 from "fs";
8832
- import path48 from "path";
10614
+ import fs44 from "fs";
10615
+ import path51 from "path";
8833
10616
  import { Readable } from "stream";
8834
10617
  import { pipeline as pipeline2 } from "stream/promises";
8835
10618
 
@@ -8843,15 +10626,15 @@ function isMediaRef(value) {
8843
10626
  function extractOutput(output) {
8844
10627
  const result = { media: [], texts: [] };
8845
10628
  const seen = /* @__PURE__ */ new Set();
8846
- const visit = (value, path54) => {
10629
+ const visit = (value, path57) => {
8847
10630
  if (value === null || value === void 0) return;
8848
10631
  if (typeof value === "string") {
8849
- if (isMediaRef(value)) result.media.push({ url: value, path: path54 || "output" });
8850
- else if (value.length > 0) result.texts.push(path54 ? `${path54}: ${value}` : value);
10632
+ if (isMediaRef(value)) result.media.push({ url: value, path: path57 || "output" });
10633
+ else if (value.length > 0) result.texts.push(path57 ? `${path57}: ${value}` : value);
8851
10634
  return;
8852
10635
  }
8853
10636
  if (typeof value !== "object") {
8854
- result.texts.push(path54 ? `${path54}: ${String(value)}` : String(value));
10637
+ result.texts.push(path57 ? `${path57}: ${String(value)}` : String(value));
8855
10638
  return;
8856
10639
  }
8857
10640
  if (seen.has(value)) return;
@@ -8859,14 +10642,14 @@ function extractOutput(output) {
8859
10642
  if (Array.isArray(value)) {
8860
10643
  if (value.length > 0 && value.every((v) => typeof v === "string" && !isMediaRef(v))) {
8861
10644
  const joined = value.join("");
8862
- if (joined.length > 0) result.texts.push(path54 ? `${path54}: ${joined}` : joined);
10645
+ if (joined.length > 0) result.texts.push(path57 ? `${path57}: ${joined}` : joined);
8863
10646
  return;
8864
10647
  }
8865
- value.forEach((item, i) => visit(item, `${path54}[${i}]`));
10648
+ value.forEach((item, i) => visit(item, `${path57}[${i}]`));
8866
10649
  return;
8867
10650
  }
8868
10651
  for (const [key, val] of Object.entries(value)) {
8869
- visit(val, path54 ? `${path54}.${key}` : key);
10652
+ visit(val, path57 ? `${path57}.${key}` : key);
8870
10653
  }
8871
10654
  };
8872
10655
  visit(output, "");
@@ -8984,23 +10767,23 @@ var DOWNLOAD_TIMEOUT_MS = 3e5;
8984
10767
  function isDirTarget(output) {
8985
10768
  if (/[\\/]$/.test(output)) return true;
8986
10769
  try {
8987
- return fs43.statSync(output).isDirectory();
10770
+ return fs44.statSync(output).isDirectory();
8988
10771
  } catch {
8989
10772
  return false;
8990
10773
  }
8991
10774
  }
8992
10775
  function resolveDestination(predictionId, index, count, ext, opts) {
8993
- if (!opts.output) return path48.join(opts.outDir, outputFilename(predictionId, index, ext));
8994
- const output = path48.resolve(opts.output);
8995
- if (isDirTarget(opts.output)) return path48.join(output, outputFilename(predictionId, index, ext));
8996
- const givenExt = path48.extname(output);
10776
+ if (!opts.output) return path51.join(opts.outDir, outputFilename(predictionId, index, ext));
10777
+ const output = path51.resolve(opts.output);
10778
+ if (isDirTarget(opts.output)) return path51.join(output, outputFilename(predictionId, index, ext));
10779
+ const givenExt = path51.extname(output);
8997
10780
  if (count === 1) return givenExt ? output : `${output}${ext}`;
8998
10781
  const base = givenExt ? output.slice(0, -givenExt.length) : output;
8999
10782
  return `${base}-${String(index + 1).padStart(2, "0")}${ext}`;
9000
10783
  }
9001
10784
  async function writeResponse(response, dest) {
9002
10785
  if (!response.body) throw new HttpError(0, "Response body is null", response.url);
9003
- const fileStream = fs43.createWriteStream(dest);
10786
+ const fileStream = fs44.createWriteStream(dest);
9004
10787
  try {
9005
10788
  await pipeline2(
9006
10789
  Readable.fromWeb(response.body),
@@ -9024,8 +10807,8 @@ async function saveOne(item, predictionId, index, count, opts) {
9024
10807
  extFromUrl4(item.url) ?? ".bin",
9025
10808
  opts
9026
10809
  );
9027
- fs43.mkdirSync(path48.dirname(dest2), { recursive: true });
9028
- fs43.writeFileSync(dest2, bytes);
10810
+ fs44.mkdirSync(path51.dirname(dest2), { recursive: true });
10811
+ fs44.writeFileSync(dest2, bytes);
9029
10812
  return dest2;
9030
10813
  }
9031
10814
  const doFetch = opts.fetch ?? ((url, init) => globalThis.fetch(url, init));
@@ -9041,11 +10824,11 @@ async function saveOne(item, predictionId, index, count, opts) {
9041
10824
  }
9042
10825
  const ext = extFromUrl4(item.url) ?? extFromContentType(response.headers.get("content-type")) ?? ".bin";
9043
10826
  const dest = resolveDestination(predictionId, index, count, ext, opts);
9044
- fs43.mkdirSync(path48.dirname(dest), { recursive: true });
10827
+ fs44.mkdirSync(path51.dirname(dest), { recursive: true });
9045
10828
  try {
9046
10829
  await writeResponse(response, dest);
9047
10830
  } catch (err2) {
9048
- fs43.rmSync(dest, { force: true });
10831
+ fs44.rmSync(dest, { force: true });
9049
10832
  throw err2;
9050
10833
  }
9051
10834
  return dest;
@@ -9081,7 +10864,7 @@ function addFlowOptions(cmd, defaultTimeoutMs) {
9081
10864
  "Ask Replicate to hold the create request open (1-60s, default 60); keeps polling if still running"
9082
10865
  ).option("--wait-timeout <ms>", "Polling timeout in milliseconds", String(defaultTimeoutMs)).option("--download", "Download media outputs (default)").option("--no-download", "Print output URLs instead of downloading").option("--output <path>", "Output file path, or a directory for multiple outputs").option("-v, --verbose", "Verbose logging");
9083
10866
  }
9084
- function parsePositiveInt(raw, flag, max) {
10867
+ function parsePositiveInt2(raw, flag, max) {
9085
10868
  const n = Number(raw);
9086
10869
  if (!Number.isInteger(n) || n <= 0 || max !== void 0 && n > max) {
9087
10870
  throw new ValidationError(
@@ -9093,15 +10876,15 @@ function parsePositiveInt(raw, flag, max) {
9093
10876
  function parsePreferWait(value) {
9094
10877
  if (value === void 0 || value === false) return void 0;
9095
10878
  if (value === true) return 60;
9096
- return parsePositiveInt(value, "--prefer-wait", 60);
10879
+ return parsePositiveInt2(value, "--prefer-wait", 60);
9097
10880
  }
9098
- function resumeCommand2(id) {
10881
+ function resumeCommand3(id) {
9099
10882
  return `multix replicate status ${id} --wait --download`;
9100
10883
  }
9101
10884
  function printAsyncHandle(prediction2) {
9102
10885
  console.log(`Prediction submitted: ${prediction2.id} (status: ${prediction2.status})`);
9103
10886
  console.log(` Check: multix replicate status ${prediction2.id}`);
9104
- console.log(` Resume: ${resumeCommand2(prediction2.id)}`);
10887
+ console.log(` Resume: ${resumeCommand3(prediction2.id)}`);
9105
10888
  console.log(` Cancel: multix replicate cancel ${prediction2.id}`);
9106
10889
  }
9107
10890
  async function reportOutputs(prediction2, opts) {
@@ -9135,7 +10918,7 @@ async function completePrediction(client, prediction2, opts) {
9135
10918
  }
9136
10919
  const final = await client.waitForPrediction(prediction2, {
9137
10920
  timeoutMs: opts.waitTimeoutMs,
9138
- resumeHint: `Resume with: ${resumeCommand2(id)}`,
10921
+ resumeHint: `Resume with: ${resumeCommand3(id)}`,
9139
10922
  onPoll: (p, attempt) => {
9140
10923
  if (p.status !== lastStatus) opts.logger.debug(`attempt ${attempt}: ${p.status}`);
9141
10924
  lastStatus = p.status;
@@ -9152,7 +10935,7 @@ async function executePrediction2(params) {
9152
10935
  if (opts.async && opts.preferWait) {
9153
10936
  throw new ValidationError("--async and --prefer-wait are mutually exclusive");
9154
10937
  }
9155
- const waitTimeoutMs = parsePositiveInt(opts.waitTimeout, "--wait-timeout");
10938
+ const waitTimeoutMs = parsePositiveInt2(opts.waitTimeout, "--wait-timeout");
9156
10939
  const preferWaitSeconds = parsePreferWait(opts.preferWait);
9157
10940
  logger.info(`Submitting to Replicate ${describeModelRef(model)}...`);
9158
10941
  const prediction2 = await client.createPrediction({ model, input, preferWaitSeconds });
@@ -9186,7 +10969,7 @@ function buildImageInput(prompt, opts) {
9186
10969
  ...extra,
9187
10970
  prompt,
9188
10971
  ...opts.aspectRatio ? { aspect_ratio: opts.aspectRatio } : {},
9189
- ...opts.numOutputs ? { num_outputs: parsePositiveInt(opts.numOutputs, "--num-outputs", 10) } : {},
10972
+ ...opts.numOutputs ? { num_outputs: parsePositiveInt2(opts.numOutputs, "--num-outputs", 10) } : {},
9190
10973
  ...opts.seed !== void 0 ? { seed: parseSeed(opts.seed) } : {},
9191
10974
  ...opts.outputFormat ? { output_format: opts.outputFormat } : {}
9192
10975
  };
@@ -9223,8 +11006,8 @@ function registerReplicateImageCommand(parent) {
9223
11006
 
9224
11007
  // src/providers/replicate/node-input-files.ts
9225
11008
  init_errors();
9226
- import fs44 from "fs";
9227
- import path49 from "path";
11009
+ import fs45 from "fs";
11010
+ import path52 from "path";
9228
11011
  var DATA_URI_MAX_BYTES = 256 * 1024;
9229
11012
  var UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
9230
11013
  function formatMb2(bytes) {
@@ -9232,10 +11015,10 @@ function formatMb2(bytes) {
9232
11015
  }
9233
11016
  async function resolveFileInput(value, client, logger) {
9234
11017
  if (isMediaRef(value)) return value;
9235
- const filePath = path49.resolve(value);
11018
+ const filePath = path52.resolve(value);
9236
11019
  let stat;
9237
11020
  try {
9238
- stat = fs44.statSync(filePath);
11021
+ stat = fs45.statSync(filePath);
9239
11022
  } catch {
9240
11023
  throw new ValidationError(`Input file not found: ${value} (pass a local path or an https URL)`);
9241
11024
  }
@@ -9246,9 +11029,9 @@ async function resolveFileInput(value, client, logger) {
9246
11029
  `Input file ${value} is ${formatMb2(stat.size)}; Replicate accepts uploads under 100MB. Host it and pass an https URL instead.`
9247
11030
  );
9248
11031
  }
9249
- const filename = path49.basename(filePath);
11032
+ const filename = path52.basename(filePath);
9250
11033
  const contentType = mimeFromFilename(filename);
9251
- const bytes = fs44.readFileSync(filePath);
11034
+ const bytes = fs45.readFileSync(filePath);
9252
11035
  if (stat.size <= DATA_URI_MAX_BYTES) {
9253
11036
  logger.debug(`Inlining ${filename} (${stat.size} bytes) as a data URI`);
9254
11037
  return `data:${contentType};base64,${bytes.toString("base64")}`;
@@ -9275,14 +11058,14 @@ function parseFileAssignments(values) {
9275
11058
  }
9276
11059
 
9277
11060
  // src/providers/replicate/commands/run.ts
9278
- var collect8 = (value, previous = []) => [...previous, value];
11061
+ var collect9 = (value, previous = []) => [...previous, value];
9279
11062
  function registerReplicateRunCommand(parent) {
9280
11063
  const cmd = parent.command("run <model>").description(
9281
11064
  "Run any Replicate model: owner/name (official), owner/name:version, or a version id"
9282
11065
  ).requiredOption("--input <json>", "Model input as JSON, or @path/to/input.json").option("--version <id>", "Explicit model version id (uses the versioned endpoint)").option(
9283
11066
  "--file <key=path>",
9284
11067
  "Set input <key> to a local file or URL (uploaded if needed); repeatable",
9285
- collect8
11068
+ collect9
9286
11069
  );
9287
11070
  addFlowOptions(cmd, 6e5).action(
9288
11071
  async (model, opts) => {
@@ -9306,7 +11089,7 @@ function registerReplicateStatusCommand(parent) {
9306
11089
  const client = createReplicateClient();
9307
11090
  if (opts.wait) {
9308
11091
  await completePrediction(client, predictionId, {
9309
- waitTimeoutMs: parsePositiveInt(opts.waitTimeout, "--wait-timeout"),
11092
+ waitTimeoutMs: parsePositiveInt2(opts.waitTimeout, "--wait-timeout"),
9310
11093
  logger,
9311
11094
  download: Boolean(opts.download),
9312
11095
  output: opts.output
@@ -9320,7 +11103,7 @@ function registerReplicateStatusCommand(parent) {
9320
11103
  }
9321
11104
  console.log(JSON.stringify(summarizePrediction(prediction2), null, 2));
9322
11105
  if (!isTerminalStatus2(prediction2.status)) {
9323
- logger.info(`Still running. Resume with: ${resumeCommand2(prediction2.id)}`);
11106
+ logger.info(`Still running. Resume with: ${resumeCommand3(prediction2.id)}`);
9324
11107
  } else if (prediction2.status !== "succeeded") {
9325
11108
  throw new ReplicatePredictionError(prediction2);
9326
11109
  }
@@ -9346,7 +11129,7 @@ function buildVideoInput(prompt, opts) {
9346
11129
  return {
9347
11130
  ...extra,
9348
11131
  prompt,
9349
- ...opts.duration ? { duration: parsePositiveInt(opts.duration, "--duration", 60) } : {},
11132
+ ...opts.duration ? { duration: parsePositiveInt2(opts.duration, "--duration", 60) } : {},
9350
11133
  ...opts.aspectRatio ? { aspect_ratio: opts.aspectRatio } : {},
9351
11134
  ...opts.resolution ? { resolution: opts.resolution } : {},
9352
11135
  ...opts.seed !== void 0 ? { seed: parseSeed(opts.seed) } : {}
@@ -9375,7 +11158,7 @@ function registerReplicateCommands(program2) {
9375
11158
  }
9376
11159
 
9377
11160
  // src/commands/check.ts
9378
- import fs45 from "fs";
11161
+ import fs46 from "fs";
9379
11162
  import os4 from "os";
9380
11163
 
9381
11164
  // src/commands/check-helpers/binary-check.ts
@@ -9434,6 +11217,7 @@ To configure multix, set at least one provider API key:
9434
11217
  fal.ai : https://fal.ai/dashboard/keys
9435
11218
  Atlas Cloud : https://www.atlascloud.ai/console/api-keys
9436
11219
  Replicate : https://replicate.com/account/api-tokens
11220
+ HeyGen : https://app.heygen.com/developers/api
9437
11221
 
9438
11222
  Option A \u2014 user global config (recommended):
9439
11223
  echo 'GEMINI_API_KEY=your-key' >> ~/.multix/.env
@@ -9515,6 +11299,12 @@ var PROVIDERS = [
9515
11299
  envFallback: "REPLICATE_API_KEY",
9516
11300
  optional: true,
9517
11301
  link: "https://replicate.com/account/api-tokens"
11302
+ },
11303
+ {
11304
+ name: "HeyGen",
11305
+ envPrimary: "HEYGEN_API_KEY",
11306
+ optional: true,
11307
+ link: "https://app.heygen.com/developers/api"
9518
11308
  }
9519
11309
  ];
9520
11310
  function resolveProviderKey(p) {
@@ -9566,7 +11356,7 @@ function registerCheckCommand(program2) {
9566
11356
  }
9567
11357
  if (!legacyClaudeEnvEnabled()) {
9568
11358
  const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os4.homedir();
9569
- const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs45.existsSync(f));
11359
+ const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs46.existsSync(f));
9570
11360
  if (legacy.length > 0) {
9571
11361
  logger.info(
9572
11362
  `Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
@@ -9671,8 +11461,8 @@ function registerCheckCommand(program2) {
9671
11461
 
9672
11462
  // src/commands/doc/convert.ts
9673
11463
  init_errors();
9674
- import fs46 from "fs";
9675
- import path50 from "path";
11464
+ import fs47 from "fs";
11465
+ import path53 from "path";
9676
11466
 
9677
11467
  // src/commands/doc/default-prompt.ts
9678
11468
  var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
@@ -9693,18 +11483,18 @@ async function convertDocuments(opts) {
9693
11483
  const model = opts.model ?? DOC_MODEL_DEFAULT;
9694
11484
  const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
9695
11485
  for (const f of inputs) {
9696
- if (!fs46.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
11486
+ if (!fs47.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
9697
11487
  }
9698
11488
  let outputPath;
9699
11489
  if (output) {
9700
- outputPath = path50.resolve(output);
11490
+ outputPath = path53.resolve(output);
9701
11491
  } else if (autoName && inputs.length === 1 && inputs[0]) {
9702
- const base = path50.basename(inputs[0], path50.extname(inputs[0]));
9703
- outputPath = path50.join(getOutputDir(), `${base}-extraction.md`);
11492
+ const base = path53.basename(inputs[0], path53.extname(inputs[0]));
11493
+ outputPath = path53.join(getOutputDir(), `${base}-extraction.md`);
9704
11494
  } else {
9705
- outputPath = path50.join(getOutputDir(), "document-extraction.md");
11495
+ outputPath = path53.join(getOutputDir(), "document-extraction.md");
9706
11496
  }
9707
- fs46.mkdirSync(path50.dirname(outputPath), { recursive: true });
11497
+ fs47.mkdirSync(path53.dirname(outputPath), { recursive: true });
9708
11498
  const results = [];
9709
11499
  for (let i = 0; i < inputs.length; i++) {
9710
11500
  const filePath = inputs[i];
@@ -9764,7 +11554,7 @@ async function convertDocuments(opts) {
9764
11554
  `);
9765
11555
  for (const r of results) {
9766
11556
  lines.push(`
9767
- ## ${path50.basename(r.file)}
11557
+ ## ${path53.basename(r.file)}
9768
11558
  `);
9769
11559
  if (r.status === "success" && r.markdown) {
9770
11560
  lines.push(`${r.markdown}
@@ -9777,7 +11567,7 @@ async function convertDocuments(opts) {
9777
11567
  }
9778
11568
  lines.push("\n---\n");
9779
11569
  }
9780
- fs46.writeFileSync(outputPath, lines.join("\n"), "utf8");
11570
+ fs47.writeFileSync(outputPath, lines.join("\n"), "utf8");
9781
11571
  const succeeded = results.filter((r) => r.status === "success").length;
9782
11572
  console.log(`
9783
11573
  ${"=".repeat(50)}`);
@@ -9825,12 +11615,12 @@ function registerDocCommands(program2) {
9825
11615
 
9826
11616
  // src/commands/media/index.ts
9827
11617
  init_errors();
9828
- import fs49 from "fs";
9829
- import path53 from "path";
11618
+ import fs50 from "fs";
11619
+ import path56 from "path";
9830
11620
 
9831
11621
  // src/commands/media/batch.ts
9832
- import fs47 from "fs";
9833
- import path51 from "path";
11622
+ import fs48 from "fs";
11623
+ import path54 from "path";
9834
11624
 
9835
11625
  // src/commands/media/detect-type.ts
9836
11626
  var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
@@ -10026,10 +11816,10 @@ async function batchOptimize(opts) {
10026
11816
  verbose,
10027
11817
  logger
10028
11818
  } = opts;
10029
- fs47.mkdirSync(outputDir, { recursive: true });
10030
- const entries = fs47.readdirSync(inputDir);
11819
+ fs48.mkdirSync(outputDir, { recursive: true });
11820
+ const entries = fs48.readdirSync(inputDir);
10031
11821
  const mediaFiles = entries.filter((f) => {
10032
- const ext = path51.extname(f).toLowerCase();
11822
+ const ext = path54.extname(f).toLowerCase();
10033
11823
  return ALL_EXTS.has(ext);
10034
11824
  });
10035
11825
  if (mediaFiles.length === 0) {
@@ -10040,9 +11830,9 @@ async function batchOptimize(opts) {
10040
11830
  let succeeded = 0;
10041
11831
  const errors = [];
10042
11832
  for (const file of mediaFiles) {
10043
- const inputPath = path51.join(inputDir, file);
10044
- const outputPath = path51.join(outputDir, file);
10045
- const ext = path51.extname(file).toLowerCase();
11833
+ const inputPath = path54.join(inputDir, file);
11834
+ const outputPath = path54.join(outputDir, file);
11835
+ const ext = path54.extname(file).toLowerCase();
10046
11836
  const kind = extToKind(ext);
10047
11837
  logger?.debug(`Processing: ${file}`);
10048
11838
  try {
@@ -10079,8 +11869,8 @@ async function batchOptimize(opts) {
10079
11869
 
10080
11870
  // src/commands/media/split-video.ts
10081
11871
  init_errors();
10082
- import fs48 from "fs";
10083
- import path52 from "path";
11872
+ import fs49 from "fs";
11873
+ import path55 from "path";
10084
11874
  async function splitVideo(opts) {
10085
11875
  await assertFfmpeg();
10086
11876
  const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
@@ -10091,10 +11881,10 @@ async function splitVideo(opts) {
10091
11881
  logger?.info("Video is short enough \u2014 no splitting needed");
10092
11882
  return [input];
10093
11883
  }
10094
- fs48.mkdirSync(outputDir, { recursive: true });
10095
- const ext = path52.extname(input) || ".mp4";
10096
- const baseName = path52.basename(input, ext);
10097
- const outputPattern = path52.join(outputDir, `${baseName}_chunk_%03d${ext}`);
11884
+ fs49.mkdirSync(outputDir, { recursive: true });
11885
+ const ext = path55.extname(input) || ".mp4";
11886
+ const baseName = path55.basename(input, ext);
11887
+ const outputPattern = path55.join(outputDir, `${baseName}_chunk_%03d${ext}`);
10098
11888
  logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
10099
11889
  const args = [
10100
11890
  "-i",
@@ -10113,8 +11903,8 @@ async function splitVideo(opts) {
10113
11903
  await runFfmpeg(args, { verbose, logger });
10114
11904
  const files = [];
10115
11905
  for (let i = 0; i < numChunks + 1; i++) {
10116
- const fname = path52.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
10117
- if (fs48.existsSync(fname)) files.push(fname);
11906
+ const fname = path55.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
11907
+ if (fs49.existsSync(fname)) files.push(fname);
10118
11908
  }
10119
11909
  logger?.success(`Created ${files.length} chunks in ${outputDir}`);
10120
11910
  return files;
@@ -10128,9 +11918,9 @@ function registerMediaCommands(program2) {
10128
11918
  media.command("optimize").description("Optimize a single media file (video, audio, or image)").requiredOption("--input <file>", "Input media file").requiredOption("--output <file>", "Output file path").option("--target-size <MB>", "Target file size in MB").option("--quality <n>", "Quality: video CRF (0-51) or image (1-100)", "85").option("--max-width <px>", "Max image/video width in pixels", "1920").option("--bitrate <rate>", "Audio bitrate (e.g. 64k)", "64k").option("--resolution <WxH>", "Force video resolution (e.g. 1920x1080)").option("-v, --verbose", "Verbose logging").action(
10129
11919
  async (opts) => {
10130
11920
  const logger = createLogger({ verbose: opts.verbose ?? false });
10131
- if (!fs49.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
10132
- fs49.mkdirSync(path53.dirname(path53.resolve(opts.output)), { recursive: true });
10133
- const ext = path53.extname(opts.input).toLowerCase();
11921
+ if (!fs50.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
11922
+ fs50.mkdirSync(path56.dirname(path56.resolve(opts.output)), { recursive: true });
11923
+ const ext = path56.extname(opts.input).toLowerCase();
10134
11924
  const kind = extToKind(ext);
10135
11925
  if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
10136
11926
  const quality = Number.parseInt(opts.quality, 10);
@@ -10170,7 +11960,7 @@ function registerMediaCommands(program2) {
10170
11960
  media.command("split").description("Split a long video into fixed-duration chunks").requiredOption("--input <file>", "Input video file").option("--output-dir <dir>", "Output directory for chunks", "./chunks").option("--chunk-duration <sec>", "Chunk duration in seconds", "3600").option("-v, --verbose", "Verbose logging").action(
10171
11961
  async (opts) => {
10172
11962
  const logger = createLogger({ verbose: opts.verbose ?? false });
10173
- if (!fs49.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
11963
+ if (!fs50.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
10174
11964
  const chunks = await splitVideo({
10175
11965
  input: opts.input,
10176
11966
  outputDir: opts.outputDir,
@@ -10186,7 +11976,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
10186
11976
  media.command("batch").description("Batch optimize all media files in a directory").requiredOption("--input-dir <dir>", "Input directory").requiredOption("--output-dir <dir>", "Output directory").option("--quality <n>", "Quality setting", "85").option("--max-width <px>", "Max image/video width", "1920").option("--bitrate <rate>", "Audio bitrate", "64k").option("-v, --verbose", "Verbose logging").action(
10187
11977
  async (opts) => {
10188
11978
  const logger = createLogger({ verbose: opts.verbose ?? false });
10189
- if (!fs49.existsSync(opts.inputDir))
11979
+ if (!fs50.existsSync(opts.inputDir))
10190
11980
  throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
10191
11981
  const result = await batchOptimize({
10192
11982
  inputDir: opts.inputDir,
@@ -10368,6 +12158,7 @@ function registerCommands(program2) {
10368
12158
  registerFalCommands(program2);
10369
12159
  registerAtlasCloudCommands(program2);
10370
12160
  registerReplicateCommands(program2);
12161
+ registerHeygenCommands(program2);
10371
12162
  registerMediaCommands(program2);
10372
12163
  registerDocCommands(program2);
10373
12164
  }
@@ -10381,7 +12172,7 @@ loadEnv();
10381
12172
  var require3 = createRequire2(import.meta.url);
10382
12173
  var pkg = require3("../package.json");
10383
12174
  var program = new Command().name("multix").description(
10384
- "AI multimodal CLI \u2014 image/video/audio/3D generation, editing, transcription, and media processing via OpenAI, Gemini, MiniMax, OpenRouter, Leonardo, BytePlus, ElevenLabs, fal.ai, Atlas Cloud, Replicate, ffmpeg, and ImageMagick"
12175
+ "AI multimodal CLI \u2014 image/video/audio/3D generation, editing, transcription, and media processing via OpenAI, Gemini, MiniMax, OpenRouter, Leonardo, BytePlus, ElevenLabs, fal.ai, Atlas Cloud, Replicate, HeyGen, ffmpeg, and ImageMagick"
10385
12176
  ).version(pkg.version).enablePositionalOptions();
10386
12177
  registerCommands(program);
10387
12178
  program.parseAsync(process.argv).catch((err2) => {