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