@pixeldojo/mcp 0.8.0 → 0.9.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.cjs +357 -56
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +357 -56
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +357 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +357 -56
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -186,7 +186,7 @@ var init_upload_helpers = __esm({
|
|
|
186
186
|
function getFieldConstraints(apiId, client) {
|
|
187
187
|
const cached = schemaCache.get(apiId);
|
|
188
188
|
if (cached) return cached;
|
|
189
|
-
const pending = client.getModelSchema(apiId).then(
|
|
189
|
+
const pending = Promise.resolve().then(() => client.getModelSchema(apiId)).then(
|
|
190
190
|
(res) => res.schema?.properties ?? {}
|
|
191
191
|
).catch(() => {
|
|
192
192
|
schemaCache.delete(apiId);
|
|
@@ -254,10 +254,34 @@ async function snapDuration(apiId, requested, client, opts = {}) {
|
|
|
254
254
|
}
|
|
255
255
|
return requested;
|
|
256
256
|
}
|
|
257
|
-
|
|
257
|
+
async function resolveImageRefs(apiId, urls, client) {
|
|
258
|
+
if (urls.length === 0) return null;
|
|
259
|
+
const props = await getFieldConstraints(apiId, client);
|
|
260
|
+
if (props === null) return null;
|
|
261
|
+
const key = REF_KEYS.find((k) => props[k] !== void 0);
|
|
262
|
+
if (!key) return null;
|
|
263
|
+
const field = props[key];
|
|
264
|
+
if (field.type === "array") {
|
|
265
|
+
const cap = typeof field.maxItems === "number" ? field.maxItems : urls.length;
|
|
266
|
+
const kept = urls.slice(0, Math.max(1, cap));
|
|
267
|
+
return { key, value: kept, dropped: urls.length - kept.length };
|
|
268
|
+
}
|
|
269
|
+
return { key, value: urls[0], dropped: urls.length - 1 };
|
|
270
|
+
}
|
|
271
|
+
var schemaCache, REF_KEYS;
|
|
258
272
|
var init_schema_snap = __esm({
|
|
259
273
|
"src/skills/schema-snap.ts"() {
|
|
260
274
|
schemaCache = /* @__PURE__ */ new Map();
|
|
275
|
+
REF_KEYS = [
|
|
276
|
+
"image_urls",
|
|
277
|
+
"images",
|
|
278
|
+
"image_input",
|
|
279
|
+
"reference_images",
|
|
280
|
+
"reference_urls",
|
|
281
|
+
"image_url",
|
|
282
|
+
"input_image",
|
|
283
|
+
"image"
|
|
284
|
+
];
|
|
261
285
|
}
|
|
262
286
|
});
|
|
263
287
|
function buildPayload(apiId, input) {
|
|
@@ -409,12 +433,21 @@ function buildPayload2(apiId, input) {
|
|
|
409
433
|
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
410
434
|
};
|
|
411
435
|
case "flux-edit":
|
|
412
|
-
|
|
436
|
+
return {
|
|
437
|
+
prompt: input.prompt,
|
|
438
|
+
input_image_1: urls[0],
|
|
439
|
+
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
440
|
+
};
|
|
413
441
|
case "gpt-image-2-edit":
|
|
442
|
+
return {
|
|
443
|
+
prompt: input.prompt,
|
|
444
|
+
image_urls: urls.slice(0, 8),
|
|
445
|
+
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
446
|
+
};
|
|
447
|
+
case "xai-image-edit":
|
|
414
448
|
return {
|
|
415
449
|
prompt: input.prompt,
|
|
416
450
|
image_url: urls[0],
|
|
417
|
-
...urls.length > 1 ? { image_urls: urls } : {},
|
|
418
451
|
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
419
452
|
};
|
|
420
453
|
case "google-nano-banana":
|
|
@@ -430,6 +463,19 @@ function buildPayload2(apiId, input) {
|
|
|
430
463
|
};
|
|
431
464
|
}
|
|
432
465
|
}
|
|
466
|
+
async function buildBody(apiId, input, ctx) {
|
|
467
|
+
const urls = input.image_urls && input.image_urls.length > 0 ? input.image_urls : input.image_url ? [input.image_url] : [];
|
|
468
|
+
const refs = await resolveImageRefs(apiId, urls, ctx.client);
|
|
469
|
+
if (!refs) return { body: buildPayload2(apiId, input), dropped: 0 };
|
|
470
|
+
const fallback = buildPayload2(apiId, input);
|
|
471
|
+
const body = { ...fallback };
|
|
472
|
+
for (const key of ["image_url", "image_urls", "images", "image_input", "input_image_1"]) {
|
|
473
|
+
if (key !== refs.key) delete body[key];
|
|
474
|
+
}
|
|
475
|
+
body.prompt = input.prompt;
|
|
476
|
+
body[refs.key] = refs.value;
|
|
477
|
+
return { body, dropped: refs.dropped };
|
|
478
|
+
}
|
|
433
479
|
async function run2(input, ctx) {
|
|
434
480
|
let resolved = input;
|
|
435
481
|
try {
|
|
@@ -460,7 +506,8 @@ async function run2(input, ctx) {
|
|
|
460
506
|
...resolved,
|
|
461
507
|
aspect_ratio: await snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client)
|
|
462
508
|
};
|
|
463
|
-
const body =
|
|
509
|
+
const { body, dropped } = await buildBody(apiId, resolved, ctx);
|
|
510
|
+
const droppedNote = dropped > 0 ? ` (${dropped} reference${dropped === 1 ? "" : "s"} dropped \u2014 ${apiId} accepts fewer)` : "";
|
|
464
511
|
const submitted = await ctx.client.run(apiId, body);
|
|
465
512
|
const result = await pollJobUntilDoneOrHandoff(
|
|
466
513
|
submitted.jobId,
|
|
@@ -473,7 +520,7 @@ async function run2(input, ctx) {
|
|
|
473
520
|
);
|
|
474
521
|
return pollResultToSkillResult(
|
|
475
522
|
result,
|
|
476
|
-
(urls) => urls.length === 0 ? `Edit job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Edit complete. Asset: ${urls[0]}` : `Edit produced ${urls.length} assets. URLs: ${urls.join(", ")}`
|
|
523
|
+
(urls) => urls.length === 0 ? `Edit job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Edit complete${droppedNote}. Asset: ${urls[0]}` : `Edit produced ${urls.length} assets${droppedNote}. URLs: ${urls.join(", ")}`
|
|
477
524
|
);
|
|
478
525
|
}
|
|
479
526
|
var DEFAULT_MODEL2, ALLOWED_MODELS, InputSchema2, InputJsonSchema2, editSkill;
|
|
@@ -933,7 +980,256 @@ var init_from_url = __esm({
|
|
|
933
980
|
};
|
|
934
981
|
}
|
|
935
982
|
});
|
|
983
|
+
async function filmApi(ctx, method, path, body) {
|
|
984
|
+
const res = await fetch(`${ctx.baseUrl}${path}`, {
|
|
985
|
+
method,
|
|
986
|
+
headers: {
|
|
987
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
988
|
+
"Content-Type": "application/json",
|
|
989
|
+
"x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
|
|
990
|
+
"x-pixeldojo-skill": "film"
|
|
991
|
+
},
|
|
992
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
993
|
+
});
|
|
994
|
+
const data = await res.json().catch(() => ({}));
|
|
995
|
+
return { status: res.status, data };
|
|
996
|
+
}
|
|
997
|
+
function progressLine(f) {
|
|
998
|
+
const all = shots(f);
|
|
999
|
+
const frames = all.filter((sh) => ["frame-ready", "approved", "clip-generating", "clip-ready"].includes(sh.status) || sh.frameUrl).length;
|
|
1000
|
+
const clips = all.filter((sh) => sh.status === "clip-ready").length;
|
|
1001
|
+
const rendering = all.filter((sh) => sh.status === "clip-generating").length;
|
|
1002
|
+
return `frames ${frames}/${all.length}, clips ${clips}/${all.length}${rendering > 0 ? ` (${rendering} rendering)` : ""}`;
|
|
1003
|
+
}
|
|
1004
|
+
async function plan(input, ctx) {
|
|
1005
|
+
if (!input.idea?.trim() && !input.script?.trim()) {
|
|
1006
|
+
return { kind: "text", text: "Provide an `idea` (we write the script, free) or a full `script` to plan a film." };
|
|
1007
|
+
}
|
|
1008
|
+
const created = await filmApi(ctx, "POST", "/api/films", {
|
|
1009
|
+
aspect: input.aspect,
|
|
1010
|
+
look: input.look,
|
|
1011
|
+
lengthTarget: input.length,
|
|
1012
|
+
assetIds: []
|
|
1013
|
+
});
|
|
1014
|
+
if (!created.data.film) return { kind: "text", text: `Could not create the film: ${created.data.error ?? created.status}` };
|
|
1015
|
+
const id = created.data.film.id;
|
|
1016
|
+
let script = input.script?.trim() ?? "";
|
|
1017
|
+
if (!script) {
|
|
1018
|
+
const wrote = await filmApi(ctx, "POST", `/api/films/${id}/script`, { idea: input.idea.trim() });
|
|
1019
|
+
script = wrote.data.film?.script ?? "";
|
|
1020
|
+
if (!script) return { kind: "text", text: `Script writing failed: ${wrote.data.error ?? wrote.status}` };
|
|
1021
|
+
}
|
|
1022
|
+
const planned = await filmApi(ctx, "POST", `/api/films/${id}/plan`, { script });
|
|
1023
|
+
const film = planned.data.film;
|
|
1024
|
+
if (!film) return { kind: "text", text: `Shot planning failed: ${planned.data.error ?? planned.status}` };
|
|
1025
|
+
const total = shots(film).length;
|
|
1026
|
+
const sceneLines = film.scenes.map((s, i) => ` Scene ${i + 1} "${s.title}"${s.location ? ` [${s.location}]` : ""}: ${s.shots.length} shots`);
|
|
1027
|
+
return {
|
|
1028
|
+
kind: "text",
|
|
1029
|
+
text: [
|
|
1030
|
+
`Planned "${film.title}" (film_id: ${film.id}) \u2014 ${film.scenes.length} scenes, ${total} shots, ${input.look}, ${input.aspect}.`,
|
|
1031
|
+
...sceneLines,
|
|
1032
|
+
"",
|
|
1033
|
+
`Nothing has been charged yet. Producing renders every shot (roughly 20-25 credits per shot at default engines, charged as each renders) plus a few credits for the soundtrack.`,
|
|
1034
|
+
`Review or edit at ${ctx.baseUrl}/film-studio/${film.id} \u2014 or call pixeldojo:film with {"film_id": "${film.id}", "action": "produce"} to start production.`
|
|
1035
|
+
].join("\n")
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
async function produce(filmId, ctx) {
|
|
1039
|
+
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1040
|
+
if (!data.film) return { kind: "text", text: `Film not found: ${data.error ?? filmId}` };
|
|
1041
|
+
const pending = shots(data.film).filter((sh) => sh.status === "idle" || sh.status === "failed" && !sh.frameUrl);
|
|
1042
|
+
let fired = 0;
|
|
1043
|
+
for (const sh of pending.slice(0, 6)) {
|
|
1044
|
+
const r = await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "frame" });
|
|
1045
|
+
if (r.status === 200) fired += 1;
|
|
1046
|
+
}
|
|
1047
|
+
return {
|
|
1048
|
+
kind: "text",
|
|
1049
|
+
text: `Production started on "${data.film.title}": ${fired} frame(s) dispatched (each is a normal frame charge; the server's Frame check reviews every one). Poll pixeldojo:film with {"film_id": "${filmId}", "action": "status"} every ~30 seconds \u2014 each poll advances the pipeline.`
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function buildScorePlanBody(film) {
|
|
1053
|
+
const scenes = film.scenes.filter((s) => s.shots.length > 0);
|
|
1054
|
+
const sections = scenes.flatMap((scene, i) => {
|
|
1055
|
+
const runtime = Math.max(3e3, scene.shots.reduce((n, sh) => n + (sh.durationSec || 0), 0) * 1e3);
|
|
1056
|
+
const styles = i === 0 ? ARC.opening : i === scenes.length - 1 ? ARC.finale : ARC.middle;
|
|
1057
|
+
const parts = Math.max(1, Math.ceil(runtime / 12e4));
|
|
1058
|
+
const partMs = Math.round(runtime / parts);
|
|
1059
|
+
return Array.from({ length: parts }, (_, part) => ({
|
|
1060
|
+
section_name: parts > 1 ? `${scene.title} (part ${part + 1})` : scene.title,
|
|
1061
|
+
positive_local_styles: styles,
|
|
1062
|
+
negative_local_styles: [],
|
|
1063
|
+
duration_ms: part === parts - 1 ? runtime - partMs * (parts - 1) : partMs,
|
|
1064
|
+
lines: []
|
|
1065
|
+
}));
|
|
1066
|
+
});
|
|
1067
|
+
return {
|
|
1068
|
+
composition_plan: {
|
|
1069
|
+
positive_global_styles: (LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]).split(", "),
|
|
1070
|
+
negative_global_styles: ["vocals", "singing", "lyrics"],
|
|
1071
|
+
sections
|
|
1072
|
+
},
|
|
1073
|
+
respect_sections_durations: true,
|
|
1074
|
+
output_format: "mp3_standard"
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function buildFlatMusicBody(film, all) {
|
|
1078
|
+
const runtimeSec = all.reduce((n, sh) => n + (sh.durationSec || 0), 0);
|
|
1079
|
+
const ms = Math.min(6e5, Math.max(5e3, Math.ceil(runtimeSec * 1e3 / 5e3) * 5e3));
|
|
1080
|
+
return {
|
|
1081
|
+
prompt: `${LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]}. Instrumental only, no vocals. One consistent theme, loopable as a background bed under dialogue.`,
|
|
1082
|
+
music_length_ms: ms,
|
|
1083
|
+
force_instrumental: true,
|
|
1084
|
+
output_format: "mp3_standard"
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
async function advance(filmId, soundtrack, musicJobId, ctx) {
|
|
1088
|
+
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}?refresh=1`);
|
|
1089
|
+
const film = data.film;
|
|
1090
|
+
if (!film) return { kind: "text", text: `Film not found: ${data.error ?? filmId}` };
|
|
1091
|
+
const all = shots(film);
|
|
1092
|
+
if (film.status === "exported" && film.exportUrl) {
|
|
1093
|
+
return {
|
|
1094
|
+
kind: "text",
|
|
1095
|
+
text: `Film finished: "${film.title}"
|
|
1096
|
+
Watch/download: ${film.exportUrl}
|
|
1097
|
+
Workspace: ${ctx.baseUrl}/film-studio/${film.id}
|
|
1098
|
+
It is saved in the Library folder "Film: ${film.title}".`
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
if (film.exportError) {
|
|
1102
|
+
return { kind: "text", text: `Export failed: ${film.exportError} Poll again with action "status" to retry.` };
|
|
1103
|
+
}
|
|
1104
|
+
const idleFrames = all.filter((sh) => sh.status === "idle");
|
|
1105
|
+
for (const sh of idleFrames.slice(0, 6)) {
|
|
1106
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "frame" });
|
|
1107
|
+
}
|
|
1108
|
+
const framesDone = all.every((sh) => ["frame-ready", "clip-generating", "clip-ready"].includes(sh.status));
|
|
1109
|
+
if (framesDone && all.some((sh) => sh.status === "frame-ready" && !sh.approved)) {
|
|
1110
|
+
await filmApi(ctx, "PATCH", `/api/films/${filmId}`, {
|
|
1111
|
+
scenes: film.scenes.map((s) => ({
|
|
1112
|
+
id: s.id,
|
|
1113
|
+
title: s.title,
|
|
1114
|
+
location: s.location,
|
|
1115
|
+
shots: s.shots.map((sh) => ({
|
|
1116
|
+
id: sh.id,
|
|
1117
|
+
prompt: sh.prompt,
|
|
1118
|
+
characterIds: sh.characterIds ?? [],
|
|
1119
|
+
elementIds: sh.elementIds ?? [],
|
|
1120
|
+
engine: sh.engine,
|
|
1121
|
+
cameraPreset: sh.cameraPreset,
|
|
1122
|
+
approved: true,
|
|
1123
|
+
durationSec: sh.durationSec
|
|
1124
|
+
}))
|
|
1125
|
+
}))
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
const inFlight = count(film, (sh) => sh.status === "clip-generating");
|
|
1129
|
+
const clipQueue = all.filter((sh) => (sh.status === "frame-ready" || sh.status === "failed" && Boolean(sh.frameUrl)) && framesDone);
|
|
1130
|
+
for (const sh of clipQueue.slice(0, Math.max(0, 4 - inFlight))) {
|
|
1131
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "clip" });
|
|
1132
|
+
}
|
|
1133
|
+
const clipsDone = all.length > 0 && all.every((sh) => sh.status === "clip-ready");
|
|
1134
|
+
if (clipsDone) {
|
|
1135
|
+
const hasMusic = film.audioStems.some((st) => st.kind === "music");
|
|
1136
|
+
if (soundtrack !== "none" && !hasMusic) {
|
|
1137
|
+
if (musicJobId) {
|
|
1138
|
+
const job = await ctx.client.getJob(musicJobId);
|
|
1139
|
+
const out = job.output;
|
|
1140
|
+
const outUrl = out?.audio ?? out?.video ?? out?.images?.[0];
|
|
1141
|
+
if (job.status === "completed" && typeof outUrl === "string") {
|
|
1142
|
+
await filmApi(ctx, "PATCH", `/api/films/${filmId}`, { audioStems: [{ kind: "music", url: outUrl }] });
|
|
1143
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/export`, {});
|
|
1144
|
+
return { kind: "text", text: `Score attached \u2014 export started. Poll again in ~60s with {"film_id": "${filmId}", "action": "status"} for the finished film URL.` };
|
|
1145
|
+
}
|
|
1146
|
+
if (job.status === "failed") {
|
|
1147
|
+
return { kind: "text", text: `The score failed (${job.error ?? "unknown"}) and was refunded. Poll again to retry, or pass soundtrack "none" to export without music.` };
|
|
1148
|
+
}
|
|
1149
|
+
return { kind: "text", text: `Score still composing. Poll again in ~20s with {"film_id": "${filmId}", "action": "status", "music_job_id": "${musicJobId}"}.` };
|
|
1150
|
+
}
|
|
1151
|
+
const body = soundtrack === "scene-synced" ? buildScorePlanBody(film) : buildFlatMusicBody(film, all);
|
|
1152
|
+
const dispatch = await ctx.client.run("text-to-music", body);
|
|
1153
|
+
return { kind: "text", text: `All ${all.length} clips ready \u2014 the ${soundtrack} score is composing (charged like any music generation). Poll again in ~20s with {"film_id": "${filmId}", "action": "status", "music_job_id": "${dispatch.jobId}"}.` };
|
|
1154
|
+
}
|
|
1155
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/export`, {});
|
|
1156
|
+
return { kind: "text", text: `All clips ready \u2014 export started (${progressLine(film)}). Poll again in ~60s with action "status" for the finished film URL.` };
|
|
1157
|
+
}
|
|
1158
|
+
const failed = all.filter((sh) => sh.status === "failed");
|
|
1159
|
+
return {
|
|
1160
|
+
kind: "text",
|
|
1161
|
+
text: [
|
|
1162
|
+
`In progress: ${progressLine(film)}.`,
|
|
1163
|
+
failed.length > 0 ? `${failed.length} shot(s) failed and were re-queued (${failed[0]?.errorMessage?.slice(0, 100) ?? ""}).` : "",
|
|
1164
|
+
`Poll again in ~30s with {"film_id": "${filmId}", "action": "status"}.`
|
|
1165
|
+
].filter(Boolean).join("\n")
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
936
1168
|
async function run8(input, ctx) {
|
|
1169
|
+
const action = input.action === "plan" && input.film_id ? "status" : input.action;
|
|
1170
|
+
if (action === "plan") return plan(input, ctx);
|
|
1171
|
+
if (!input.film_id) return { kind: "text", text: "produce/status need the film_id returned by the plan step." };
|
|
1172
|
+
if (action === "produce") return produce(input.film_id, ctx);
|
|
1173
|
+
return advance(input.film_id, input.soundtrack, input.music_job_id, ctx);
|
|
1174
|
+
}
|
|
1175
|
+
var LOOKS, InputSchema8, InputJsonSchema8, shots, count, LOOK_MUSIC, ARC, filmSkill;
|
|
1176
|
+
var init_film = __esm({
|
|
1177
|
+
"src/skills/film.ts"() {
|
|
1178
|
+
LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western"];
|
|
1179
|
+
InputSchema8 = z.object({
|
|
1180
|
+
idea: z.string().max(2e3).optional().describe("One-to-three sentence film idea. The studio writes the screenplay (free). Provide idea OR script for planning."),
|
|
1181
|
+
script: z.string().max(2e4).optional().describe("A full screenplay to plan instead of an idea (sluglines, action lines, dialogue)."),
|
|
1182
|
+
look: z.enum(LOOKS).default("live-action").describe("Visual style carried through frames, clips, and soundtrack."),
|
|
1183
|
+
aspect: z.enum(["16:9", "9:16"]).default("16:9"),
|
|
1184
|
+
length: z.enum(["30s", "60s", "2-3min"]).default("60s").describe("Target length; drives the shot count (~6/12/24 shots)."),
|
|
1185
|
+
film_id: z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status."),
|
|
1186
|
+
action: z.enum(["plan", "produce", "status"]).default("plan").describe("'plan' creates + storyboards (free). 'produce' starts charged generation. 'status' advances production and reports progress; poll it every ~30s until it returns the finished film."),
|
|
1187
|
+
soundtrack: z.enum(["scene-synced", "flat", "none"]).default("scene-synced").describe("scene-synced composes one musical section per scene, pinned to the scene lengths (default). flat is a single cohesive bed. none skips music."),
|
|
1188
|
+
music_job_id: z.string().max(80).optional().describe("Internal: pass back the music job id from a previous status reply so the composing score is attached instead of re-charged.")
|
|
1189
|
+
});
|
|
1190
|
+
InputJsonSchema8 = {
|
|
1191
|
+
type: "object",
|
|
1192
|
+
properties: {
|
|
1193
|
+
idea: { type: "string", description: "One-to-three sentence film idea. The studio writes the screenplay (free)." },
|
|
1194
|
+
script: { type: "string", description: "A full screenplay to plan instead of an idea." },
|
|
1195
|
+
look: { type: "string", enum: [...LOOKS], description: "Visual style. Default live-action." },
|
|
1196
|
+
aspect: { type: "string", enum: ["16:9", "9:16"], description: "Default 16:9." },
|
|
1197
|
+
length: { type: "string", enum: ["30s", "60s", "2-3min"], description: "Target length (~6/12/24 shots). Default 60s." },
|
|
1198
|
+
film_id: { type: "string", description: "Existing film id for produce/status." },
|
|
1199
|
+
action: { type: "string", enum: ["plan", "produce", "status"], description: "plan (free) | produce (charged) | status (poll every ~30s)." },
|
|
1200
|
+
soundtrack: { type: "string", enum: ["scene-synced", "flat", "none"], description: "Default scene-synced." },
|
|
1201
|
+
music_job_id: { type: "string", description: "Pass back from a previous status reply while the score is composing." }
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
shots = (f) => f.scenes.flatMap((s) => s.shots);
|
|
1205
|
+
count = (f, pred) => shots(f).filter(pred).length;
|
|
1206
|
+
LOOK_MUSIC = {
|
|
1207
|
+
"live-action": "modern cinematic underscore, warm strings and soft felt piano, subtle rhythmic pulse, slow emotional build",
|
|
1208
|
+
anime: "sweeping anime film score, lush strings and piano, taiko drum accents, soaring heartfelt theme",
|
|
1209
|
+
"3d-animation": "playful heartfelt family-animation score, pizzicato strings, celesta and warm woodwinds, gentle sense of wonder",
|
|
1210
|
+
noir: "smoky film-noir jazz underscore, brushed drums, upright bass, distant muted trumpet, slow simmering tension",
|
|
1211
|
+
horror: "sparse horror underscore, low cello drones, dissonant string swells, creeping heartbeat pulse, unsettling quiet",
|
|
1212
|
+
"sci-fi": "atmospheric science-fiction score, analog synth pads, deep sub-bass pulses, shimmering arpeggios, vast and cold",
|
|
1213
|
+
documentary: "understated documentary score, felt piano, soft string beds, organic percussion textures, reflective and true",
|
|
1214
|
+
western: "cinematic western score, lonely twangy guitar, dusty strings, distant horns, wide-open desert tension"
|
|
1215
|
+
};
|
|
1216
|
+
ARC = {
|
|
1217
|
+
opening: ["establishing the theme", "restrained", "quiet anticipation"],
|
|
1218
|
+
middle: ["building tension", "rising intensity", "forward motion"],
|
|
1219
|
+
finale: ["climactic", "full arrangement", "emotional peak resolving to stillness"]
|
|
1220
|
+
};
|
|
1221
|
+
filmSkill = {
|
|
1222
|
+
name: "pixeldojo:film",
|
|
1223
|
+
description: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Phased: plan is free and returns a cost estimate; produce starts charged generation; status advances production (poll every ~30s).",
|
|
1224
|
+
inputZod: InputSchema8,
|
|
1225
|
+
inputJsonSchema: InputJsonSchema8,
|
|
1226
|
+
allowedModels: [],
|
|
1227
|
+
defaultModel: "",
|
|
1228
|
+
run: run8
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
async function run9(input, ctx) {
|
|
937
1233
|
const productImages = [
|
|
938
1234
|
...input.product_image_urls ?? [],
|
|
939
1235
|
...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx.client)))
|
|
@@ -976,13 +1272,13 @@ async function run8(input, ctx) {
|
|
|
976
1272
|
(urls) => urls.length === 0 ? `Ad job ${submitted.jobId} completed but returned no assets.` : `Ad rendered (style: ${styleLabel}${submitted.ad?.product?.name ? `, product: ${submitted.ad.product.name}` : ""}). Video: ${urls[0]}`
|
|
977
1273
|
);
|
|
978
1274
|
}
|
|
979
|
-
var
|
|
1275
|
+
var InputSchema9, InputJsonSchema9, adSkill;
|
|
980
1276
|
var init_ad = __esm({
|
|
981
1277
|
"src/skills/ad.ts"() {
|
|
982
1278
|
init_types();
|
|
983
1279
|
init_poll();
|
|
984
1280
|
init_upload_helpers();
|
|
985
|
-
|
|
1281
|
+
InputSchema9 = z.object({
|
|
986
1282
|
style: z.string().max(80).optional().describe(
|
|
987
1283
|
"Ad style slug (default ugc-testimonial). Catalog: GET /api/v1/marketing/presets \u2014 e.g. ugc-testimonial, unboxing-reveal, unboxing-asmr, virtual-try-on, street-style, product-showcase, macro-details, crush-test, hyper-motion, before-after, day-in-the-life, the-gift, tv-spot."
|
|
988
1284
|
),
|
|
@@ -1008,7 +1304,7 @@ var init_ad = __esm({
|
|
|
1008
1304
|
path: ["product_url"]
|
|
1009
1305
|
}
|
|
1010
1306
|
);
|
|
1011
|
-
|
|
1307
|
+
InputJsonSchema9 = {
|
|
1012
1308
|
type: "object",
|
|
1013
1309
|
properties: {
|
|
1014
1310
|
style: {
|
|
@@ -1064,16 +1360,16 @@ var init_ad = __esm({
|
|
|
1064
1360
|
adSkill = {
|
|
1065
1361
|
name: "pixeldojo:ad",
|
|
1066
1362
|
description: "Marketing Studio: turn any product into a video ad. Pass a product URL (auto-extracted) or product images, pick an ad style (UGC testimonial, unboxing, try-on, showcase, TV spot, and more via GET /api/v1/marketing/presets), optionally add presenter reference images, a location, a hook, and the exact spoken script. Renders with voiceover; returns the video or a statusUrl handoff to poll via pixeldojo:status. Ideal for automated generate-and-post workflows.",
|
|
1067
|
-
inputZod:
|
|
1068
|
-
inputJsonSchema:
|
|
1363
|
+
inputZod: InputSchema9,
|
|
1364
|
+
inputJsonSchema: InputJsonSchema9,
|
|
1069
1365
|
// Composition skill — the server routes to the ad engine internally.
|
|
1070
1366
|
allowedModels: [],
|
|
1071
1367
|
defaultModel: "",
|
|
1072
|
-
run:
|
|
1368
|
+
run: run9
|
|
1073
1369
|
};
|
|
1074
1370
|
}
|
|
1075
1371
|
});
|
|
1076
|
-
async function
|
|
1372
|
+
async function run10(input, ctx) {
|
|
1077
1373
|
const client = ctx.client;
|
|
1078
1374
|
const result = await client.runCampaign(input);
|
|
1079
1375
|
if (result.kind === "campaign") {
|
|
@@ -1100,10 +1396,10 @@ async function run9(input, ctx) {
|
|
|
1100
1396
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
1101
1397
|
};
|
|
1102
1398
|
}
|
|
1103
|
-
var
|
|
1399
|
+
var InputSchema10, InputJsonSchema10, campaignSkill;
|
|
1104
1400
|
var init_campaign = __esm({
|
|
1105
1401
|
"src/skills/campaign.ts"() {
|
|
1106
|
-
|
|
1402
|
+
InputSchema10 = z.object({
|
|
1107
1403
|
productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
|
|
1108
1404
|
product: z.object({
|
|
1109
1405
|
name: z.string().min(1).max(200),
|
|
@@ -1119,7 +1415,7 @@ var init_campaign = __esm({
|
|
|
1119
1415
|
message: "productUrl or product is required",
|
|
1120
1416
|
path: ["productUrl"]
|
|
1121
1417
|
});
|
|
1122
|
-
|
|
1418
|
+
InputJsonSchema10 = {
|
|
1123
1419
|
type: "object",
|
|
1124
1420
|
properties: {
|
|
1125
1421
|
productUrl: {
|
|
@@ -1150,17 +1446,17 @@ var init_campaign = __esm({
|
|
|
1150
1446
|
campaignSkill = {
|
|
1151
1447
|
name: "pixeldojo:campaign",
|
|
1152
1448
|
description: "Generate a coherent product launch package (hero image + N lifestyle variants + optional vertical video) from one URL or product profile. Composes hero / lifestyle / video models server-side; returns assets in one call or a handoff with campaignId to poll via pixeldojo:campaign_status.",
|
|
1153
|
-
inputZod:
|
|
1154
|
-
inputJsonSchema:
|
|
1449
|
+
inputZod: InputSchema10,
|
|
1450
|
+
inputJsonSchema: InputJsonSchema10,
|
|
1155
1451
|
// Composition skill — internal sub-jobs route to per-tool models, no
|
|
1156
1452
|
// single allowed-models list applies here.
|
|
1157
1453
|
allowedModels: [],
|
|
1158
1454
|
defaultModel: "",
|
|
1159
|
-
run:
|
|
1455
|
+
run: run10
|
|
1160
1456
|
};
|
|
1161
1457
|
}
|
|
1162
1458
|
});
|
|
1163
|
-
async function
|
|
1459
|
+
async function run11(input, ctx) {
|
|
1164
1460
|
const client = ctx.client;
|
|
1165
1461
|
const result = await client.getCampaign(input.campaignId);
|
|
1166
1462
|
if (result.kind === "campaign") {
|
|
@@ -1190,13 +1486,13 @@ async function run10(input, ctx) {
|
|
|
1190
1486
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
1191
1487
|
};
|
|
1192
1488
|
}
|
|
1193
|
-
var
|
|
1489
|
+
var InputSchema11, InputJsonSchema11, campaignStatusSkill;
|
|
1194
1490
|
var init_campaign_status = __esm({
|
|
1195
1491
|
"src/skills/campaign-status.ts"() {
|
|
1196
|
-
|
|
1492
|
+
InputSchema11 = z.object({
|
|
1197
1493
|
campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
|
|
1198
1494
|
});
|
|
1199
|
-
|
|
1495
|
+
InputJsonSchema11 = {
|
|
1200
1496
|
type: "object",
|
|
1201
1497
|
properties: {
|
|
1202
1498
|
campaignId: {
|
|
@@ -1209,15 +1505,15 @@ var init_campaign_status = __esm({
|
|
|
1209
1505
|
campaignStatusSkill = {
|
|
1210
1506
|
name: "pixeldojo:campaign_status",
|
|
1211
1507
|
description: "Poll a campaign by campaignId returned from pixeldojo:campaign. Returns assets when every sub-job is done, or a handoff describing what is still in flight.",
|
|
1212
|
-
inputZod:
|
|
1213
|
-
inputJsonSchema:
|
|
1508
|
+
inputZod: InputSchema11,
|
|
1509
|
+
inputJsonSchema: InputJsonSchema11,
|
|
1214
1510
|
allowedModels: [],
|
|
1215
1511
|
defaultModel: "",
|
|
1216
|
-
run:
|
|
1512
|
+
run: run11
|
|
1217
1513
|
};
|
|
1218
1514
|
}
|
|
1219
1515
|
});
|
|
1220
|
-
async function
|
|
1516
|
+
async function run12(input, ctx) {
|
|
1221
1517
|
let url;
|
|
1222
1518
|
try {
|
|
1223
1519
|
url = await uploadLocalFile(input.path, ctx.client);
|
|
@@ -1233,16 +1529,16 @@ async function run11(input, ctx) {
|
|
|
1233
1529
|
(temp storage; auto-deletes in ~24h \u2014 pass this URL as image_url to :generate or :edit)`
|
|
1234
1530
|
};
|
|
1235
1531
|
}
|
|
1236
|
-
var
|
|
1532
|
+
var InputSchema12, InputJsonSchema12, uploadSkill;
|
|
1237
1533
|
var init_upload = __esm({
|
|
1238
1534
|
"src/skills/upload.ts"() {
|
|
1239
1535
|
init_upload_helpers();
|
|
1240
|
-
|
|
1536
|
+
InputSchema12 = z.object({
|
|
1241
1537
|
path: z.string().min(1).max(4096).describe(
|
|
1242
1538
|
"Absolute local file path to upload. JPEG, PNG, WebP, GIF (\u226450MB) or MP4, WebM, MOV (\u2264200MB). Returns a public URL on the temp CDN; storage auto-expires after 24 hours."
|
|
1243
1539
|
)
|
|
1244
1540
|
});
|
|
1245
|
-
|
|
1541
|
+
InputJsonSchema12 = {
|
|
1246
1542
|
type: "object",
|
|
1247
1543
|
properties: {
|
|
1248
1544
|
path: {
|
|
@@ -1255,15 +1551,15 @@ var init_upload = __esm({
|
|
|
1255
1551
|
uploadSkill = {
|
|
1256
1552
|
name: "pixeldojo:upload",
|
|
1257
1553
|
description: "Upload a local image or video file and get back a public URL you can pass to :generate or :edit as image_url. Use when the user has a reference image on disk (drag-dropped into Claude Code, screenshot, etc.) and you need a URL. Storage auto-expires after 24 hours.",
|
|
1258
|
-
inputZod:
|
|
1259
|
-
inputJsonSchema:
|
|
1554
|
+
inputZod: InputSchema12,
|
|
1555
|
+
inputJsonSchema: InputJsonSchema12,
|
|
1260
1556
|
allowedModels: [],
|
|
1261
1557
|
defaultModel: "",
|
|
1262
|
-
run:
|
|
1558
|
+
run: run12
|
|
1263
1559
|
};
|
|
1264
1560
|
}
|
|
1265
1561
|
});
|
|
1266
|
-
async function
|
|
1562
|
+
async function run13(input, ctx) {
|
|
1267
1563
|
const requested = input.model ?? DEFAULT_MODEL6;
|
|
1268
1564
|
if (!ALLOWED4.includes(requested)) {
|
|
1269
1565
|
return {
|
|
@@ -1312,7 +1608,7 @@ async function run12(input, ctx) {
|
|
|
1312
1608
|
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
|
|
1313
1609
|
);
|
|
1314
1610
|
}
|
|
1315
|
-
var ALLOWED4, DEFAULT_MODEL6,
|
|
1611
|
+
var ALLOWED4, DEFAULT_MODEL6, InputSchema13, InputJsonSchema13, audioSkill;
|
|
1316
1612
|
var init_audio = __esm({
|
|
1317
1613
|
"src/skills/audio.ts"() {
|
|
1318
1614
|
init_poll();
|
|
@@ -1320,7 +1616,7 @@ var init_audio = __esm({
|
|
|
1320
1616
|
init_types();
|
|
1321
1617
|
ALLOWED4 = ["seed-audio", "text-to-speech"];
|
|
1322
1618
|
DEFAULT_MODEL6 = "seed-audio";
|
|
1323
|
-
|
|
1619
|
+
InputSchema13 = z.object({
|
|
1324
1620
|
prompt: z.string().min(1).describe(
|
|
1325
1621
|
"A script for speech, or a scene description for cinematic audio (dialogue + effects + music). Reference uploaded clips as @Audio1..@Audio3."
|
|
1326
1622
|
),
|
|
@@ -1331,7 +1627,7 @@ var init_audio = __esm({
|
|
|
1331
1627
|
output_format: z.string().optional().describe("seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3, wav, flac, pcm."),
|
|
1332
1628
|
speed: z.number().min(0.5).max(2).optional().describe("Speech speed multiplier (0.5\u20132).")
|
|
1333
1629
|
});
|
|
1334
|
-
|
|
1630
|
+
InputJsonSchema13 = {
|
|
1335
1631
|
type: "object",
|
|
1336
1632
|
properties: {
|
|
1337
1633
|
prompt: { type: "string" },
|
|
@@ -1347,15 +1643,15 @@ var init_audio = __esm({
|
|
|
1347
1643
|
audioSkill = {
|
|
1348
1644
|
name: "pixeldojo:audio",
|
|
1349
1645
|
description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain TTS available).",
|
|
1350
|
-
inputZod:
|
|
1351
|
-
inputJsonSchema:
|
|
1646
|
+
inputZod: InputSchema13,
|
|
1647
|
+
inputJsonSchema: InputJsonSchema13,
|
|
1352
1648
|
allowedModels: [...ALLOWED4],
|
|
1353
1649
|
defaultModel: DEFAULT_MODEL6,
|
|
1354
|
-
run:
|
|
1650
|
+
run: run13
|
|
1355
1651
|
};
|
|
1356
1652
|
}
|
|
1357
1653
|
});
|
|
1358
|
-
async function
|
|
1654
|
+
async function run14(input, ctx) {
|
|
1359
1655
|
const res = await ctx.client.listLibrary({
|
|
1360
1656
|
q: input.q,
|
|
1361
1657
|
model: input.model,
|
|
@@ -1386,16 +1682,16 @@ ${lines.join("\n")}
|
|
|
1386
1682
|
Pass any URL back into :generate, :edit, :character, or :upscale as a reference input.`
|
|
1387
1683
|
};
|
|
1388
1684
|
}
|
|
1389
|
-
var
|
|
1685
|
+
var InputSchema14, InputJsonSchema14, librarySkill;
|
|
1390
1686
|
var init_library = __esm({
|
|
1391
1687
|
"src/skills/library.ts"() {
|
|
1392
|
-
|
|
1688
|
+
InputSchema14 = z.object({
|
|
1393
1689
|
q: z.string().optional().describe("Substring match against the prompt that produced the asset."),
|
|
1394
1690
|
model: z.string().optional().describe("Restrict to a single model (apiId)."),
|
|
1395
1691
|
modality: z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
|
|
1396
1692
|
limit: z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
|
|
1397
1693
|
});
|
|
1398
|
-
|
|
1694
|
+
InputJsonSchema14 = {
|
|
1399
1695
|
type: "object",
|
|
1400
1696
|
properties: {
|
|
1401
1697
|
q: { type: "string" },
|
|
@@ -1408,15 +1704,15 @@ var init_library = __esm({
|
|
|
1408
1704
|
librarySkill = {
|
|
1409
1705
|
name: "pixeldojo:library",
|
|
1410
1706
|
description: "Search your media library: saved My Media items (permanent) and recent API/MCP generations (expire after 24h), newest first. Returns asset URLs to reuse as reference inputs. Keep a result forever with pixeldojo:save.",
|
|
1411
|
-
inputZod:
|
|
1412
|
-
inputJsonSchema:
|
|
1707
|
+
inputZod: InputSchema14,
|
|
1708
|
+
inputJsonSchema: InputJsonSchema14,
|
|
1413
1709
|
allowedModels: [],
|
|
1414
1710
|
defaultModel: "",
|
|
1415
|
-
run:
|
|
1711
|
+
run: run14
|
|
1416
1712
|
};
|
|
1417
1713
|
}
|
|
1418
1714
|
});
|
|
1419
|
-
async function
|
|
1715
|
+
async function run15(input, ctx) {
|
|
1420
1716
|
const res = await ctx.client.saveMedia({
|
|
1421
1717
|
url: input.url,
|
|
1422
1718
|
prompt: input.prompt,
|
|
@@ -1430,17 +1726,17 @@ async function run14(input, ctx) {
|
|
|
1430
1726
|
It will appear at https://pixeldojo.ai/my-images and in pixeldojo:library searches.`
|
|
1431
1727
|
};
|
|
1432
1728
|
}
|
|
1433
|
-
var
|
|
1729
|
+
var InputSchema15, InputJsonSchema15, saveSkill;
|
|
1434
1730
|
var init_save = __esm({
|
|
1435
1731
|
"src/skills/save.ts"() {
|
|
1436
|
-
|
|
1732
|
+
InputSchema15 = z.object({
|
|
1437
1733
|
url: z.string().url().describe("The asset URL to save (image or video)."),
|
|
1438
1734
|
prompt: z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
|
|
1439
1735
|
tool_name: z.string().optional().describe("Tool/model slug that produced it."),
|
|
1440
1736
|
modality: z.enum(["image", "video", "audio"]).optional(),
|
|
1441
1737
|
job_id: z.string().optional().describe("The jobId that produced this asset, when known.")
|
|
1442
1738
|
});
|
|
1443
|
-
|
|
1739
|
+
InputJsonSchema15 = {
|
|
1444
1740
|
type: "object",
|
|
1445
1741
|
properties: {
|
|
1446
1742
|
url: { type: "string", format: "uri" },
|
|
@@ -1454,11 +1750,11 @@ var init_save = __esm({
|
|
|
1454
1750
|
saveSkill = {
|
|
1455
1751
|
name: "pixeldojo:save",
|
|
1456
1752
|
description: "Save a generated image or video into the user's My Media library permanently. Generation outputs otherwise expire after 24 hours \u2014 call this when the user wants to keep a result.",
|
|
1457
|
-
inputZod:
|
|
1458
|
-
inputJsonSchema:
|
|
1753
|
+
inputZod: InputSchema15,
|
|
1754
|
+
inputJsonSchema: InputJsonSchema15,
|
|
1459
1755
|
allowedModels: [],
|
|
1460
1756
|
defaultModel: "",
|
|
1461
|
-
run:
|
|
1757
|
+
run: run15
|
|
1462
1758
|
};
|
|
1463
1759
|
}
|
|
1464
1760
|
});
|
|
@@ -1676,6 +1972,7 @@ __export(skills_exports, {
|
|
|
1676
1972
|
campaignStatusSkill: () => campaignStatusSkill,
|
|
1677
1973
|
characterSkill: () => characterSkill,
|
|
1678
1974
|
editSkill: () => editSkill,
|
|
1975
|
+
filmSkill: () => filmSkill,
|
|
1679
1976
|
findSkill: () => findSkill,
|
|
1680
1977
|
fromUrlSkill: () => fromUrlSkill,
|
|
1681
1978
|
generateSkill: () => generateSkill,
|
|
@@ -1702,6 +1999,7 @@ var init_skills = __esm({
|
|
|
1702
1999
|
init_upscale();
|
|
1703
2000
|
init_status();
|
|
1704
2001
|
init_from_url();
|
|
2002
|
+
init_film();
|
|
1705
2003
|
init_ad();
|
|
1706
2004
|
init_campaign();
|
|
1707
2005
|
init_campaign_status();
|
|
@@ -1727,7 +2025,8 @@ var init_skills = __esm({
|
|
|
1727
2025
|
uploadSkill,
|
|
1728
2026
|
adSkill,
|
|
1729
2027
|
campaignSkill,
|
|
1730
|
-
campaignStatusSkill
|
|
2028
|
+
campaignStatusSkill,
|
|
2029
|
+
filmSkill
|
|
1731
2030
|
];
|
|
1732
2031
|
}
|
|
1733
2032
|
});
|
|
@@ -1782,7 +2081,7 @@ function makeRedactedLogger(target = console) {
|
|
|
1782
2081
|
}
|
|
1783
2082
|
|
|
1784
2083
|
// src/server.ts
|
|
1785
|
-
var PACKAGE_VERSION = "0.
|
|
2084
|
+
var PACKAGE_VERSION = "0.9.0" ;
|
|
1786
2085
|
function buildServer(options) {
|
|
1787
2086
|
if (!options.apiKey) {
|
|
1788
2087
|
throw new Error(
|
|
@@ -1849,6 +2148,8 @@ function buildServer(options) {
|
|
|
1849
2148
|
});
|
|
1850
2149
|
const ctx = {
|
|
1851
2150
|
client,
|
|
2151
|
+
apiKey: options.apiKey,
|
|
2152
|
+
baseUrl,
|
|
1852
2153
|
mcpVersion: version,
|
|
1853
2154
|
logger,
|
|
1854
2155
|
buildStatusUrl: (jobId) => `${baseUrl}/api/v1/jobs/${encodeURIComponent(jobId)}`
|