@pixeldojo/mcp 0.26.0 → 0.31.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/README.md +3 -1
- package/dist/cli.cjs +1062 -76
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1062 -76
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1062 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1062 -76
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -237,11 +237,11 @@ function nearestAspectFromEnum(requested, allowed) {
|
|
|
237
237
|
}
|
|
238
238
|
async function snapAspectRatio(apiId, requested, client, opts = {}) {
|
|
239
239
|
if (!requested) return void 0;
|
|
240
|
-
const
|
|
241
|
-
if (
|
|
240
|
+
const props2 = await getFieldConstraints(apiId, client);
|
|
241
|
+
if (props2 === null) {
|
|
242
242
|
return opts.onUnknown === "omit" ? void 0 : requested;
|
|
243
243
|
}
|
|
244
|
-
const field =
|
|
244
|
+
const field = props2.aspect_ratio;
|
|
245
245
|
if (!field) return void 0;
|
|
246
246
|
if (!field.enum || field.enum.length === 0) return requested;
|
|
247
247
|
return nearestAspectFromEnum(requested, field.enum);
|
|
@@ -272,11 +272,11 @@ function resolutionHeight(value) {
|
|
|
272
272
|
}
|
|
273
273
|
async function snapResolution(apiId, requested, client, opts = {}) {
|
|
274
274
|
if (!requested) return void 0;
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
275
|
+
const props2 = await getFieldConstraints(apiId, client);
|
|
276
|
+
if (props2 === null) {
|
|
277
277
|
return opts.onUnknown === "omit" ? void 0 : requested;
|
|
278
278
|
}
|
|
279
|
-
const field =
|
|
279
|
+
const field = props2.resolution;
|
|
280
280
|
if (!field) return void 0;
|
|
281
281
|
if (!field.enum || field.enum.length === 0) return requested;
|
|
282
282
|
if (field.enum.includes(requested)) return requested;
|
|
@@ -297,11 +297,11 @@ async function snapResolution(apiId, requested, client, opts = {}) {
|
|
|
297
297
|
}
|
|
298
298
|
async function snapDuration(apiId, requested, client, opts = {}) {
|
|
299
299
|
if (requested === void 0) return void 0;
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
300
|
+
const props2 = await getFieldConstraints(apiId, client);
|
|
301
|
+
if (props2 === null) {
|
|
302
302
|
return opts.onUnknown === "omit" ? void 0 : requested;
|
|
303
303
|
}
|
|
304
|
-
const field =
|
|
304
|
+
const field = props2.duration;
|
|
305
305
|
if (!field) return void 0;
|
|
306
306
|
if (field.enum && field.enum.length > 0) {
|
|
307
307
|
const allowed = field.enum.map(Number).filter((n) => Number.isFinite(n));
|
|
@@ -329,11 +329,11 @@ async function resolveImageRefs(apiId, urls, client) {
|
|
|
329
329
|
if (urls.length === 0) return null;
|
|
330
330
|
const info = await getModelInfo(apiId, client);
|
|
331
331
|
if (info === null) return null;
|
|
332
|
-
const
|
|
333
|
-
const key = info.media ? info.media.image?.key : REF_KEYS.find((k) =>
|
|
332
|
+
const props2 = info.props;
|
|
333
|
+
const key = info.media ? info.media.image?.key : REF_KEYS.find((k) => props2[k] !== void 0);
|
|
334
334
|
if (!key) return null;
|
|
335
335
|
const also = info.media?.image?.also;
|
|
336
|
-
const field =
|
|
336
|
+
const field = props2[key] ?? {};
|
|
337
337
|
if (field.type === "array") {
|
|
338
338
|
const cap = typeof field.maxItems === "number" ? field.maxItems : urls.length;
|
|
339
339
|
const kept = urls.slice(0, Math.max(1, cap));
|
|
@@ -549,7 +549,7 @@ var init_generate = __esm({
|
|
|
549
549
|
InputSchema = z.object({
|
|
550
550
|
prompt: z.string().min(1).max(8e3).describe('Prompt describing what to generate. Write @Name to use a character or element the user saved in Character Studio ("@Mara on a beach"): its photos are attached automatically on models that take reference images. pixeldojo_library source=characters lists them.'),
|
|
551
551
|
model: z.string().optional().describe(
|
|
552
|
-
`Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5" for images (the default), "seedance-2-5" for video (prompt only or a starting image), "wan-3-0-video" for video the others refuse. Use another model only when the user names one. Full catalog: https://pixeldojo.ai/api/v1/models; one model's fields: https://pixeldojo.ai/llm/{apiId}.txt. Omit for seedream-5, which makes still images only; name a video model to get a video.`
|
|
552
|
+
`Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5" for images (the default), "seedance-2-5" for video (prompt only or a starting image), "kling-4-0" for a whole scene up to 30 s written as a shot list (dialogue in quotes, exact on-screen text), "minimax-h3-turbo" for the cheapest video from a photo or up to 9 reference images (it needs at least one picture), "wan-3-0-video" for video the others refuse. Use another model only when the user names one. Full catalog: https://pixeldojo.ai/api/v1/models; one model's fields: https://pixeldojo.ai/llm/{apiId}.txt. Omit for seedream-5, which makes still images only; name a video model to get a video.`
|
|
553
553
|
),
|
|
554
554
|
preset: z.string().optional().describe(
|
|
555
555
|
"Preset slug that seeds the running model's settings; explicit fields and params override it. Each preset belongs to one model (browse them by model at https://pixeldojo.ai/presets), so set `model` to that preset's model: a slug the running model has no preset for is rejected with a 400 before any charge."
|
|
@@ -588,7 +588,7 @@ var init_generate = __esm({
|
|
|
588
588
|
prompt: { type: "string", description: 'Prompt describing what to generate. Write @Name to use a character or element the user saved in Character Studio ("@Mara on a beach"): its photos are attached automatically on models that take reference images. pixeldojo_library source=characters lists them.' },
|
|
589
589
|
model: {
|
|
590
590
|
type: "string",
|
|
591
|
-
description: `Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5" for images (the default), "seedance-2-5" for video (prompt only or a starting image), "wan-3-0-video" for video the others refuse. Use another model only when the user names one. Full catalog: https://pixeldojo.ai/api/v1/models; one model's fields: https://pixeldojo.ai/llm/{apiId}.txt. Omit for seedream-5, which makes still images only; name a video model to get a video.`
|
|
591
|
+
description: `Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5" for images (the default), "seedance-2-5" for video (prompt only or a starting image), "kling-4-0" for a whole scene up to 30 s written as a shot list (dialogue in quotes, exact on-screen text), "minimax-h3-turbo" for the cheapest video from a photo or up to 9 reference images (it needs at least one picture), "wan-3-0-video" for video the others refuse. Use another model only when the user names one. Full catalog: https://pixeldojo.ai/api/v1/models; one model's fields: https://pixeldojo.ai/llm/{apiId}.txt. Omit for seedream-5, which makes still images only; name a video model to get a video.`
|
|
592
592
|
},
|
|
593
593
|
preset: {
|
|
594
594
|
type: "string",
|
|
@@ -636,8 +636,8 @@ var init_generate = __esm({
|
|
|
636
636
|
};
|
|
637
637
|
generateSkill = {
|
|
638
638
|
name: "pixeldojo_generate",
|
|
639
|
-
description: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image. If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills, `video_url` for a source clip, `image_path`/`image_paths` for absolute local files (uploaded to 24h temp storage first). A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
|
|
640
|
-
hostedDescription: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image. If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills and `video_url` for a source clip, all public http(s) URLs. A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
|
|
639
|
+
description: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image; kling-4-0 for a multi-shot scene up to 30 s with dialogue; minimax-h3-turbo for the lowest price when you have a photo or reference images (it refuses a prompt alone). If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills, `video_url` for a source clip, `image_path`/`image_paths` for absolute local files (uploaded to 24h temp storage first). A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
|
|
640
|
+
hostedDescription: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image; kling-4-0 for a multi-shot scene up to 30 s with dialogue; minimax-h3-turbo for the lowest price when you have a photo or reference images (it refuses a prompt alone). If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills and `video_url` for a source clip, all public http(s) URLs. A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
|
|
641
641
|
inputZod: InputSchema,
|
|
642
642
|
inputJsonSchema: InputJsonSchema,
|
|
643
643
|
allowedModels: null,
|
|
@@ -1051,6 +1051,8 @@ var init_storyboard = __esm({
|
|
|
1051
1051
|
ALLOWED2 = [
|
|
1052
1052
|
"veo-3.1-fast",
|
|
1053
1053
|
"veo-3.1-standard",
|
|
1054
|
+
// Kling 4.0 (2026-10-24): a brief written as a shot list renders as one clip, up to 30 s.
|
|
1055
|
+
"kling-4-0",
|
|
1054
1056
|
"kling-video-v3-standard-text",
|
|
1055
1057
|
"seedance-2-high",
|
|
1056
1058
|
VIDEO_REFERENCE_MODEL
|
|
@@ -1671,7 +1673,7 @@ function needsSave(url) {
|
|
|
1671
1673
|
return Boolean(url && url.startsWith("https://temp.pixeldojo.ai/"));
|
|
1672
1674
|
}
|
|
1673
1675
|
async function filmApi(ctx, method, path, body) {
|
|
1674
|
-
const res = await fetch(`${ctx.baseUrl}${path}`, {
|
|
1676
|
+
const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}${path}`, {
|
|
1675
1677
|
method,
|
|
1676
1678
|
headers: {
|
|
1677
1679
|
Authorization: `Bearer ${ctx.apiKey}`,
|
|
@@ -2253,7 +2255,7 @@ var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, InputSchema10, InputJsonSchema10, sho
|
|
|
2253
2255
|
var init_film = __esm({
|
|
2254
2256
|
"src/skills/film.ts"() {
|
|
2255
2257
|
init_upload_helpers();
|
|
2256
|
-
STORY_LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
|
|
2258
|
+
STORY_LOOKS = ["live-action", "action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
|
|
2257
2259
|
MUSIC_VIDEO_LOOKS = ["rap-video", "neon-club", "stage-performance", "retro-camcorder", "y2k-pop", "moody-rnb", "rock-grit", "dream-pop", "k-pop"];
|
|
2258
2260
|
LOOKS = [...STORY_LOOKS, ...MUSIC_VIDEO_LOOKS];
|
|
2259
2261
|
InputSchema10 = z.object({
|
|
@@ -2442,6 +2444,27 @@ function buildVideoPayload(verb, apiId, i) {
|
|
|
2442
2444
|
return { body: { prompt, video_url: i.videoUrl, video_duration: vd }, note: droppedImages };
|
|
2443
2445
|
case "happyhorse-1.0-video-edit":
|
|
2444
2446
|
return { body: { prompt, video_url: i.videoUrl, video_duration: vd, reference_urls: i.images } };
|
|
2447
|
+
case "kling-4-0":
|
|
2448
|
+
return {
|
|
2449
|
+
body: {
|
|
2450
|
+
prompt: /@video\s*1/i.test(prompt) ? prompt : `Edit @Video1: ${prompt}`,
|
|
2451
|
+
video: i.videoUrl,
|
|
2452
|
+
reference_images: i.images,
|
|
2453
|
+
duration: clamp(Math.ceil(vd), 3, 30),
|
|
2454
|
+
resolution: "1080p"
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
case "minimax-h3-turbo":
|
|
2458
|
+
return {
|
|
2459
|
+
body: {
|
|
2460
|
+
prompt: `Recreate <Video 1> exactly: the same people, the same motion and timing, the same camera move and framing, with one change: ${prompt}`,
|
|
2461
|
+
reference_videos: [i.videoUrl],
|
|
2462
|
+
reference_video_audio: true,
|
|
2463
|
+
resolution: "720p",
|
|
2464
|
+
duration: clamp(Math.ceil(vd ?? 5), 5, 15)
|
|
2465
|
+
},
|
|
2466
|
+
note: droppedImages
|
|
2467
|
+
};
|
|
2445
2468
|
default:
|
|
2446
2469
|
return { body: { prompt, video_url: i.videoUrl, video_duration: vd, image_urls: i.images } };
|
|
2447
2470
|
}
|
|
@@ -2570,6 +2593,8 @@ var init_video = __esm({
|
|
|
2570
2593
|
"seedance-2-5-video-edit",
|
|
2571
2594
|
"flux-video-edit",
|
|
2572
2595
|
"kling-video-edit",
|
|
2596
|
+
"kling-4-0",
|
|
2597
|
+
"minimax-h3-turbo",
|
|
2573
2598
|
"wan-video-edit",
|
|
2574
2599
|
"seedance-video-edit",
|
|
2575
2600
|
"happyhorse-1.0-video-edit",
|
|
@@ -2589,9 +2614,10 @@ var init_video = __esm({
|
|
|
2589
2614
|
"happyhorse-1.0-video-edit",
|
|
2590
2615
|
"xai-video-edit",
|
|
2591
2616
|
"kling-motion-control",
|
|
2592
|
-
"wan-video-character-swap"
|
|
2617
|
+
"wan-video-character-swap",
|
|
2618
|
+
"kling-4-0"
|
|
2593
2619
|
]);
|
|
2594
|
-
NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["flux-video-edit", "xai-video-edit"]);
|
|
2620
|
+
NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["flux-video-edit", "xai-video-edit", "minimax-h3-turbo"]);
|
|
2595
2621
|
InputSchema11 = z.object({
|
|
2596
2622
|
verb: z.enum(VERBS),
|
|
2597
2623
|
prompt: z.string().max(8e3).optional(),
|
|
@@ -2677,7 +2703,7 @@ var init_video = __esm({
|
|
|
2677
2703
|
}
|
|
2678
2704
|
});
|
|
2679
2705
|
async function episodeApi(ctx, method, path, body) {
|
|
2680
|
-
const res = await fetch(`${ctx.baseUrl}/api/faceless-studio/episodes${path}`, {
|
|
2706
|
+
const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/faceless-studio/episodes${path}`, {
|
|
2681
2707
|
method,
|
|
2682
2708
|
headers: {
|
|
2683
2709
|
Authorization: `Bearer ${ctx.apiKey}`,
|
|
@@ -2917,6 +2943,487 @@ var init_episode = __esm({
|
|
|
2917
2943
|
};
|
|
2918
2944
|
}
|
|
2919
2945
|
});
|
|
2946
|
+
async function dramaApi(ctx, method, path, body) {
|
|
2947
|
+
const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/drama${path}`, {
|
|
2948
|
+
method,
|
|
2949
|
+
headers: {
|
|
2950
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
2951
|
+
"Content-Type": "application/json",
|
|
2952
|
+
"x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
|
|
2953
|
+
"x-pixeldojo-skill": "drama"
|
|
2954
|
+
},
|
|
2955
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
2956
|
+
});
|
|
2957
|
+
const data = await res.json().catch(() => ({}));
|
|
2958
|
+
return { status: res.status, data };
|
|
2959
|
+
}
|
|
2960
|
+
function describeDrama(v, o = DRAMA_OPTS) {
|
|
2961
|
+
const id = v.id;
|
|
2962
|
+
const stubborn = Array.from(new Set(v.retaken.filter((b, i, all) => all.indexOf(b) !== i)));
|
|
2963
|
+
const simplify = stubborn.length ? `${o.unit[0].toUpperCase()}${o.unit.slice(1)}s ${stubborn.join(", ")} have been filmed three times: suggest simplifying them (drop an action, split a busy shot, change the angle) rather than rewording the note.` : "";
|
|
2964
|
+
const next2 = (action) => `${o.tool} with {"${o.idField}": "${id}", "action": "${action}"}`;
|
|
2965
|
+
const music = o.unit === "section";
|
|
2966
|
+
const what = music ? "music video" : "episode";
|
|
2967
|
+
if (v.status === "failed") return { kind: "text", text: `The ${what} stopped: ${v.error ?? "unknown error"}. Each render that failed was refunded.${v.beats.some((b) => b.url) ? ` Re-film ${o.unit}s with action "redo".` : ""}` };
|
|
2968
|
+
if (v.status === "quote") {
|
|
2969
|
+
const cost = music ? `Filming costs about ${v.estimate_credits} credits (cast portraits and ${v.sections?.length ?? "several"} sections lip-synced to the song).` : `Filming costs about ${v.estimate_credits} credits (cast portraits, 12-second scenes with their dialogue and sound${v.score === false ? ", no music" : ", and one score over the finished edit"}); every scene is checked, and a scene that drops a line or shows a clear fault waits for the user's OK before it is filmed again, at the scene's own price (a render that fails is tried again free).`;
|
|
2970
|
+
return {
|
|
2971
|
+
kind: "text",
|
|
2972
|
+
text: [`Planned (${o.idField}: ${id}), ${v.format === "9:16" ? "vertical 9:16" : "widescreen 16:9"} (to switch, create it again in the other format before producing):`, v.plan ?? "", "", `Nothing has been charged yet. ${cost} Show this to the user and, once they agree, call ${next2("produce")}. (In Sensei: put it in pixeldojo_plan as ONE step with skill "${music ? "music_video" : "drama"}" and params {"${o.idField}": "${id}"}, which prices it from this quote.)`].join("\n")
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
const gist = (note) => {
|
|
2976
|
+
const first = note.split(/(?<=\.) /)[0];
|
|
2977
|
+
return first.length > 160 ? `${first.slice(0, 157).trimEnd()}...` : first;
|
|
2978
|
+
};
|
|
2979
|
+
const rewrites = (b) => {
|
|
2980
|
+
const shots2 = Array.from(new Set(Array.from((b.redo_films ?? "").matchAll(/Shot (\d+) \(/g), (m) => m[1])));
|
|
2981
|
+
return shots2.length ? ` (the redo rewrites shot${shots2.length > 1 ? "s" : ""} ${shots2.join(", ")})` : "";
|
|
2982
|
+
};
|
|
2983
|
+
if (v.status === "notes") {
|
|
2984
|
+
const flagged = v.beats.filter((b) => b.note && (b.needs_redo ?? true));
|
|
2985
|
+
const other = v.beats.filter((b) => b.note && b.needs_redo === false);
|
|
2986
|
+
return {
|
|
2987
|
+
kind: "text",
|
|
2988
|
+
text: [
|
|
2989
|
+
`Every ${o.unit} is filmed. Before the cut, the review flagged ${o.unit}s ${flagged.map((b) => b.beat).join(", ")}:`,
|
|
2990
|
+
...flagged.map((b) => `- ${o.unit} ${b.beat}: ${gist(b.note)}${rewrites(b)}`),
|
|
2991
|
+
other.length ? `Also noted, no redo needed: ${other.map((b) => `${o.unit} ${b.beat}: ${gist(b.note)}`).join(" ")}` : "",
|
|
2992
|
+
simplify,
|
|
2993
|
+
`Show these to the user and ask which they want: redo those ${o.unit}s with the review's fixes (${o.tool} with {"${o.idField}": "${id}", "action": "redo", "beats": [${flagged.map((b) => b.beat).join(", ")}]}, no notes needed, each at its own price; add notes only for the user's own changes), or cut it as it is (action "cut", free). Nothing more is filmed until they choose.`
|
|
2994
|
+
].filter(Boolean).join("\n")
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
if (v.status === "done" && v.final_url) {
|
|
2998
|
+
const redo = v.beats.filter((b) => (b.note || b.missing_lines.length) && (b.needs_redo ?? true));
|
|
2999
|
+
const notes = redo.map((b) => `${o.unit} ${b.beat}: ${b.note ? gist(b.note) : `did not say ${b.missing_lines.map((l) => `"${l}"`).join(", ")}`}`);
|
|
3000
|
+
const other = v.beats.filter((b) => b.note && b.needs_redo === false).map((b) => `${o.unit} ${b.beat}: ${gist(b.note)}`);
|
|
3001
|
+
return {
|
|
3002
|
+
kind: "assets",
|
|
3003
|
+
text: [
|
|
3004
|
+
`"${v.title}" is finished and saved to the Library: ${v.final_url}`,
|
|
3005
|
+
v.retaken.length ? `Filmed again: ${o.unit} ${v.retaken.join(", ")}.` : "",
|
|
3006
|
+
notes.length ? `Review notes: ${notes.join("; ")}. Offer a redo of those ${o.unit}s (action "redo" with beats and notes).` : "",
|
|
3007
|
+
other.length ? `Also noted, no redo needed: ${other.join("; ")}.` : "",
|
|
3008
|
+
simplify
|
|
3009
|
+
].filter(Boolean).join("\n"),
|
|
3010
|
+
assetUrls: [v.final_url]
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
const doing = music ? { scripting: "listening to the song and writing the plan", shots: "filming the sections", cutting: "cutting it to the song" }[v.status] ?? STEP[v.status] ?? v.status : STEP[v.status] ?? v.status;
|
|
3014
|
+
const ready = v.beats.filter((b) => b.url).length;
|
|
3015
|
+
const total = v.beats.length || (music ? v.sections?.length ?? 0 : 5);
|
|
3016
|
+
const again = v.rendering.map((n) => v.beats.find((b) => b.beat === n)).filter((b) => Boolean(b?.url)).map((b) => `${o.unit} ${b.beat} is being filmed again${b.filming_note ? ` (your note: ${gist(b.filming_note)})` : b.missing_lines.length ? ` (it dropped the line "${b.missing_lines[0]}")` : b.note ? ` (the review asked: ${gist(b.note)})` : ""}`);
|
|
3017
|
+
return {
|
|
3018
|
+
kind: "text",
|
|
3019
|
+
text: `Still ${doing}${v.status === "shots" ? ` (${ready} of ${total} ${o.unit}s ready${v.rendering.length ? `, filming ${o.unit} ${v.rendering.join(", ")}` : ""})` : ""}.${again.length ? ` ${again.join("; ")}.` : ""} It takes about 10 to 15 minutes in all. Call ${next2("status")} in about a minute.`,
|
|
3020
|
+
followUp: { input: { action: "status", [o.idField]: id }, afterSec: v.status === "scripting" ? 20 : 45 }
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
async function waitForPlan2(ctx, id) {
|
|
3024
|
+
const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
3025
|
+
let last = await dramaApi(ctx, "GET", `/${id}`);
|
|
3026
|
+
for (let i = 0; i < 8 && last.data.status === "scripting"; i += 1) {
|
|
3027
|
+
await sleep(3e3);
|
|
3028
|
+
last = await dramaApi(ctx, "GET", `/${id}`);
|
|
3029
|
+
}
|
|
3030
|
+
return last;
|
|
3031
|
+
}
|
|
3032
|
+
var KINDS, LOOKS2, STORY_TYPES, InputSchema13, InputJsonSchema13, errorText, STEP, DRAMA_OPTS, dramaSkill;
|
|
3033
|
+
var init_drama = __esm({
|
|
3034
|
+
"src/skills/drama.ts"() {
|
|
3035
|
+
KINDS = ["micro-drama", "short-film", "trailer"];
|
|
3036
|
+
LOOKS2 = ["live-action", "action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
|
|
3037
|
+
STORY_TYPES = ["billionaire", "werewolf", "mafia", "revenge", "secret-identity", "vampire", "second-chance"];
|
|
3038
|
+
InputSchema13 = z.object({
|
|
3039
|
+
action: z.enum(["create", "produce", "status", "redo", "cut"]).default("create"),
|
|
3040
|
+
idea: z.string().min(8).max(1500).optional(),
|
|
3041
|
+
format: z.enum(["9:16", "16:9"]).default("9:16"),
|
|
3042
|
+
mature: z.boolean().optional(),
|
|
3043
|
+
kind: z.enum(KINDS).default("micro-drama"),
|
|
3044
|
+
look: z.enum(LOOKS2).optional(),
|
|
3045
|
+
story_type: z.enum(STORY_TYPES).optional(),
|
|
3046
|
+
title_card: z.boolean().optional(),
|
|
3047
|
+
name_tags: z.boolean().optional(),
|
|
3048
|
+
end_screen: z.boolean().optional(),
|
|
3049
|
+
end_text: z.string().max(60).optional(),
|
|
3050
|
+
brand_color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
|
|
3051
|
+
logo_url: z.string().url().optional(),
|
|
3052
|
+
cast: z.array(z.object({ name: z.string().min(1).max(40), image_url: z.string().url() })).max(5).optional(),
|
|
3053
|
+
scenes: z.number().int().min(1).max(15).optional(),
|
|
3054
|
+
score: z.boolean().optional(),
|
|
3055
|
+
drama_id: z.string().max(60).optional(),
|
|
3056
|
+
beats: z.array(z.number().int().min(1).max(15)).min(1).max(15).optional(),
|
|
3057
|
+
notes: z.array(z.string().max(300)).max(15).optional()
|
|
3058
|
+
});
|
|
3059
|
+
InputJsonSchema13 = {
|
|
3060
|
+
type: "object",
|
|
3061
|
+
properties: {
|
|
3062
|
+
action: {
|
|
3063
|
+
type: "string",
|
|
3064
|
+
enum: ["create", "produce", "status", "redo", "cut"],
|
|
3065
|
+
description: "create (default, free): writes the episode plan (5 scenes unless scenes says, cast, lines) and returns drama_id and the price. Show the plan and price to the user and wait for their OK. produce (charged): films it. status (free): progress; call every ~60s until it returns the episode. redo (charged): re-film chosen beats of a finished episode or one waiting at the review, then it cuts again. cut (free): when the review flagged beats and the user wants it as it is, cut it now."
|
|
3066
|
+
},
|
|
3067
|
+
idea: { type: "string", description: "Create: the story in a line or two (who, the conflict, the twist)." },
|
|
3068
|
+
format: { type: "string", enum: ["9:16", "16:9"], description: "Create: vertical 9:16 (default) or widescreen 16:9." },
|
|
3069
|
+
mature: { type: "boolean", description: "Create: true for adult work (the server also detects it from the idea). Runs on the less-restricted engines." },
|
|
3070
|
+
kind: {
|
|
3071
|
+
type: "string",
|
|
3072
|
+
enum: [...KINDS],
|
|
3073
|
+
description: "Create: micro-drama (default: episode one with a hook and a cliffhanger), short-film (a complete 60 s story) or trailer (sells a film that does not exist, never shows the ending)."
|
|
3074
|
+
},
|
|
3075
|
+
look: { type: "string", enum: [...LOOKS2], description: "Create (optional): the visual style and genre. Default live-action (a micro-drama story type brings its own)." },
|
|
3076
|
+
story_type: {
|
|
3077
|
+
type: "string",
|
|
3078
|
+
enum: [...STORY_TYPES],
|
|
3079
|
+
description: "Create (optional, micro-drama only): the genre, which sets its promises (a hidden heir, a fated mate, a debt of blood...). Pick the one the idea fits; omit for anything else."
|
|
3080
|
+
},
|
|
3081
|
+
title_card: { type: "boolean", description: "Create: the episode title over the opening hook. Default on." },
|
|
3082
|
+
name_tags: { type: "boolean", description: "Create: a name and role tag the first time each character speaks. Default on." },
|
|
3083
|
+
end_screen: { type: "boolean", description: 'Create: a 3.5-second "To be continued" end screen after the last line. Default on.' },
|
|
3084
|
+
end_text: { type: "string", description: `Create: the end screen's line, up to 60 characters. Default "Episode 2 coming soon".` },
|
|
3085
|
+
brand_color: { type: "string", description: "Create: accent colour for the graphics, #RRGGBB. Default a warm gold." },
|
|
3086
|
+
logo_url: { type: "string", description: "Create: a logo on the PixelDojo CDN for the end screen." },
|
|
3087
|
+
cast: {
|
|
3088
|
+
type: "array",
|
|
3089
|
+
items: {
|
|
3090
|
+
type: "object",
|
|
3091
|
+
properties: {
|
|
3092
|
+
name: { type: "string", description: "The character's name as the story uses it." },
|
|
3093
|
+
image_url: { type: "string", description: "A clear front-facing portrait on the PixelDojo CDN." }
|
|
3094
|
+
},
|
|
3095
|
+
required: ["name", "image_url"]
|
|
3096
|
+
},
|
|
3097
|
+
description: "Create (optional): up to 5 characters to star (people, animals or creatures), each with a portrait on the PixelDojo CDN (a saved character from pixeldojo_library). Others get a portrait made for them."
|
|
3098
|
+
},
|
|
3099
|
+
scenes: { type: "integer", minimum: 1, maximum: 15, description: "Create (optional): how many 12-second scenes, default 5. Pass it when the user names a count or a length (about 12 s a scene)." },
|
|
3100
|
+
score: { type: "boolean", description: "Create (optional): one score laid over the finished edit, on by default and included in the price. Pass false only when the user wants no music (dialogue and ambience only)." },
|
|
3101
|
+
drama_id: { type: "string", description: "From create. Required for produce, status and redo." },
|
|
3102
|
+
beats: { type: "array", items: { type: "integer", minimum: 1, maximum: 15 }, description: "Redo: the beat numbers to film again." },
|
|
3103
|
+
notes: { type: "array", items: { type: "string" }, description: "Redo (optional): what to fix in each of those beats, in the same order." }
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
errorText = (d, status2) => (typeof d.error === "string" ? d.error : d.error?.message) ?? `HTTP ${status2}`;
|
|
3107
|
+
STEP = {
|
|
3108
|
+
scripting: "writing the episode",
|
|
3109
|
+
keyframes: "making the cast portraits and the location",
|
|
3110
|
+
shots: "filming the beats",
|
|
3111
|
+
cutting: "cutting the episode"
|
|
3112
|
+
};
|
|
3113
|
+
DRAMA_OPTS = { tool: "pixeldojo_drama", idField: "drama_id", unit: "beat" };
|
|
3114
|
+
dramaSkill = {
|
|
3115
|
+
name: "pixeldojo_drama",
|
|
3116
|
+
description: "Make a 60-second micro-drama episode (hook, twist, cliffhanger; optionally a classic genre such as billionaire, werewolf or mafia), short film (a complete story) or movie trailer from an idea, vertical 9:16 or 16:9, in any of Film Studio's looks (live-action, anime, 3D animation, noir, horror, sci-fi, western, romance, comedy and more). Five 12-second scenes, each filmed in one pass with several cuts, the cast's faces kept from portraits and every line spoken and lip-synced, cut together with their own sound and one score over the finished edit, with free motion graphics (title card, name tags, end screen; each can be turned off), and saved to the Library. Not for music videos (pixeldojo_music_video) create is free and returns the plan and the exact price (about 380 credits for a regular episode, about 160 for mature work); produce films it; then call status until it is finished (10-15 minutes).",
|
|
3117
|
+
inputZod: InputSchema13,
|
|
3118
|
+
inputJsonSchema: InputJsonSchema13,
|
|
3119
|
+
allowedModels: ["seedance-2-5", "seedream-4"],
|
|
3120
|
+
defaultModel: "seedance-2-5",
|
|
3121
|
+
async run(input, ctx) {
|
|
3122
|
+
if (input.action === "create") {
|
|
3123
|
+
if (!input.idea?.trim()) return { kind: "text", text: "Provide an `idea` (a line or two) to plan an episode." };
|
|
3124
|
+
const created = await dramaApi(ctx, "POST", "", {
|
|
3125
|
+
idea: input.idea.trim(),
|
|
3126
|
+
format: input.format,
|
|
3127
|
+
kind: input.kind,
|
|
3128
|
+
...input.mature ? { mature: true } : {},
|
|
3129
|
+
...input.look ? { look: input.look } : {},
|
|
3130
|
+
...input.story_type ? { story_type: input.story_type } : {},
|
|
3131
|
+
...input.scenes ? { scenes: input.scenes } : {},
|
|
3132
|
+
...input.score === false ? { score: false } : {},
|
|
3133
|
+
...Object.fromEntries(
|
|
3134
|
+
["title_card", "name_tags", "end_screen", "end_text", "brand_color", "logo_url"].filter((k) => input[k] !== void 0).map((k) => [k, input[k]])
|
|
3135
|
+
),
|
|
3136
|
+
...input.cast?.length ? { cast: input.cast } : {},
|
|
3137
|
+
// Stop before the cut when the review flags a scene: the user decides, not the agent.
|
|
3138
|
+
review: true
|
|
3139
|
+
});
|
|
3140
|
+
if (!created.data.id) return { kind: "text", text: `Could not start the episode: ${errorText(created.data, created.status)}` };
|
|
3141
|
+
const planned = await waitForPlan2(ctx, created.data.id);
|
|
3142
|
+
return describeDrama(planned.data);
|
|
3143
|
+
}
|
|
3144
|
+
if (!input.drama_id) return { kind: "text", text: `${input.action} needs drama_id (from create).` };
|
|
3145
|
+
const res = input.action === "status" ? await dramaApi(ctx, "GET", `/${input.drama_id}`) : input.action === "produce" || input.action === "cut" ? await dramaApi(ctx, "POST", `/${input.drama_id}`, { action: input.action }) : input.beats?.length ? await dramaApi(ctx, "POST", `/${input.drama_id}`, { action: "redo", beats: input.beats, ...input.notes?.length ? { notes: input.notes } : {} }) : null;
|
|
3146
|
+
if (!res) return { kind: "text", text: "redo needs beats (the beat numbers to film again)." };
|
|
3147
|
+
if (!res.data.id) return { kind: "text", text: errorText(res.data, res.status) };
|
|
3148
|
+
return describeDrama(res.data);
|
|
3149
|
+
}
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3153
|
+
async function api(ctx, method, path, body) {
|
|
3154
|
+
const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/music-video${path}`, {
|
|
3155
|
+
method,
|
|
3156
|
+
headers: {
|
|
3157
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
3158
|
+
"Content-Type": "application/json",
|
|
3159
|
+
"x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
|
|
3160
|
+
"x-pixeldojo-skill": "music_video"
|
|
3161
|
+
},
|
|
3162
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
3163
|
+
});
|
|
3164
|
+
return { status: res.status, data: await res.json().catch(() => ({})) };
|
|
3165
|
+
}
|
|
3166
|
+
var MUSIC_LOOKS, InputSchema14, InputJsonSchema14, errorText2, describe, musicVideoSkill;
|
|
3167
|
+
var init_music_video = __esm({
|
|
3168
|
+
"src/skills/music-video.ts"() {
|
|
3169
|
+
init_drama();
|
|
3170
|
+
MUSIC_LOOKS = [
|
|
3171
|
+
"live-action",
|
|
3172
|
+
"action",
|
|
3173
|
+
"anime",
|
|
3174
|
+
"3d-animation",
|
|
3175
|
+
"noir",
|
|
3176
|
+
"horror",
|
|
3177
|
+
"sci-fi",
|
|
3178
|
+
"documentary",
|
|
3179
|
+
"western",
|
|
3180
|
+
"romance",
|
|
3181
|
+
"romantic-comedy",
|
|
3182
|
+
"comedy",
|
|
3183
|
+
"rap-video",
|
|
3184
|
+
"neon-club",
|
|
3185
|
+
"stage-performance",
|
|
3186
|
+
"retro-camcorder",
|
|
3187
|
+
"y2k-pop",
|
|
3188
|
+
"moody-rnb",
|
|
3189
|
+
"rock-grit",
|
|
3190
|
+
"dream-pop",
|
|
3191
|
+
"k-pop"
|
|
3192
|
+
];
|
|
3193
|
+
InputSchema14 = z.object({
|
|
3194
|
+
action: z.enum(["create", "produce", "status", "redo"]).default("create"),
|
|
3195
|
+
song_url: z.string().url().optional(),
|
|
3196
|
+
idea: z.string().min(8).max(1500).optional(),
|
|
3197
|
+
format: z.enum(["9:16", "16:9"]).default("9:16"),
|
|
3198
|
+
mature: z.boolean().optional(),
|
|
3199
|
+
look: z.enum(MUSIC_LOOKS).optional(),
|
|
3200
|
+
start_sec: z.number().min(0).optional(),
|
|
3201
|
+
length_sec: z.number().int().min(10).max(60).optional(),
|
|
3202
|
+
artist: z.string().max(60).optional(),
|
|
3203
|
+
song_title: z.string().max(80).optional(),
|
|
3204
|
+
title_card: z.boolean().optional(),
|
|
3205
|
+
end_screen: z.boolean().optional(),
|
|
3206
|
+
end_text: z.string().max(60).optional(),
|
|
3207
|
+
cast: z.array(z.object({ name: z.string().min(1).max(40), image_url: z.string().url() })).max(3).optional(),
|
|
3208
|
+
music_video_id: z.string().max(60).optional(),
|
|
3209
|
+
beats: z.array(z.number().int().min(1).max(12)).min(1).max(12).optional(),
|
|
3210
|
+
notes: z.array(z.string().max(300)).max(12).optional()
|
|
3211
|
+
});
|
|
3212
|
+
InputJsonSchema14 = {
|
|
3213
|
+
type: "object",
|
|
3214
|
+
properties: {
|
|
3215
|
+
action: {
|
|
3216
|
+
type: "string",
|
|
3217
|
+
enum: ["create", "produce", "status", "redo"],
|
|
3218
|
+
description: "create (default, free): hears the song, splits it into sections and writes the plan; returns music_video_id and the price. Show both and wait for the user's OK. produce (charged): films it. status (free): progress; call every ~60s until it returns the video. redo (charged): re-film chosen sections, then it cuts again."
|
|
3219
|
+
},
|
|
3220
|
+
song_url: { type: "string", description: "Create: the song on the PixelDojo CDN (attach or upload it first)." },
|
|
3221
|
+
idea: { type: "string", description: "Create: the story or performance concept in a line or two." },
|
|
3222
|
+
format: { type: "string", enum: ["9:16", "16:9"], description: "Create: vertical 9:16 (default) or widescreen 16:9." },
|
|
3223
|
+
mature: { type: "boolean", description: "Create: true for adult work (the server also detects it from the idea). Runs on the less-restricted engines." },
|
|
3224
|
+
look: { type: "string", enum: [...MUSIC_LOOKS], description: "Create (optional): the visual style, e.g. neon-club, rap-video, stage-performance, retro-camcorder, k-pop." },
|
|
3225
|
+
start_sec: { type: "number", description: "Create (optional): where in the song to start, in seconds. Default 0." },
|
|
3226
|
+
length_sec: { type: "integer", minimum: 10, maximum: 60, description: "Create (optional): seconds of the song to film, 10 to 60. Default 60 or the rest of the song." },
|
|
3227
|
+
artist: { type: "string", description: "Create (optional): the artist name for the title card." },
|
|
3228
|
+
song_title: { type: "string", description: "Create (optional): the song title for the title card." },
|
|
3229
|
+
title_card: { type: "boolean", description: "Create: the song title over the opening. Default on." },
|
|
3230
|
+
end_screen: { type: "boolean", description: "Create: an end screen after the song. Default off." },
|
|
3231
|
+
end_text: { type: "string", description: "Create: the end screen's line, up to 60 characters." },
|
|
3232
|
+
cast: {
|
|
3233
|
+
type: "array",
|
|
3234
|
+
items: {
|
|
3235
|
+
type: "object",
|
|
3236
|
+
properties: {
|
|
3237
|
+
name: { type: "string", description: "The character's name; the first one is the performer." },
|
|
3238
|
+
image_url: { type: "string", description: "A clear front-facing portrait on the PixelDojo CDN." }
|
|
3239
|
+
},
|
|
3240
|
+
required: ["name", "image_url"]
|
|
3241
|
+
},
|
|
3242
|
+
description: "Create (optional): up to 3 characters, the performer first, each with a portrait on the PixelDojo CDN."
|
|
3243
|
+
},
|
|
3244
|
+
music_video_id: { type: "string", description: "From create. Required for produce, status and redo." },
|
|
3245
|
+
beats: { type: "array", items: { type: "integer", minimum: 1 }, description: "Redo: the section numbers to film again." },
|
|
3246
|
+
notes: { type: "array", items: { type: "string" }, description: "Redo (optional): what to fix in each of those sections, in the same order." }
|
|
3247
|
+
}
|
|
3248
|
+
};
|
|
3249
|
+
errorText2 = (d, status2) => (typeof d.error === "string" ? d.error : d.error?.message) ?? `HTTP ${status2}`;
|
|
3250
|
+
describe = (v) => describeDrama(v, { tool: "pixeldojo_music_video", idField: "music_video_id", unit: "section" });
|
|
3251
|
+
musicVideoSkill = {
|
|
3252
|
+
name: "pixeldojo_music_video",
|
|
3253
|
+
description: "Make a music video for the user's song (up to 60 seconds of it), vertical 9:16 or 16:9, in any Film Studio look (neon club, rap video, stage performance, retro camcorder, k-pop, anime and more). The song is split into sections at lyric gaps and each section is filmed in one pass to its own slice of the song, so the performer's lips follow every word, with the cast's faces kept from portraits; then it is cut under the original song with a title card and saved to the Library. create is free and returns the plan and price (about 1.25 credits per second of song plus portraits); produce films it; then call status until it is finished (10-15 minutes).",
|
|
3254
|
+
inputZod: InputSchema14,
|
|
3255
|
+
inputJsonSchema: InputJsonSchema14,
|
|
3256
|
+
allowedModels: ["p-video-2-pro-reference", "seedream-4"],
|
|
3257
|
+
defaultModel: "p-video-2-pro-reference",
|
|
3258
|
+
async run(input, ctx) {
|
|
3259
|
+
if (input.action === "create") {
|
|
3260
|
+
if (!input.song_url || !input.idea?.trim()) return { kind: "text", text: "Provide `song_url` (the song on the PixelDojo CDN) and an `idea` to plan a music video." };
|
|
3261
|
+
const { action: _a, music_video_id: _m, beats: _b, notes: _n, ...rest } = input;
|
|
3262
|
+
const created = await api(ctx, "POST", "", { ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== void 0)), idea: input.idea.trim() });
|
|
3263
|
+
if (!created.data.id) return { kind: "text", text: `Could not start the music video: ${errorText2(created.data, created.status)}` };
|
|
3264
|
+
const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
3265
|
+
let last = await api(ctx, "GET", `/${created.data.id}`);
|
|
3266
|
+
for (let i = 0; i < 8 && last.data.status === "scripting"; i += 1) {
|
|
3267
|
+
await sleep(3e3);
|
|
3268
|
+
last = await api(ctx, "GET", `/${created.data.id}`);
|
|
3269
|
+
}
|
|
3270
|
+
return describe(last.data);
|
|
3271
|
+
}
|
|
3272
|
+
if (!input.music_video_id) return { kind: "text", text: `${input.action} needs music_video_id (from create).` };
|
|
3273
|
+
const id = input.music_video_id;
|
|
3274
|
+
const res = input.action === "status" ? await api(ctx, "GET", `/${id}`) : input.action === "produce" ? await api(ctx, "POST", `/${id}`, { action: "produce" }) : input.beats?.length ? await api(ctx, "POST", `/${id}`, { action: "redo", beats: input.beats, ...input.notes?.length ? { notes: input.notes } : {} }) : null;
|
|
3275
|
+
if (!res) return { kind: "text", text: "redo needs beats (the section numbers to film again)." };
|
|
3276
|
+
if (!res.data.id) return { kind: "text", text: errorText2(res.data, res.status) };
|
|
3277
|
+
return describe(res.data);
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
});
|
|
3282
|
+
async function kataApi(ctx, method, path, body) {
|
|
3283
|
+
const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/kata${path}`, {
|
|
3284
|
+
method,
|
|
3285
|
+
headers: {
|
|
3286
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
3287
|
+
"Content-Type": "application/json",
|
|
3288
|
+
"x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
|
|
3289
|
+
"x-pixeldojo-skill": "kata"
|
|
3290
|
+
},
|
|
3291
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
3292
|
+
});
|
|
3293
|
+
const data = await res.json().catch(() => ({}));
|
|
3294
|
+
return { status: res.status, data };
|
|
3295
|
+
}
|
|
3296
|
+
function describeKata(v) {
|
|
3297
|
+
const next2 = (action) => `pixeldojo_kata with {"kata_id": "${v.id}", "action": "${action}"}`;
|
|
3298
|
+
if (v.status === "failed") return { kind: "text", text: `The Kata stopped: ${v.error ?? "unknown error"}. Anything that failed was refunded.` };
|
|
3299
|
+
if (v.status === "quote") {
|
|
3300
|
+
const scenes = v.scenes.map((s, i) => `${i + 1}. ${KIND[s.kind] ?? s.kind}${s.cast.length ? ` with ${s.cast.join(" and ")}` : ""}${s.prompt ? `: ${s.prompt.slice(0, 140)}` : ""}`);
|
|
3301
|
+
const words = v.shots.filter((s) => s.text).map((s) => `${s.from.toFixed(1)}s "${s.text}"`);
|
|
3302
|
+
return {
|
|
3303
|
+
kind: "text",
|
|
3304
|
+
text: [
|
|
3305
|
+
`Planned "${v.title}" (kata_id: ${v.id}): ${v.duration_sec}s, ${v.format}, ${v.shots.length} cuts kept to the frame, ${v.scenes.length} setups:`,
|
|
3306
|
+
...scenes,
|
|
3307
|
+
words.length ? `On screen: ${words.join(", ")}.` : "",
|
|
3308
|
+
`Price: about ${v.estimate_credits} credits (${(v.quote ?? []).map((l) => `${l.label} ${l.credits}`).join(", ")}); up to ${v.max_credits_with_retakes} if bad takes are made again. Nothing has been charged yet. Show this to the user and, once they agree, call ${next2("produce")}. (In Sensei: put it in pixeldojo_plan as ONE step with skill "kata" and params {"kata_id": "${v.id}"}, which prices it from this quote.)`
|
|
3309
|
+
].filter(Boolean).join("\n")
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
if (v.status === "done" && v.final_url) {
|
|
3313
|
+
return { kind: "assets", text: `"${v.title}" is finished and saved to the Library: ${v.final_url}`, assetUrls: [v.final_url] };
|
|
3314
|
+
}
|
|
3315
|
+
return {
|
|
3316
|
+
kind: "text",
|
|
3317
|
+
text: `Still ${STEP2[v.status] ?? v.status}. It takes about 5 to 15 minutes in all. Call ${next2("status")} in about a minute.`,
|
|
3318
|
+
followUp: { input: { action: "status", kata_id: v.id }, afterSec: v.status === "analyzing" || v.status === "scripting" ? 20 : 45 }
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
async function waitForPlan3(ctx, id) {
|
|
3322
|
+
const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
3323
|
+
let last = await kataApi(ctx, "GET", `/${id}`);
|
|
3324
|
+
for (let i = 0; i < 10 && (last.data.status === "analyzing" || last.data.status === "scripting"); i += 1) {
|
|
3325
|
+
await sleep(4e3);
|
|
3326
|
+
last = await kataApi(ctx, "GET", `/${id}`);
|
|
3327
|
+
}
|
|
3328
|
+
return last;
|
|
3329
|
+
}
|
|
3330
|
+
var InputSchema15, InputJsonSchema15, errorText3, STEP2, KIND, kataSkill;
|
|
3331
|
+
var init_kata = __esm({
|
|
3332
|
+
"src/skills/kata.ts"() {
|
|
3333
|
+
InputSchema15 = z.object({
|
|
3334
|
+
action: z.enum(["create", "produce", "status", "presets"]).default("create"),
|
|
3335
|
+
reference_url: z.string().url().optional(),
|
|
3336
|
+
preset: z.string().max(80).optional(),
|
|
3337
|
+
cast: z.array(z.object({ name: z.string().min(1).max(40), image_url: z.string().url().optional(), description: z.string().max(200).optional() })).max(3).optional(),
|
|
3338
|
+
photos: z.array(z.object({ url: z.string().url(), note: z.string().max(80).optional() })).max(4).optional(),
|
|
3339
|
+
instructions: z.string().max(1500).optional(),
|
|
3340
|
+
look: z.string().max(80).optional(),
|
|
3341
|
+
kata_id: z.string().max(60).optional()
|
|
3342
|
+
});
|
|
3343
|
+
InputJsonSchema15 = {
|
|
3344
|
+
type: "object",
|
|
3345
|
+
properties: {
|
|
3346
|
+
action: {
|
|
3347
|
+
type: "string",
|
|
3348
|
+
enum: ["create", "produce", "status", "presets"],
|
|
3349
|
+
description: "create (default, free): reads the reference edit and plans the remake; returns kata_id and the price. Show the plan and price and wait for the user's OK. produce (charged): makes it. status (free): progress; call every ~60s until it returns the edit. presets (free): the Kata library, ready-made edits to recreate."
|
|
3350
|
+
},
|
|
3351
|
+
reference_url: { type: "string", description: "Create: the 4 to 60 second edit to recreate: an upload on the PixelDojo CDN or a direct link to the video file. TikTok, Instagram, YouTube and X posts cannot be fetched: ask the user to save the video from the app and upload it. Or give preset instead." },
|
|
3352
|
+
preset: { type: "string", description: 'Create: a Kata library preset slug (from action "presets") instead of a reference.' },
|
|
3353
|
+
cast: {
|
|
3354
|
+
type: "array",
|
|
3355
|
+
items: {
|
|
3356
|
+
type: "object",
|
|
3357
|
+
properties: {
|
|
3358
|
+
name: { type: "string", description: "Their name. Without image_url, the saved Character Studio character of that name." },
|
|
3359
|
+
image_url: { type: "string", description: "A clear photo of them on the PixelDojo CDN." },
|
|
3360
|
+
description: { type: "string", description: "Optional: who they are, in a few words." }
|
|
3361
|
+
},
|
|
3362
|
+
required: ["name"]
|
|
3363
|
+
},
|
|
3364
|
+
description: 'Create: up to 3 people who replace the people in the edit. Use saved characters by name (pixeldojo_library source "characters").'
|
|
3365
|
+
},
|
|
3366
|
+
photos: {
|
|
3367
|
+
type: "array",
|
|
3368
|
+
items: { type: "object", properties: { url: { type: "string", description: "Public URL of the photo." }, note: { type: "string", description: 'What it is (e.g. "the product").' } }, required: ["url"] },
|
|
3369
|
+
description: "Create: up to 4 products or photos to put in the edit, on the PixelDojo CDN."
|
|
3370
|
+
},
|
|
3371
|
+
instructions: { type: "string", description: "Create (optional): what to change, e.g. the setting, the words on screen, where the product goes." },
|
|
3372
|
+
look: { type: "string", description: 'Create (optional): style transfer, one look for every new shot, e.g. "hand-drawn anime".' },
|
|
3373
|
+
kata_id: { type: "string", description: "From create. Required for produce and status." }
|
|
3374
|
+
}
|
|
3375
|
+
};
|
|
3376
|
+
errorText3 = (d, status2) => d.message ?? (typeof d.error === "string" ? d.error : d.error?.message) ?? `HTTP ${status2}`;
|
|
3377
|
+
STEP2 = {
|
|
3378
|
+
analyzing: "reading the edit cut by cut",
|
|
3379
|
+
scripting: "planning the remake",
|
|
3380
|
+
music: "scoring a new track on the same tempo",
|
|
3381
|
+
beats: "finding the beat",
|
|
3382
|
+
keyframes: "making the keyframes",
|
|
3383
|
+
animating: "filming the shots",
|
|
3384
|
+
cutting: "cutting it where the reference cuts",
|
|
3385
|
+
rendering: "setting the words and transitions",
|
|
3386
|
+
reviewing: "checking the final cut"
|
|
3387
|
+
};
|
|
3388
|
+
KIND = { clip: "new shot", still: "still", swap: "motion transfer", photo: "your photo" };
|
|
3389
|
+
kataSkill = {
|
|
3390
|
+
name: "pixeldojo_kata",
|
|
3391
|
+
description: `Kata: recreate a reference edit (a viral short, an ad, a montage, a trailer, 4 to 60 s) with the user's own characters, products and photos. It keeps the same pacing and cut points to the frame, the transitions, the camera moves, the caption style and the music feel (an original score on the same tempo, never the reference's audio); dance moves come across by motion transfer, and a look can restyle every shot. Start from a reference_url or a Kata library preset (action "presets"). create is free and returns the plan and the price; produce makes it; then call status until it is finished. Saved to the Library.`,
|
|
3392
|
+
inputZod: InputSchema15,
|
|
3393
|
+
inputJsonSchema: InputJsonSchema15,
|
|
3394
|
+
allowedModels: ["seedance-2-5", "google-nano-banana", "kling-motion-control"],
|
|
3395
|
+
defaultModel: "seedance-2-5",
|
|
3396
|
+
async run(input, ctx) {
|
|
3397
|
+
if (input.action === "presets") {
|
|
3398
|
+
const res2 = await kataApi(ctx, "GET", "/presets");
|
|
3399
|
+
const list = res2.data.presets ?? [];
|
|
3400
|
+
if (!list.length) return { kind: "text", text: "The Kata library is empty right now. Recreate any edit with a reference_url." };
|
|
3401
|
+
return {
|
|
3402
|
+
kind: "text",
|
|
3403
|
+
text: ["Kata library (pass the slug as preset):", ...list.map((p) => `- ${p.slug}: ${p.title} (${p.category}, ${Math.round(p.durationSec)}s, ${p.shots} cuts). ${p.blurb}`)].join("\n")
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
if (input.action === "create") {
|
|
3407
|
+
if (!input.reference_url && !input.preset) return { kind: "text", text: 'Give a reference_url (the edit to recreate, on the PixelDojo CDN) or a preset from action "presets".' };
|
|
3408
|
+
const created = await kataApi(ctx, "POST", "", {
|
|
3409
|
+
...input.preset ? { preset: input.preset } : { reference_url: input.reference_url },
|
|
3410
|
+
...input.cast?.length ? { cast: input.cast } : {},
|
|
3411
|
+
...input.photos?.length ? { photos: input.photos } : {},
|
|
3412
|
+
...input.instructions ? { instructions: input.instructions } : {},
|
|
3413
|
+
...input.look ? { look: input.look } : {}
|
|
3414
|
+
});
|
|
3415
|
+
if (!created.data.id) return { kind: "text", text: `Could not start the Kata: ${errorText3(created.data, created.status)}` };
|
|
3416
|
+
const planned = await waitForPlan3(ctx, created.data.id);
|
|
3417
|
+
return describeKata(planned.data);
|
|
3418
|
+
}
|
|
3419
|
+
if (!input.kata_id) return { kind: "text", text: `${input.action} needs kata_id (from create).` };
|
|
3420
|
+
const res = input.action === "status" ? await kataApi(ctx, "GET", `/${input.kata_id}`) : await kataApi(ctx, "POST", `/${input.kata_id}`, { action: "produce" });
|
|
3421
|
+
if (!res.data.id) return { kind: "text", text: errorText3(res.data, res.status) };
|
|
3422
|
+
return describeKata(res.data);
|
|
3423
|
+
}
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
});
|
|
2920
3427
|
async function run13(input, ctx) {
|
|
2921
3428
|
const productImages = [
|
|
2922
3429
|
...input.product_image_urls ?? [],
|
|
@@ -2941,7 +3448,9 @@ async function run13(input, ctx) {
|
|
|
2941
3448
|
quality: input.quality,
|
|
2942
3449
|
resolution: input.resolution,
|
|
2943
3450
|
aspect_ratio: input.aspect_ratio,
|
|
2944
|
-
duration: input.duration
|
|
3451
|
+
duration: input.duration,
|
|
3452
|
+
reference_video_url: input.reference_video_url,
|
|
3453
|
+
brand_kit: input.brand_kit
|
|
2945
3454
|
};
|
|
2946
3455
|
const client = ctx.client;
|
|
2947
3456
|
const submitted = await client.runAd(body);
|
|
@@ -2957,16 +3466,16 @@ async function run13(input, ctx) {
|
|
|
2957
3466
|
const styleLabel = submitted.ad?.style ?? input.style ?? "ugc-testimonial";
|
|
2958
3467
|
return pollResultToSkillResult(
|
|
2959
3468
|
result,
|
|
2960
|
-
(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]}
|
|
3469
|
+
(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]} Add the brand end card (logo, tagline, button) with pixeldojo_marketing action end_card and this video_url.`
|
|
2961
3470
|
);
|
|
2962
3471
|
}
|
|
2963
|
-
var
|
|
3472
|
+
var InputSchema16, InputJsonSchema16, adSkill;
|
|
2964
3473
|
var init_ad = __esm({
|
|
2965
3474
|
"src/skills/ad.ts"() {
|
|
2966
3475
|
init_types();
|
|
2967
3476
|
init_poll();
|
|
2968
3477
|
init_upload_helpers();
|
|
2969
|
-
|
|
3478
|
+
InputSchema16 = z.object({
|
|
2970
3479
|
style: z.string().max(80).optional().describe(
|
|
2971
3480
|
"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."
|
|
2972
3481
|
),
|
|
@@ -2984,7 +3493,9 @@ var init_ad = __esm({
|
|
|
2984
3493
|
quality: z.enum(["mini", "fast", "high"]).optional(),
|
|
2985
3494
|
resolution: z.enum(["480p", "720p", "1080p", "4k"]).optional().describe('1080p/4k require quality "high".'),
|
|
2986
3495
|
aspect_ratio: z.enum(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]).optional(),
|
|
2987
|
-
duration: z.number().int().min(4).max(15).optional()
|
|
3496
|
+
duration: z.number().int().min(4).max(15).optional(),
|
|
3497
|
+
reference_video_url: z.string().url().optional(),
|
|
3498
|
+
brand_kit: z.boolean().optional()
|
|
2988
3499
|
}).refine(
|
|
2989
3500
|
(v) => v.product_url || (v.product_image_urls?.length || v.product_image_paths?.length) && v.product_name,
|
|
2990
3501
|
{
|
|
@@ -2992,7 +3503,7 @@ var init_ad = __esm({
|
|
|
2992
3503
|
path: ["product_url"]
|
|
2993
3504
|
}
|
|
2994
3505
|
);
|
|
2995
|
-
|
|
3506
|
+
InputJsonSchema16 = {
|
|
2996
3507
|
type: "object",
|
|
2997
3508
|
properties: {
|
|
2998
3509
|
style: {
|
|
@@ -3070,6 +3581,15 @@ var init_ad = __esm({
|
|
|
3070
3581
|
minimum: 4,
|
|
3071
3582
|
maximum: 15,
|
|
3072
3583
|
description: "Ad length in seconds. Defaults to the style's length; clamped to what the engine allows."
|
|
3584
|
+
},
|
|
3585
|
+
reference_video_url: {
|
|
3586
|
+
type: "string",
|
|
3587
|
+
format: "uri",
|
|
3588
|
+
description: "Ad multiplier: an ad that already works (https mp4, up to 30 s). The new ad copies its hook, pacing and camera moves with this product and presenter, never its product, people or text."
|
|
3589
|
+
},
|
|
3590
|
+
brand_kit: {
|
|
3591
|
+
type: "boolean",
|
|
3592
|
+
description: "The account's brand kit (palette, voice, selling points; see pixeldojo_brand_kit) rides on every ad. false skips it."
|
|
3073
3593
|
}
|
|
3074
3594
|
}
|
|
3075
3595
|
// "product_url OR product_name+images" is enforced by the server and the
|
|
@@ -3079,8 +3599,8 @@ var init_ad = __esm({
|
|
|
3079
3599
|
name: "pixeldojo_ad",
|
|
3080
3600
|
description: "Marketing Studio: turn a product into a rendered video ad with voiceover. Requires `product_url` (name, description and images are extracted), or `product_name` plus product_image_urls / product_image_paths. Pick a `style` slug (default ugc-testimonial; full catalog at GET https://pixeldojo.ai/api/v1/marketing/presets), and optionally add presenter reference images, a location, a hook, and the exact spoken script. Returns the video URL, or a jobId to check with pixeldojo_status.",
|
|
3081
3601
|
hostedDescription: "Marketing Studio: turn a product into a rendered video ad with voiceover. Requires `product_url` (name, description and images are extracted), or `product_name` plus product_image_urls (public http(s) URLs). Pick a `style` slug (default ugc-testimonial; full catalog at GET https://pixeldojo.ai/api/v1/marketing/presets), and optionally add presenter reference images, a location, a hook, and the exact spoken script. Returns the video URL, or a jobId to check with pixeldojo_status.",
|
|
3082
|
-
inputZod:
|
|
3083
|
-
inputJsonSchema:
|
|
3602
|
+
inputZod: InputSchema16,
|
|
3603
|
+
inputJsonSchema: InputJsonSchema16,
|
|
3084
3604
|
// Composition skill — the server routes to the ad engine internally.
|
|
3085
3605
|
allowedModels: [],
|
|
3086
3606
|
defaultModel: "",
|
|
@@ -3115,10 +3635,10 @@ async function run14(input, ctx) {
|
|
|
3115
3635
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
3116
3636
|
};
|
|
3117
3637
|
}
|
|
3118
|
-
var
|
|
3638
|
+
var InputSchema17, InputJsonSchema17, campaignSkill;
|
|
3119
3639
|
var init_campaign = __esm({
|
|
3120
3640
|
"src/skills/campaign.ts"() {
|
|
3121
|
-
|
|
3641
|
+
InputSchema17 = z.object({
|
|
3122
3642
|
productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
|
|
3123
3643
|
product: z.object({
|
|
3124
3644
|
name: z.string().min(1).max(200),
|
|
@@ -3134,7 +3654,7 @@ var init_campaign = __esm({
|
|
|
3134
3654
|
message: "productUrl or product is required",
|
|
3135
3655
|
path: ["productUrl"]
|
|
3136
3656
|
});
|
|
3137
|
-
|
|
3657
|
+
InputJsonSchema17 = {
|
|
3138
3658
|
type: "object",
|
|
3139
3659
|
properties: {
|
|
3140
3660
|
productUrl: {
|
|
@@ -3185,8 +3705,8 @@ var init_campaign = __esm({
|
|
|
3185
3705
|
campaignSkill = {
|
|
3186
3706
|
name: "pixeldojo_campaign",
|
|
3187
3707
|
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.",
|
|
3188
|
-
inputZod:
|
|
3189
|
-
inputJsonSchema:
|
|
3708
|
+
inputZod: InputSchema17,
|
|
3709
|
+
inputJsonSchema: InputJsonSchema17,
|
|
3190
3710
|
// Composition skill — internal sub-jobs route to per-tool models, no
|
|
3191
3711
|
// single allowed-models list applies here.
|
|
3192
3712
|
allowedModels: [],
|
|
@@ -3225,13 +3745,13 @@ async function run15(input, ctx) {
|
|
|
3225
3745
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
3226
3746
|
};
|
|
3227
3747
|
}
|
|
3228
|
-
var
|
|
3748
|
+
var InputSchema18, InputJsonSchema18, campaignStatusSkill;
|
|
3229
3749
|
var init_campaign_status = __esm({
|
|
3230
3750
|
"src/skills/campaign-status.ts"() {
|
|
3231
|
-
|
|
3751
|
+
InputSchema18 = z.object({
|
|
3232
3752
|
campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo_campaign handoff.")
|
|
3233
3753
|
});
|
|
3234
|
-
|
|
3754
|
+
InputJsonSchema18 = {
|
|
3235
3755
|
type: "object",
|
|
3236
3756
|
properties: {
|
|
3237
3757
|
campaignId: {
|
|
@@ -3244,8 +3764,8 @@ var init_campaign_status = __esm({
|
|
|
3244
3764
|
campaignStatusSkill = {
|
|
3245
3765
|
name: "pixeldojo_campaign_status",
|
|
3246
3766
|
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.",
|
|
3247
|
-
inputZod:
|
|
3248
|
-
inputJsonSchema:
|
|
3767
|
+
inputZod: InputSchema18,
|
|
3768
|
+
inputJsonSchema: InputJsonSchema18,
|
|
3249
3769
|
allowedModels: [],
|
|
3250
3770
|
defaultModel: "",
|
|
3251
3771
|
run: run15
|
|
@@ -3268,16 +3788,16 @@ async function run16(input, ctx) {
|
|
|
3268
3788
|
(temp storage; auto-deletes in ~24h. Pass this URL as image_url, video_url for a clip, or song_url on pixeldojo_film for a song, to any pixeldojo tool that takes one.)`
|
|
3269
3789
|
};
|
|
3270
3790
|
}
|
|
3271
|
-
var
|
|
3791
|
+
var InputSchema19, InputJsonSchema19, uploadSkill;
|
|
3272
3792
|
var init_upload = __esm({
|
|
3273
3793
|
"src/skills/upload.ts"() {
|
|
3274
3794
|
init_upload_helpers();
|
|
3275
|
-
|
|
3795
|
+
InputSchema19 = z.object({
|
|
3276
3796
|
path: z.string().min(1).max(4096).describe(
|
|
3277
3797
|
"Absolute local file path to upload. JPEG, PNG, WebP, GIF (\u226450MB), MP4, WebM, MOV (\u2264200MB) or MP3, WAV, M4A, AAC, OGG, FLAC audio (\u226450MB). Returns a public URL on the temp CDN; storage auto-expires after 24 hours."
|
|
3278
3798
|
)
|
|
3279
3799
|
});
|
|
3280
|
-
|
|
3800
|
+
InputJsonSchema19 = {
|
|
3281
3801
|
type: "object",
|
|
3282
3802
|
properties: {
|
|
3283
3803
|
path: {
|
|
@@ -3290,8 +3810,8 @@ var init_upload = __esm({
|
|
|
3290
3810
|
uploadSkill = {
|
|
3291
3811
|
name: "pixeldojo_upload",
|
|
3292
3812
|
description: "Upload a local image, video or audio file (JPEG, PNG, WebP, GIF up to 50MB; MP4, WebM, MOV up to 200MB; MP3, WAV, M4A, AAC, OGG, FLAC up to 50MB) and get back a public URL that expires after 24 hours. Free. Pass an audio URL as song_url to pixeldojo_film for a music video. Pass the URL as image_url (stills) or video_url (clips) to tools that take hosted URLs only, such as pixeldojo_character, pixeldojo_storyboard and pixeldojo_upscale. pixeldojo_generate, pixeldojo_edit and pixeldojo_ad take local paths directly and need no separate upload.",
|
|
3293
|
-
inputZod:
|
|
3294
|
-
inputJsonSchema:
|
|
3813
|
+
inputZod: InputSchema19,
|
|
3814
|
+
inputJsonSchema: InputJsonSchema19,
|
|
3295
3815
|
allowedModels: [],
|
|
3296
3816
|
defaultModel: "",
|
|
3297
3817
|
run: run16
|
|
@@ -3336,14 +3856,14 @@ async function run17(input, ctx) {
|
|
|
3336
3856
|
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
|
|
3337
3857
|
);
|
|
3338
3858
|
}
|
|
3339
|
-
var ALLOWED4, DEFAULT_MODEL6,
|
|
3859
|
+
var ALLOWED4, DEFAULT_MODEL6, InputSchema20, InputJsonSchema20, audioSkill;
|
|
3340
3860
|
var init_audio = __esm({
|
|
3341
3861
|
"src/skills/audio.ts"() {
|
|
3342
3862
|
init_poll();
|
|
3343
3863
|
init_types();
|
|
3344
3864
|
ALLOWED4 = ["seed-audio", "text-to-speech"];
|
|
3345
3865
|
DEFAULT_MODEL6 = "seed-audio";
|
|
3346
|
-
|
|
3866
|
+
InputSchema20 = z.object({
|
|
3347
3867
|
prompt: z.string().min(1).describe(
|
|
3348
3868
|
"seed-audio: a script to speak, or a scene description (dialogue, sound effects, music), up to 2048 characters; refer to audio_urls clips in order as @Audio1..@Audio3. text-to-speech: the exact text to speak, up to 10,000 characters."
|
|
3349
3869
|
),
|
|
@@ -3353,7 +3873,7 @@ var init_audio = __esm({
|
|
|
3353
3873
|
output_format: z.string().optional().describe("Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."),
|
|
3354
3874
|
speed: z.number().min(0.5).max(2).optional().describe("Speech speed multiplier, 0.5-2 (default 1). Both models.")
|
|
3355
3875
|
});
|
|
3356
|
-
|
|
3876
|
+
InputJsonSchema20 = {
|
|
3357
3877
|
type: "object",
|
|
3358
3878
|
properties: {
|
|
3359
3879
|
prompt: {
|
|
@@ -3391,8 +3911,8 @@ var init_audio = __esm({
|
|
|
3391
3911
|
audioSkill = {
|
|
3392
3912
|
name: "pixeldojo_audio",
|
|
3393
3913
|
description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain text-to-speech available). Voice cloning takes hosted reference clip URLs in audio_urls; local audio files are not accepted. Returns an audio URL, or a jobId for pixeldojo_status.",
|
|
3394
|
-
inputZod:
|
|
3395
|
-
inputJsonSchema:
|
|
3914
|
+
inputZod: InputSchema20,
|
|
3915
|
+
inputJsonSchema: InputJsonSchema20,
|
|
3396
3916
|
allowedModels: [...ALLOWED4],
|
|
3397
3917
|
defaultModel: DEFAULT_MODEL6,
|
|
3398
3918
|
run: run17
|
|
@@ -3450,17 +3970,17 @@ ${lines.join("\n")}
|
|
|
3450
3970
|
Pass any URL back into pixeldojo_generate, pixeldojo_edit, pixeldojo_character, or pixeldojo_upscale as a reference input.`
|
|
3451
3971
|
};
|
|
3452
3972
|
}
|
|
3453
|
-
var
|
|
3973
|
+
var InputSchema21, InputJsonSchema21, librarySkill;
|
|
3454
3974
|
var init_library = __esm({
|
|
3455
3975
|
"src/skills/library.ts"() {
|
|
3456
|
-
|
|
3976
|
+
InputSchema21 = z.object({
|
|
3457
3977
|
source: z.enum(["media", "characters"]).optional().describe("media (default): saved and recent results. characters: saved Character Studio characters and elements, usable as @Name in prompts."),
|
|
3458
3978
|
q: z.string().optional().describe("Substring match against the prompt that produced the asset."),
|
|
3459
3979
|
model: z.string().optional().describe("Restrict to a single model (apiId)."),
|
|
3460
3980
|
modality: z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
|
|
3461
3981
|
limit: z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
|
|
3462
3982
|
});
|
|
3463
|
-
|
|
3983
|
+
InputJsonSchema21 = {
|
|
3464
3984
|
type: "object",
|
|
3465
3985
|
properties: {
|
|
3466
3986
|
source: {
|
|
@@ -3485,8 +4005,8 @@ var init_library = __esm({
|
|
|
3485
4005
|
librarySkill = {
|
|
3486
4006
|
name: "pixeldojo_library",
|
|
3487
4007
|
description: "Search your Library, newest first. source=media (default): saved media (permanent) and recent API/MCP generations (deleted 1 hour after they finish), as URLs to reuse as references. source=characters: saved Character Studio characters and elements, each usable as @Name in any prompt.",
|
|
3488
|
-
inputZod:
|
|
3489
|
-
inputJsonSchema:
|
|
4008
|
+
inputZod: InputSchema21,
|
|
4009
|
+
inputJsonSchema: InputJsonSchema21,
|
|
3490
4010
|
allowedModels: [],
|
|
3491
4011
|
defaultModel: "",
|
|
3492
4012
|
run: run18
|
|
@@ -3507,17 +4027,17 @@ async function run19(input, ctx) {
|
|
|
3507
4027
|
It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
|
|
3508
4028
|
};
|
|
3509
4029
|
}
|
|
3510
|
-
var
|
|
4030
|
+
var InputSchema22, InputJsonSchema22, saveSkill;
|
|
3511
4031
|
var init_save = __esm({
|
|
3512
4032
|
"src/skills/save.ts"() {
|
|
3513
|
-
|
|
4033
|
+
InputSchema22 = z.object({
|
|
3514
4034
|
url: z.string().url().describe("The asset URL to save (image or video)."),
|
|
3515
4035
|
prompt: z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
|
|
3516
4036
|
tool_name: z.string().optional().describe("Tool/model slug that produced it."),
|
|
3517
4037
|
modality: z.enum(["image", "video", "audio"]).optional(),
|
|
3518
4038
|
job_id: z.string().optional().describe("The jobId that produced this asset, when known.")
|
|
3519
4039
|
});
|
|
3520
|
-
|
|
4040
|
+
InputJsonSchema22 = {
|
|
3521
4041
|
type: "object",
|
|
3522
4042
|
properties: {
|
|
3523
4043
|
url: { type: "string", format: "uri", description: "The result URL to keep (image or video from a PixelDojo job)." },
|
|
@@ -3531,8 +4051,8 @@ var init_save = __esm({
|
|
|
3531
4051
|
saveSkill = {
|
|
3532
4052
|
name: "pixeldojo_save",
|
|
3533
4053
|
description: `Save a generated image or video into the user's My Media library permanently ("save this", "keep it"). Results otherwise expire 1 hour after they finish. Pass the result URL; it is safety-checked before it is kept.`,
|
|
3534
|
-
inputZod:
|
|
3535
|
-
inputJsonSchema:
|
|
4054
|
+
inputZod: InputSchema22,
|
|
4055
|
+
inputJsonSchema: InputJsonSchema22,
|
|
3536
4056
|
allowedModels: [],
|
|
3537
4057
|
defaultModel: "",
|
|
3538
4058
|
run: run19
|
|
@@ -3551,21 +4071,21 @@ async function run20(input, ctx) {
|
|
|
3551
4071
|
text: `Saved ${res.kind} "${res.name}" to Character Studio (${res.image_urls.length} image${res.image_urls.length === 1 ? "" : "s"}). Use it in any prompt as ${res.mention}, for example "${res.mention} walking on a beach at sunset". It also appears at https://pixeldojo.ai/characters.`
|
|
3552
4072
|
};
|
|
3553
4073
|
}
|
|
3554
|
-
var
|
|
4074
|
+
var KINDS2, InputSchema23, InputJsonSchema23, saveCharacterSkill;
|
|
3555
4075
|
var init_save_character = __esm({
|
|
3556
4076
|
"src/skills/save-character.ts"() {
|
|
3557
|
-
|
|
3558
|
-
|
|
4077
|
+
KINDS2 = ["character", "product", "outfit", "location", "object", "style"];
|
|
4078
|
+
InputSchema23 = z.object({
|
|
3559
4079
|
name: z.string().min(1).max(60).describe("What to call it. Used as @Name in prompts."),
|
|
3560
|
-
kind: z.enum(
|
|
4080
|
+
kind: z.enum(KINDS2).optional().describe("Default character."),
|
|
3561
4081
|
image_urls: z.array(z.string().url()).max(8).optional().describe("3 to 5 photos from different angles works best for a character."),
|
|
3562
4082
|
description: z.string().max(1500).optional().describe("Appearance notes, or the scene text for an element.")
|
|
3563
4083
|
});
|
|
3564
|
-
|
|
4084
|
+
InputJsonSchema23 = {
|
|
3565
4085
|
type: "object",
|
|
3566
4086
|
properties: {
|
|
3567
4087
|
name: { type: "string", description: "What to call it. Used as @Name in prompts afterwards." },
|
|
3568
|
-
kind: { type: "string", enum:
|
|
4088
|
+
kind: { type: "string", enum: KINDS2, description: "character (default), product, outfit, location, object (a prop) or style." },
|
|
3569
4089
|
image_urls: {
|
|
3570
4090
|
type: "array",
|
|
3571
4091
|
items: { type: "string", format: "uri" },
|
|
@@ -3579,8 +4099,8 @@ var init_save_character = __esm({
|
|
|
3579
4099
|
saveCharacterSkill = {
|
|
3580
4100
|
name: "pixeldojo_save_character",
|
|
3581
4101
|
description: "Save a character (a person, from 3-5 photos) or an element (product, outfit, location, prop, style) to the user's Character Studio library. Afterwards write @Name in any prompt to use it. List saved ones with pixeldojo_library source=characters.",
|
|
3582
|
-
inputZod:
|
|
3583
|
-
inputJsonSchema:
|
|
4102
|
+
inputZod: InputSchema23,
|
|
4103
|
+
inputJsonSchema: InputJsonSchema23,
|
|
3584
4104
|
allowedModels: [],
|
|
3585
4105
|
defaultModel: "",
|
|
3586
4106
|
run: run20
|
|
@@ -3605,7 +4125,7 @@ async function listRun(input, ctx) {
|
|
|
3605
4125
|
}
|
|
3606
4126
|
const lines = effects.map((e) => {
|
|
3607
4127
|
const ins = e.inputs.map(
|
|
3608
|
-
(i) => i.kind === "image" ? `photo "${i.label}"` : `text "${i.label}"${i.options ? ` [${i.options.join(" | ")}]` : ""}${i.optional ? " (optional)" : ""}`
|
|
4128
|
+
(i) => i.kind === "image" ? `photo "${i.label}"${i.optional ? " (optional)" : ""}` : `text "${i.label}"${i.options ? ` [${i.options.join(" | ")}]` : ""}${i.optional ? " (optional)" : ""}`
|
|
3609
4129
|
);
|
|
3610
4130
|
return `- ${e.slug} (${e.family}, ${e.output}, ${priceOf(e)}): ${e.title}. ${e.tagline} Inputs: ${ins.join(", ")}.`;
|
|
3611
4131
|
});
|
|
@@ -3729,6 +4249,446 @@ var init_effects = __esm({
|
|
|
3729
4249
|
};
|
|
3730
4250
|
}
|
|
3731
4251
|
});
|
|
4252
|
+
async function call2(ctx, skill, path, init = {}) {
|
|
4253
|
+
const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}${path}`, {
|
|
4254
|
+
method: init.method ?? "GET",
|
|
4255
|
+
headers: headers2(ctx, skill),
|
|
4256
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {}
|
|
4257
|
+
});
|
|
4258
|
+
const data = await res.json().catch(() => ({}));
|
|
4259
|
+
const err = data.error;
|
|
4260
|
+
return { ok: res.ok, data, error: typeof err === "string" ? err : err?.message ?? `HTTP ${res.status}` };
|
|
4261
|
+
}
|
|
4262
|
+
async function kitRun(input, ctx) {
|
|
4263
|
+
const path = "/api/v1/marketing/brand-kit";
|
|
4264
|
+
if (input.action === "import") {
|
|
4265
|
+
if (!input.url) return { kind: "text", text: "Pass url: the brand's website, like yourbrand.com." };
|
|
4266
|
+
const r2 = await call2(ctx, "pixeldojo_brand_kit", path, { method: "POST", body: { url: input.url, save: input.save } });
|
|
4267
|
+
if (!r2.ok) return { kind: "text", text: `Brand kit not imported: ${r2.error}` };
|
|
4268
|
+
return { kind: "text", text: `${r2.data.saved === false ? "Imported (not saved)." : "Imported and saved."}
|
|
4269
|
+
${describeKit(r2.data.brand_kit)}` };
|
|
4270
|
+
}
|
|
4271
|
+
if (input.action === "update") {
|
|
4272
|
+
const patch = {
|
|
4273
|
+
...input.name !== void 0 ? { name: input.name } : {},
|
|
4274
|
+
...input.tagline !== void 0 ? { tagline: input.tagline } : {},
|
|
4275
|
+
...input.voice !== void 0 ? { voice: input.voice } : {},
|
|
4276
|
+
...input.colors ? { colors: input.colors.map((c) => c.trim().toLowerCase()) } : {},
|
|
4277
|
+
...input.fonts ? { fonts: input.fonts } : {},
|
|
4278
|
+
...input.selling_points ? { sellingPoints: input.selling_points } : {},
|
|
4279
|
+
...input.logo_url ? { logoUrl: input.logo_url } : {}
|
|
4280
|
+
};
|
|
4281
|
+
const r2 = await call2(ctx, "pixeldojo_brand_kit", path, { method: "PATCH", body: patch });
|
|
4282
|
+
if (!r2.ok) return { kind: "text", text: `Brand kit not updated: ${r2.error}` };
|
|
4283
|
+
return { kind: "text", text: `Updated.
|
|
4284
|
+
${describeKit(r2.data.brand_kit)}` };
|
|
4285
|
+
}
|
|
4286
|
+
const r = await call2(ctx, "pixeldojo_brand_kit", path);
|
|
4287
|
+
if (!r.ok) return { kind: "text", text: `Could not read the brand kit: ${r.error}` };
|
|
4288
|
+
return { kind: "text", text: describeKit(r.data.brand_kit ?? null) };
|
|
4289
|
+
}
|
|
4290
|
+
async function templatesRun(input, ctx) {
|
|
4291
|
+
const q = new URLSearchParams();
|
|
4292
|
+
if (input.kind) q.set("kind", input.kind);
|
|
4293
|
+
if (input.category) q.set("category", input.category);
|
|
4294
|
+
const r = await call2(ctx, "pixeldojo_marketing", `/api/v1/marketing/templates?${q}`);
|
|
4295
|
+
if (!r.ok) return { kind: "text", text: `Could not list templates: ${r.error}` };
|
|
4296
|
+
const rows = r.data.templates ?? [];
|
|
4297
|
+
const lines = rows.map((t) => {
|
|
4298
|
+
const how = t.kind === "image" ? "action image" : t.kind === "motion" ? `action motion, fields ${t.fields?.join(", ")}, ${t.credits} credits` : "pixeldojo_ad style";
|
|
4299
|
+
return `- ${t.id} (${t.label}, ${t.category}): ${t.name}. ${how}. Example: ${t.preview ?? "none"}`;
|
|
4300
|
+
});
|
|
4301
|
+
const engines = r.data.image_engines ?? [];
|
|
4302
|
+
return {
|
|
4303
|
+
kind: "text",
|
|
4304
|
+
text: [
|
|
4305
|
+
`${rows.length} templates. Image templates run with action image (template = id), motion templates with action motion, video ad styles with pixeldojo_ad (style = id).`,
|
|
4306
|
+
...lines,
|
|
4307
|
+
"",
|
|
4308
|
+
`Image engines: ${engines.map((e) => `${e.id} ${e.credits_per_image} credits per image (${e.note})`).join("; ")}.`,
|
|
4309
|
+
...r.data.notes ?? []
|
|
4310
|
+
].join("\n")
|
|
4311
|
+
};
|
|
4312
|
+
}
|
|
4313
|
+
async function imageRun(input, ctx) {
|
|
4314
|
+
const local = input.product_image_paths?.length ? await uploadLocalFiles(input.product_image_paths, ctx) : [];
|
|
4315
|
+
const images = [...input.product_image_urls ?? [], ...local];
|
|
4316
|
+
if (!input.product_url && !(input.product_name && images.length)) {
|
|
4317
|
+
return { kind: "text", text: "Pass product_url, or product_name plus product_image_urls (or product_image_paths)." };
|
|
4318
|
+
}
|
|
4319
|
+
const r = await call2(ctx, "pixeldojo_marketing", "/api/v1/marketing/image", {
|
|
4320
|
+
method: "POST",
|
|
4321
|
+
body: {
|
|
4322
|
+
template: input.template,
|
|
4323
|
+
product_url: input.product_url,
|
|
4324
|
+
...!input.product_url ? { product: { name: input.product_name, description: input.product_description, images } } : {},
|
|
4325
|
+
character_images: input.character_image_urls,
|
|
4326
|
+
headline: input.headline,
|
|
4327
|
+
points: input.points,
|
|
4328
|
+
cta: input.cta,
|
|
4329
|
+
direction: input.direction,
|
|
4330
|
+
aspect_ratio: input.aspect_ratio,
|
|
4331
|
+
engine: input.engine,
|
|
4332
|
+
count: input.count,
|
|
4333
|
+
reference_ad_url: input.reference_ad_url,
|
|
4334
|
+
brand_kit: input.brand_kit
|
|
4335
|
+
}
|
|
4336
|
+
});
|
|
4337
|
+
if (!r.ok) return { kind: "text", text: `Images not started: ${r.error}` };
|
|
4338
|
+
const jobs = r.data.jobs ?? [];
|
|
4339
|
+
const polled = await Promise.all(
|
|
4340
|
+
jobs.map(
|
|
4341
|
+
(j) => pollJobUntilDoneOrHandoff(j.jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
|
|
4342
|
+
now: ctx.pollOverrides?.now,
|
|
4343
|
+
sleep: ctx.pollOverrides?.sleep
|
|
4344
|
+
})
|
|
4345
|
+
)
|
|
4346
|
+
);
|
|
4347
|
+
const urls = polled.flatMap((p) => p.kind === "completed" ? extractAssetUrls(p.job) : []);
|
|
4348
|
+
const pending = polled.filter((p) => p.kind === "handoff").map((p) => p.jobId);
|
|
4349
|
+
const failed = polled.filter((p) => p.kind === "failed").length;
|
|
4350
|
+
const credits = jobs.reduce((n, j) => n + (j.creditCost ?? 0), 0);
|
|
4351
|
+
const head = `${r.data.template} on ${r.data.engine}: ${urls.length} ready${pending.length ? `, ${pending.length} still rendering` : ""}${failed ? `, ${failed} failed (refunded)` : ""}${credits ? `, ${credits} credits` : ""}.`;
|
|
4352
|
+
if (pending.length === 1 && !urls.length) {
|
|
4353
|
+
return { kind: "handoff", text: `${head} Check it with pixeldojo_status.`, jobId: pending[0], statusUrl: ctx.buildStatusUrl(pending[0]) };
|
|
4354
|
+
}
|
|
4355
|
+
const tail = pending.length ? ` Still rendering: ${pending.join(", ")} (check each with pixeldojo_status).` : "";
|
|
4356
|
+
if (urls.length) return { kind: "assets", text: `${head}${tail}`, assetUrls: urls };
|
|
4357
|
+
return { kind: "text", text: `${head}${tail}` };
|
|
4358
|
+
}
|
|
4359
|
+
async function settleMotion(view, ctx) {
|
|
4360
|
+
const now = ctx.pollOverrides?.now ?? Date.now;
|
|
4361
|
+
const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
|
|
4362
|
+
const until = now() + 7e4;
|
|
4363
|
+
let v = view;
|
|
4364
|
+
while (v.status === "processing" && now() < until) {
|
|
4365
|
+
await sleep(5e3);
|
|
4366
|
+
const r = await call2(ctx, "pixeldojo_marketing", `/api/v1/marketing/motion/${encodeURIComponent(v.id)}`);
|
|
4367
|
+
if (r.ok) v = r.data;
|
|
4368
|
+
}
|
|
4369
|
+
if (v.status === "completed" && v.video_url) {
|
|
4370
|
+
return { kind: "assets", text: `${v.template === "end-card" ? "End card added" : "Motion graphic rendered"} (${v.credits ?? "?"} credits). Video: ${v.video_url}`, assetUrls: [v.video_url] };
|
|
4371
|
+
}
|
|
4372
|
+
if (v.status === "failed") return { kind: "text", text: `Render failed, nothing charged: ${v.error}` };
|
|
4373
|
+
return {
|
|
4374
|
+
kind: "text",
|
|
4375
|
+
text: `Still rendering (${v.step}). Check it with pixeldojo_marketing action status motion_id ${v.id} in about 15 seconds.`,
|
|
4376
|
+
followUp: { input: { action: "status", motion_id: v.id }, afterSec: 15 }
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
async function motionRun(input, ctx) {
|
|
4380
|
+
if (input.action === "status") {
|
|
4381
|
+
if (!input.motion_id) return { kind: "text", text: "Pass motion_id." };
|
|
4382
|
+
const r2 = await call2(ctx, "pixeldojo_marketing", `/api/v1/marketing/motion/${encodeURIComponent(input.motion_id)}`);
|
|
4383
|
+
if (!r2.ok) return { kind: "text", text: `Could not read that render: ${r2.error}` };
|
|
4384
|
+
return settleMotion(r2.data, ctx);
|
|
4385
|
+
}
|
|
4386
|
+
const endCard = input.action === "end_card";
|
|
4387
|
+
const product = input.product_image_urls?.[0] ?? (input.product_image_paths?.length ? (await uploadLocalFiles(input.product_image_paths.slice(0, 1), ctx))[0] : void 0);
|
|
4388
|
+
const r = await call2(ctx, "pixeldojo_marketing", "/api/v1/marketing/motion", {
|
|
4389
|
+
method: "POST",
|
|
4390
|
+
body: {
|
|
4391
|
+
template: endCard ? "end-card" : input.template,
|
|
4392
|
+
aspect_ratio: input.aspect_ratio === "4:5" ? "9:16" : input.aspect_ratio,
|
|
4393
|
+
headline: input.headline,
|
|
4394
|
+
points: input.points,
|
|
4395
|
+
cta: input.cta,
|
|
4396
|
+
stat: input.stat,
|
|
4397
|
+
stat_label: input.stat_label,
|
|
4398
|
+
price_was: input.price_was,
|
|
4399
|
+
price_now: input.price_now,
|
|
4400
|
+
product_image_url: product,
|
|
4401
|
+
video_url: input.video_url,
|
|
4402
|
+
brand_name: input.brand_name,
|
|
4403
|
+
colors: input.colors?.map((c) => c.trim().toLowerCase()),
|
|
4404
|
+
brand_kit: input.brand_kit
|
|
4405
|
+
}
|
|
4406
|
+
});
|
|
4407
|
+
if (!r.ok) return { kind: "text", text: `Not started: ${r.error}` };
|
|
4408
|
+
return settleMotion(r.data, ctx);
|
|
4409
|
+
}
|
|
4410
|
+
async function marketingRun(input, ctx) {
|
|
4411
|
+
if (input.action === "templates") return templatesRun(input, ctx);
|
|
4412
|
+
if (input.action === "image") return imageRun(input, ctx);
|
|
4413
|
+
return motionRun(input, ctx);
|
|
4414
|
+
}
|
|
4415
|
+
var headers2, KitSchema, describeKit, brandKitSkill, ACTIONS, MOTION, MarketingSchema, props, marketingSkill;
|
|
4416
|
+
var init_marketing = __esm({
|
|
4417
|
+
"src/skills/marketing.ts"() {
|
|
4418
|
+
init_poll();
|
|
4419
|
+
init_upload_helpers();
|
|
4420
|
+
init_types();
|
|
4421
|
+
headers2 = (ctx, skill) => ({
|
|
4422
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
4423
|
+
"Content-Type": "application/json",
|
|
4424
|
+
"x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
|
|
4425
|
+
"x-pixeldojo-skill": skill
|
|
4426
|
+
});
|
|
4427
|
+
KitSchema = z.object({
|
|
4428
|
+
action: z.enum(["get", "import", "update"]).optional(),
|
|
4429
|
+
url: z.string().max(2048).optional(),
|
|
4430
|
+
save: z.boolean().optional(),
|
|
4431
|
+
name: z.string().max(60).optional(),
|
|
4432
|
+
tagline: z.string().max(80).optional(),
|
|
4433
|
+
voice: z.string().max(240).optional(),
|
|
4434
|
+
colors: z.array(z.string()).max(5).optional(),
|
|
4435
|
+
fonts: z.array(z.string()).max(2).optional(),
|
|
4436
|
+
selling_points: z.array(z.string()).max(5).optional(),
|
|
4437
|
+
logo_url: z.string().url().optional()
|
|
4438
|
+
});
|
|
4439
|
+
describeKit = (k) => !k ? "No brand kit yet. Import one from the website with action import and url." : [
|
|
4440
|
+
`Brand kit: ${k.name || "(no name)"}${k.url ? ` (${k.url})` : ""}`,
|
|
4441
|
+
`Logo: ${k.logoUrl ?? "none (add logo_url, a PixelDojo upload)"}`,
|
|
4442
|
+
`Colors: ${k.colors.join(" ") || "none"}`,
|
|
4443
|
+
`Fonts: ${k.fonts.join(", ") || "none"}`,
|
|
4444
|
+
`Tagline: ${k.tagline || "none"}`,
|
|
4445
|
+
`Voice: ${k.voice || "none"}`,
|
|
4446
|
+
`Selling points: ${k.sellingPoints.join("; ") || "none"}`,
|
|
4447
|
+
"Every ad, campaign, image and motion template uses it unless that call passes brand_kit: false."
|
|
4448
|
+
].join("\n");
|
|
4449
|
+
brandKitSkill = {
|
|
4450
|
+
name: "pixeldojo_brand_kit",
|
|
4451
|
+
description: "The account's brand kit for Marketing Studio: logo, colors, fonts, voice, tagline and selling points. action import + url reads them from the brand's website (logo, palette and fonts from the page, the voice and claims from its words) and saves it; get shows it; update changes fields (colors as #RRGGBB, logo_url must be a PixelDojo upload). Free. Once set, every pixeldojo_ad, pixeldojo_campaign and pixeldojo_marketing call applies it automatically.",
|
|
4452
|
+
inputZod: KitSchema,
|
|
4453
|
+
inputJsonSchema: {
|
|
4454
|
+
type: "object",
|
|
4455
|
+
properties: {
|
|
4456
|
+
action: { type: "string", enum: ["get", "import", "update"], description: "Default get." },
|
|
4457
|
+
url: { type: "string", description: "import: the brand website, e.g. yourbrand.com." },
|
|
4458
|
+
save: { type: "boolean", description: "import: false returns the kit without saving it. Default true." },
|
|
4459
|
+
name: { type: "string", description: "Brand name." },
|
|
4460
|
+
tagline: { type: "string", description: "Short brand tagline." },
|
|
4461
|
+
voice: { type: "string", description: "How the brand talks, one or two sentences." },
|
|
4462
|
+
colors: { type: "array", items: { type: "string" }, maxItems: 5, description: "#RRGGBB, lead color first." },
|
|
4463
|
+
fonts: { type: "array", items: { type: "string" }, maxItems: 2, description: "Up to two brand font names." },
|
|
4464
|
+
selling_points: { type: "array", items: { type: "string" }, maxItems: 5, description: "Short product claims, 2-7 words each." },
|
|
4465
|
+
logo_url: { type: "string", format: "uri", description: "A logo on PixelDojo (upload it first); PNG with transparency is best." }
|
|
4466
|
+
}
|
|
4467
|
+
},
|
|
4468
|
+
allowedModels: [],
|
|
4469
|
+
defaultModel: "",
|
|
4470
|
+
run: kitRun
|
|
4471
|
+
};
|
|
4472
|
+
ACTIONS = ["templates", "image", "motion", "end_card", "status"];
|
|
4473
|
+
MOTION = ["kinetic-headline", "feature-callouts", "count-up", "price-drop", "logo-sting"];
|
|
4474
|
+
MarketingSchema = z.object({
|
|
4475
|
+
action: z.enum(ACTIONS),
|
|
4476
|
+
kind: z.enum(["image", "motion", "video"]).optional(),
|
|
4477
|
+
category: z.string().max(40).optional(),
|
|
4478
|
+
template: z.string().max(60).optional(),
|
|
4479
|
+
product_url: z.string().url().optional(),
|
|
4480
|
+
product_name: z.string().max(200).optional(),
|
|
4481
|
+
product_description: z.string().max(2e3).optional(),
|
|
4482
|
+
product_image_urls: z.array(z.string().url()).max(5).optional(),
|
|
4483
|
+
product_image_paths: z.array(z.string()).max(5).optional(),
|
|
4484
|
+
character_image_urls: z.array(z.string().url()).max(2).optional(),
|
|
4485
|
+
headline: z.string().max(80).optional(),
|
|
4486
|
+
points: z.array(z.string().max(70)).max(3).optional(),
|
|
4487
|
+
cta: z.string().max(30).optional(),
|
|
4488
|
+
direction: z.string().max(1500).optional(),
|
|
4489
|
+
aspect_ratio: z.enum(["1:1", "4:5", "9:16", "16:9"]).optional(),
|
|
4490
|
+
engine: z.enum(["nano-banana-pro", "nano-banana-2-1", "ideogram-4-5"]).optional(),
|
|
4491
|
+
count: z.number().int().min(1).max(8).optional(),
|
|
4492
|
+
reference_ad_url: z.string().url().optional(),
|
|
4493
|
+
stat: z.string().max(16).optional(),
|
|
4494
|
+
stat_label: z.string().max(40).optional(),
|
|
4495
|
+
price_was: z.string().max(16).optional(),
|
|
4496
|
+
price_now: z.string().max(16).optional(),
|
|
4497
|
+
video_url: z.string().url().optional(),
|
|
4498
|
+
motion_id: z.string().max(80).optional(),
|
|
4499
|
+
brand_name: z.string().max(60).optional(),
|
|
4500
|
+
colors: z.array(z.string()).max(5).optional(),
|
|
4501
|
+
brand_kit: z.boolean().optional()
|
|
4502
|
+
});
|
|
4503
|
+
props = {
|
|
4504
|
+
action: {
|
|
4505
|
+
type: "string",
|
|
4506
|
+
enum: ACTIONS,
|
|
4507
|
+
description: "templates = free list (filter by kind, category). image = image template on a product. motion = code-rendered motion graphic. end_card = brand end card after a finished ad (video_url). status = check a motion render (motion_id)."
|
|
4508
|
+
},
|
|
4509
|
+
kind: { type: "string", enum: ["image", "motion", "video"], description: "templates: filter." },
|
|
4510
|
+
category: { type: "string", description: "templates: product-shot, motion, ugc, tiktok, ads, posters or marketplace." },
|
|
4511
|
+
template: {
|
|
4512
|
+
type: "string",
|
|
4513
|
+
description: `image: an image template id from templates (e.g. stacked-shout, pure-white, ice-float). motion: ${MOTION.join(", ")}.`
|
|
4514
|
+
},
|
|
4515
|
+
product_url: { type: "string", format: "uri", description: "image: product page; name, description and photos are read from it." },
|
|
4516
|
+
product_name: { type: "string", description: "Product name when there is no product_url (with product_image_urls)." },
|
|
4517
|
+
product_description: { type: "string", description: "Short product description when there is no product_url." },
|
|
4518
|
+
product_image_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 5, description: "Product photos. motion uses the first, cut out automatically." },
|
|
4519
|
+
product_image_paths: { type: "array", items: { type: "string" }, maxItems: 5, description: "Local product photos instead of URLs; uploaded first." },
|
|
4520
|
+
character_image_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 2, description: "image: your own character (e.g. from pixeldojo_library source characters) for templates with a person." },
|
|
4521
|
+
headline: { type: "string", description: "Exact words on the ad. Blank uses the brand kit tagline." },
|
|
4522
|
+
points: { type: "array", items: { type: "string" }, maxItems: 3, description: "Selling points on the ad. Blank uses the brand kit." },
|
|
4523
|
+
cta: { type: "string", description: 'Button text. Default "Shop now".' },
|
|
4524
|
+
direction: { type: "string", description: "image: extra art direction added to the template." },
|
|
4525
|
+
aspect_ratio: { type: "string", enum: ["1:1", "4:5", "9:16", "16:9"], description: "Default: the template's own (motion 9:16; 4:5 renders 9:16 there)." },
|
|
4526
|
+
engine: { type: "string", enum: ["nano-banana-pro", "nano-banana-2-1", "ideogram-4-5"], description: "image: default nano-banana-pro (4.5 credits); nano-banana-2-1 is 1.5; ideogram-4-5 2 for poster type." },
|
|
4527
|
+
count: { type: "integer", minimum: 1, maximum: 8, description: "image: how many (each charged)." },
|
|
4528
|
+
reference_ad_url: { type: "string", format: "uri", description: "image ad multiplier: an image ad to rebuild with this product and your words. For a video ad use pixeldojo_ad reference_video_url." },
|
|
4529
|
+
stat: { type: "string", description: 'count-up: the number, e.g. "10,000+", "4.9", "98%".' },
|
|
4530
|
+
stat_label: { type: "string", description: "count-up: what it counts." },
|
|
4531
|
+
price_was: { type: "string", description: 'price-drop: old price, e.g. "$140".' },
|
|
4532
|
+
price_now: { type: "string", description: 'price-drop: new price, e.g. "$98".' },
|
|
4533
|
+
video_url: { type: "string", format: "uri", description: "end_card: the finished ad (a PixelDojo result)." },
|
|
4534
|
+
motion_id: { type: "string", description: "status: the id a motion or end_card call returned." },
|
|
4535
|
+
brand_name: { type: "string", description: "motion: the brand shown when there is no logo. Default: the brand kit." },
|
|
4536
|
+
colors: { type: "array", items: { type: "string" }, maxItems: 5, description: "motion: #RRGGBB, lead color first. Default: the brand kit." },
|
|
4537
|
+
brand_kit: { type: "boolean", description: "false skips the brand kit for this call." }
|
|
4538
|
+
};
|
|
4539
|
+
marketingSkill = {
|
|
4540
|
+
name: "pixeldojo_marketing",
|
|
4541
|
+
description: "Marketing Studio templates beyond video ads. templates lists them free with real example outputs. image applies an image template to a product: static ads, product shots, posters, marketplace listings (pure white for Amazon/Shopify), UGC photos, headline spelled exactly, 1-8 at a time at the engine price; with reference_ad_url it rebuilds an existing ad with this product (ad multiplier). motion renders code-built motion graphics with exact words, brand colors and logo: kinetic-headline, feature-callouts, count-up, price-drop, logo-sting (2 credits each). end_card adds the brand logo, tagline and button after a finished video ad (1 credit per 30 s). The brand kit (pixeldojo_brand_kit) fills colors, logo, headline and selling points unless brand_kit: false.",
|
|
4542
|
+
inputZod: MarketingSchema,
|
|
4543
|
+
inputJsonSchema: { type: "object", required: ["action"], properties: props },
|
|
4544
|
+
allowedModels: [],
|
|
4545
|
+
defaultModel: "",
|
|
4546
|
+
run: marketingRun
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
});
|
|
4550
|
+
async function pollToResult(jobId, ctx, successText) {
|
|
4551
|
+
const result = await pollJobUntilDoneOrHandoff(jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
|
|
4552
|
+
now: ctx.pollOverrides?.now,
|
|
4553
|
+
sleep: ctx.pollOverrides?.sleep
|
|
4554
|
+
});
|
|
4555
|
+
return pollResultToSkillResult(result, (urls) => urls.length === 0 ? `Job ${jobId} completed but returned no assets.` : successText(urls));
|
|
4556
|
+
}
|
|
4557
|
+
async function buildRun(input, ctx) {
|
|
4558
|
+
const doFetch = ctx.fetch ?? fetch;
|
|
4559
|
+
if (input.action === "traits") {
|
|
4560
|
+
const res2 = await doFetch(`${ctx.baseUrl}/api/v1/characters/build`, { headers: headers3(ctx, "pixeldojo_build_character") });
|
|
4561
|
+
const data2 = await res2.json().catch(() => ({}));
|
|
4562
|
+
if (!res2.ok || !data2.traits) return { kind: "text", text: `Could not list traits: ${data2.error ?? `HTTP ${res2.status}`}` };
|
|
4563
|
+
return {
|
|
4564
|
+
kind: "text",
|
|
4565
|
+
text: [
|
|
4566
|
+
`Build a character: pass picks as {"trait id": "option id"} (an array for traits you can pick several of), or randomize: true to fill the rest. ${data2.takes ?? 4} takes cost ${data2.credits ?? "?"} credits. Free to list.`,
|
|
4567
|
+
...data2.traits.map(
|
|
4568
|
+
(t) => `${t.id} (${t.label}${t.pick_up_to ? `, pick up to ${t.pick_up_to}` : ""}${t.people_only ? ", people only" : ""}): ${t.options.map((o) => o.id).join(", ")}`
|
|
4569
|
+
)
|
|
4570
|
+
].join("\n")
|
|
4571
|
+
};
|
|
4572
|
+
}
|
|
4573
|
+
const res = await doFetch(`${ctx.baseUrl}/api/v1/characters/build`, {
|
|
4574
|
+
method: "POST",
|
|
4575
|
+
headers: headers3(ctx, "pixeldojo_build_character"),
|
|
4576
|
+
body: JSON.stringify({
|
|
4577
|
+
...input.picks ? { picks: input.picks } : {},
|
|
4578
|
+
...input.randomize ? { randomize: true } : {},
|
|
4579
|
+
...input.details ? { details: input.details } : {},
|
|
4580
|
+
...input.takes ? { takes: input.takes } : {}
|
|
4581
|
+
})
|
|
4582
|
+
});
|
|
4583
|
+
const data = await res.json().catch(() => ({}));
|
|
4584
|
+
if (!res.ok || !data.jobId) {
|
|
4585
|
+
const err = typeof data.error === "string" ? data.error : data.error?.message;
|
|
4586
|
+
return { kind: "text", text: `Character not built (nothing charged): ${err ?? `HTTP ${res.status}`}` };
|
|
4587
|
+
}
|
|
4588
|
+
return pollToResult(
|
|
4589
|
+
data.jobId,
|
|
4590
|
+
ctx,
|
|
4591
|
+
(urls) => [
|
|
4592
|
+
`${urls.length} take${urls.length === 1 ? "" : "s"} of ${data.summary ?? "the character"} (${data.creditCost ?? "?"} credits):`,
|
|
4593
|
+
...urls.map((u, i) => `${i + 1}. ${u}`),
|
|
4594
|
+
`Show them to the user. To keep one, call pixeldojo_save_character with a name, image_urls: [that take] and description: "${data.summary ?? ""}" within the hour (outputs expire). It then works as @Name in any prompt and can perform with pixeldojo_perform.`
|
|
4595
|
+
].join("\n")
|
|
4596
|
+
);
|
|
4597
|
+
}
|
|
4598
|
+
async function performRun(input, ctx) {
|
|
4599
|
+
const doFetch = ctx.fetch ?? fetch;
|
|
4600
|
+
if (input.action === "list") {
|
|
4601
|
+
const res2 = await doFetch(`${ctx.baseUrl}/api/v1/characters/perform`, { headers: headers3(ctx, "pixeldojo_perform") });
|
|
4602
|
+
const data2 = await res2.json().catch(() => ({}));
|
|
4603
|
+
if (!res2.ok || !data2.presets) return { kind: "text", text: `Could not list presets: ${data2.error ?? `HTTP ${res2.status}`}` };
|
|
4604
|
+
return {
|
|
4605
|
+
kind: "text",
|
|
4606
|
+
text: [
|
|
4607
|
+
'Perform presets (a 5 s video of a saved character doing it). Run one with action "run", character and preset:',
|
|
4608
|
+
...data2.presets.map((p) => `- ${p.slug} (${p.group}, ${p.credits} credits): ${p.title}. ${p.tagline}`)
|
|
4609
|
+
].join("\n")
|
|
4610
|
+
};
|
|
4611
|
+
}
|
|
4612
|
+
if (!input.character || !input.preset) {
|
|
4613
|
+
return { kind: "text", text: `Pass character (a saved character's name or @Name) and preset (from action "list").` };
|
|
4614
|
+
}
|
|
4615
|
+
const res = await doFetch(`${ctx.baseUrl}/api/v1/characters/perform`, {
|
|
4616
|
+
method: "POST",
|
|
4617
|
+
headers: headers3(ctx, "pixeldojo_perform"),
|
|
4618
|
+
body: JSON.stringify({ character: input.character, preset: input.preset })
|
|
4619
|
+
});
|
|
4620
|
+
const data = await res.json().catch(() => ({}));
|
|
4621
|
+
if (!res.ok || !data.jobId) {
|
|
4622
|
+
const err = typeof data.error === "string" ? data.error : data.error?.message;
|
|
4623
|
+
return { kind: "text", text: `Not started (nothing charged): ${err ?? `HTTP ${res.status}`}` };
|
|
4624
|
+
}
|
|
4625
|
+
return pollToResult(data.jobId, ctx, (urls) => `${input.character} performed ${input.preset} (${data.creditCost ?? "?"} credits). Asset: ${urls[0]}`);
|
|
4626
|
+
}
|
|
4627
|
+
var headers3, BuildSchema, buildCharacterSkill, PerformSchema, performSkill;
|
|
4628
|
+
var init_character_builder = __esm({
|
|
4629
|
+
"src/skills/character-builder.ts"() {
|
|
4630
|
+
init_poll();
|
|
4631
|
+
init_types();
|
|
4632
|
+
headers3 = (ctx, skill) => ({
|
|
4633
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
4634
|
+
"Content-Type": "application/json",
|
|
4635
|
+
"x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
|
|
4636
|
+
"x-pixeldojo-skill": skill
|
|
4637
|
+
});
|
|
4638
|
+
BuildSchema = z.object({
|
|
4639
|
+
action: z.enum(["traits", "build"]),
|
|
4640
|
+
picks: z.record(z.union([z.string(), z.array(z.string())])).optional(),
|
|
4641
|
+
randomize: z.boolean().optional(),
|
|
4642
|
+
details: z.string().max(200).optional(),
|
|
4643
|
+
takes: z.number().int().min(1).max(4).optional()
|
|
4644
|
+
});
|
|
4645
|
+
buildCharacterSkill = {
|
|
4646
|
+
name: "pixeldojo_build_character",
|
|
4647
|
+
description: `Build an original AI character or influencer from traits instead of photos, like Character Studio's builder: type (realistic, bold or extreme caricature, anime, 3D, cat, dog, fox, frog, insect, robot...), gender, age (adults only), body, height, wild proportions (egg body, long limbs, tiny waist), head shape (gigachad, mega jaw), hair, face features, style and accessories. action "traits" (free) lists every trait and option id; action "build" renders up to 4 takes from picks (and/or randomize). Keep one with pixeldojo_save_character.`,
|
|
4648
|
+
inputZod: BuildSchema,
|
|
4649
|
+
inputJsonSchema: {
|
|
4650
|
+
type: "object",
|
|
4651
|
+
required: ["action"],
|
|
4652
|
+
properties: {
|
|
4653
|
+
action: { type: "string", enum: ["traits", "build"], description: "traits = list trait and option ids (free). build = render the takes (paid)." },
|
|
4654
|
+
picks: {
|
|
4655
|
+
type: "object",
|
|
4656
|
+
additionalProperties: { anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }] },
|
|
4657
|
+
description: 'build: {"trait id": "option id"}, e.g. {"type": "frog", "hair": "mullet", "hairColor": "blonde", "distinctive": ["gold-grill"], "style": "retro"}. Ids from action "traits"; labels work too.'
|
|
4658
|
+
},
|
|
4659
|
+
randomize: { type: "boolean", description: "build: fill every trait not in picks at random." },
|
|
4660
|
+
details: { type: "string", description: 'build: up to 200 characters of extra look, e.g. "freckles, gold hoops, a vintage denim jacket".' },
|
|
4661
|
+
takes: { type: "integer", minimum: 1, maximum: 4, description: "build: how many takes (default 4)." }
|
|
4662
|
+
}
|
|
4663
|
+
},
|
|
4664
|
+
allowedModels: [],
|
|
4665
|
+
defaultModel: "",
|
|
4666
|
+
run: buildRun
|
|
4667
|
+
};
|
|
4668
|
+
PerformSchema = z.object({
|
|
4669
|
+
action: z.enum(["list", "run"]),
|
|
4670
|
+
character: z.string().max(100).optional(),
|
|
4671
|
+
preset: z.string().max(80).optional()
|
|
4672
|
+
});
|
|
4673
|
+
performSkill = {
|
|
4674
|
+
name: "pixeldojo_perform",
|
|
4675
|
+
description: 'Make a saved Character Studio character perform: a dance, a trend or a star moment (AI Dance, Sassy Dance, Shoulder Shimmy, Hair Flip, Catwalk, Peace Wink, Red Carpet, Award Win...) as a share-ready 5 s video. action "list" (free) gives the presets and prices; action "run" takes character (name or @Name) and preset. Usually handed off; poll with pixeldojo_status.',
|
|
4676
|
+
inputZod: PerformSchema,
|
|
4677
|
+
inputJsonSchema: {
|
|
4678
|
+
type: "object",
|
|
4679
|
+
required: ["action"],
|
|
4680
|
+
properties: {
|
|
4681
|
+
action: { type: "string", enum: ["list", "run"], description: "list = presets with prices (free). run = make the video (paid)." },
|
|
4682
|
+
character: { type: "string", description: "run: the saved character's name or @Name (pixeldojo_library source=characters lists them)." },
|
|
4683
|
+
preset: { type: "string", description: 'run: a preset slug from action "list", e.g. "ai-dance", "shoulder-shimmy", "red-carpet".' }
|
|
4684
|
+
}
|
|
4685
|
+
},
|
|
4686
|
+
allowedModels: [],
|
|
4687
|
+
defaultModel: "",
|
|
4688
|
+
run: performRun
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
});
|
|
3732
4692
|
function substitutePlaceholders(args, inputs, prevUrl) {
|
|
3733
4693
|
const missing = [];
|
|
3734
4694
|
const fill = (value) => {
|
|
@@ -3972,10 +4932,13 @@ __export(skills_exports, {
|
|
|
3972
4932
|
TOOL_ANNOTATIONS: () => TOOL_ANNOTATIONS,
|
|
3973
4933
|
adSkill: () => adSkill,
|
|
3974
4934
|
audioSkill: () => audioSkill,
|
|
4935
|
+
brandKitSkill: () => brandKitSkill,
|
|
4936
|
+
buildCharacterSkill: () => buildCharacterSkill,
|
|
3975
4937
|
campaignSkill: () => campaignSkill,
|
|
3976
4938
|
campaignStatusSkill: () => campaignStatusSkill,
|
|
3977
4939
|
captionSkill: () => captionSkill,
|
|
3978
4940
|
characterSkill: () => characterSkill,
|
|
4941
|
+
dramaSkill: () => dramaSkill,
|
|
3979
4942
|
editSkill: () => editSkill,
|
|
3980
4943
|
effectRunSkill: () => effectRunSkill,
|
|
3981
4944
|
effectsListSkill: () => effectsListSkill,
|
|
@@ -3985,9 +4948,13 @@ __export(skills_exports, {
|
|
|
3985
4948
|
findSkill: () => findSkill,
|
|
3986
4949
|
fromUrlSkill: () => fromUrlSkill,
|
|
3987
4950
|
generateSkill: () => generateSkill,
|
|
4951
|
+
kataSkill: () => kataSkill,
|
|
3988
4952
|
librarySkill: () => librarySkill,
|
|
3989
4953
|
listWorkflowsSkill: () => listWorkflowsSkill,
|
|
4954
|
+
marketingSkill: () => marketingSkill,
|
|
4955
|
+
musicVideoSkill: () => musicVideoSkill,
|
|
3990
4956
|
overlaySkill: () => overlaySkill,
|
|
4957
|
+
performSkill: () => performSkill,
|
|
3991
4958
|
runWorkflowSkill: () => runWorkflowSkill,
|
|
3992
4959
|
saveCharacterSkill: () => saveCharacterSkill,
|
|
3993
4960
|
saveSkill: () => saveSkill,
|
|
@@ -4032,6 +4999,9 @@ var init_skills = __esm({
|
|
|
4032
4999
|
init_film();
|
|
4033
5000
|
init_video();
|
|
4034
5001
|
init_episode();
|
|
5002
|
+
init_drama();
|
|
5003
|
+
init_music_video();
|
|
5004
|
+
init_kata();
|
|
4035
5005
|
init_ad();
|
|
4036
5006
|
init_campaign();
|
|
4037
5007
|
init_campaign_status();
|
|
@@ -4041,6 +5011,8 @@ var init_skills = __esm({
|
|
|
4041
5011
|
init_save();
|
|
4042
5012
|
init_save_character();
|
|
4043
5013
|
init_effects();
|
|
5014
|
+
init_marketing();
|
|
5015
|
+
init_character_builder();
|
|
4044
5016
|
init_workflows();
|
|
4045
5017
|
ALL_SKILLS = [
|
|
4046
5018
|
generateSkill,
|
|
@@ -4056,6 +5028,8 @@ var init_skills = __esm({
|
|
|
4056
5028
|
librarySkill,
|
|
4057
5029
|
saveSkill,
|
|
4058
5030
|
saveCharacterSkill,
|
|
5031
|
+
buildCharacterSkill,
|
|
5032
|
+
performSkill,
|
|
4059
5033
|
saveWorkflowSkill,
|
|
4060
5034
|
runWorkflowSkill,
|
|
4061
5035
|
listWorkflowsSkill,
|
|
@@ -4063,23 +5037,28 @@ var init_skills = __esm({
|
|
|
4063
5037
|
fromUrlSkill,
|
|
4064
5038
|
uploadSkill,
|
|
4065
5039
|
adSkill,
|
|
5040
|
+
brandKitSkill,
|
|
5041
|
+
marketingSkill,
|
|
4066
5042
|
campaignSkill,
|
|
4067
5043
|
campaignStatusSkill,
|
|
4068
5044
|
filmSkill,
|
|
4069
5045
|
videoSkill,
|
|
4070
|
-
episodeSkill
|
|
5046
|
+
episodeSkill,
|
|
5047
|
+
dramaSkill,
|
|
5048
|
+
musicVideoSkill,
|
|
5049
|
+
kataSkill
|
|
4071
5050
|
];
|
|
4072
5051
|
isLocalPathField = (key) => /_paths?$/.test(key);
|
|
4073
5052
|
HOSTED_SKILLS = ALL_SKILLS.filter(
|
|
4074
5053
|
(s) => s.name !== uploadSkill.name
|
|
4075
5054
|
).map((s) => {
|
|
4076
|
-
const
|
|
5055
|
+
const props2 = s.inputJsonSchema.properties ?? {};
|
|
4077
5056
|
return {
|
|
4078
5057
|
...s,
|
|
4079
5058
|
description: s.hostedDescription ?? s.description,
|
|
4080
5059
|
inputJsonSchema: {
|
|
4081
5060
|
...s.inputJsonSchema,
|
|
4082
|
-
properties: Object.fromEntries(Object.entries(
|
|
5061
|
+
properties: Object.fromEntries(Object.entries(props2).filter(([k]) => !isLocalPathField(k)))
|
|
4083
5062
|
}
|
|
4084
5063
|
};
|
|
4085
5064
|
});
|
|
@@ -4098,9 +5077,14 @@ var init_skills = __esm({
|
|
|
4098
5077
|
pixeldojo_effect: render("Run one-click effect"),
|
|
4099
5078
|
pixeldojo_video: render("Change, animate or extend video"),
|
|
4100
5079
|
pixeldojo_ad: render("Make video ad"),
|
|
5080
|
+
pixeldojo_brand_kit: render("Brand kit from website"),
|
|
5081
|
+
pixeldojo_marketing: render("Image ads, motion graphics and templates"),
|
|
4101
5082
|
pixeldojo_campaign: render("Make product launch campaign"),
|
|
4102
5083
|
pixeldojo_film: render("Make short film"),
|
|
4103
5084
|
pixeldojo_episode: render("Make faceless video"),
|
|
5085
|
+
pixeldojo_drama: render("Make micro-drama, short film or trailer"),
|
|
5086
|
+
pixeldojo_music_video: render("Make music video for a song"),
|
|
5087
|
+
pixeldojo_kata: render("Recreate an edit with your cast"),
|
|
4104
5088
|
pixeldojo_run_workflow: render("Run saved workflow"),
|
|
4105
5089
|
pixeldojo_upload: render("Upload local file"),
|
|
4106
5090
|
pixeldojo_save_workflow: { ...render("Save workflow"), destructiveHint: true },
|
|
@@ -4109,6 +5093,8 @@ var init_skills = __esm({
|
|
|
4109
5093
|
pixeldojo_library: read("Search media library"),
|
|
4110
5094
|
pixeldojo_save: render("Save to My Media"),
|
|
4111
5095
|
pixeldojo_save_character: render("Save character or element"),
|
|
5096
|
+
pixeldojo_build_character: render("Build AI character from traits"),
|
|
5097
|
+
pixeldojo_perform: render("Make character dance or do a trend"),
|
|
4112
5098
|
pixeldojo_list_workflows: read("List saved workflows"),
|
|
4113
5099
|
pixeldojo_from_url: { ...read("Read product page"), openWorldHint: true }
|
|
4114
5100
|
};
|
|
@@ -4118,10 +5104,10 @@ function makeAttributedClient(options) {
|
|
|
4118
5104
|
const { mcpVersion, skillName, baseFetch, ...sdkOptions } = options;
|
|
4119
5105
|
const real = baseFetch ?? globalThis.fetch.bind(globalThis);
|
|
4120
5106
|
const wrappedFetch = (input, init) => {
|
|
4121
|
-
const
|
|
4122
|
-
|
|
4123
|
-
if (skillName)
|
|
4124
|
-
return real(input, { ...init, headers:
|
|
5107
|
+
const headers4 = new Headers(init?.headers);
|
|
5108
|
+
headers4.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
|
|
5109
|
+
if (skillName) headers4.set("x-pixeldojo-skill", skillName);
|
|
5110
|
+
return real(input, { ...init, headers: headers4 });
|
|
4125
5111
|
};
|
|
4126
5112
|
return new PixelDojoClient({
|
|
4127
5113
|
...sdkOptions,
|
|
@@ -4165,7 +5151,7 @@ function makeRedactedLogger(target = console) {
|
|
|
4165
5151
|
}
|
|
4166
5152
|
|
|
4167
5153
|
// src/server.ts
|
|
4168
|
-
var PACKAGE_VERSION = "0.
|
|
5154
|
+
var PACKAGE_VERSION = "0.31.0" ;
|
|
4169
5155
|
var SERVER_INSTRUCTIONS = "Pixel Dojo generates images, video, audio and short films from one catalog of AI models, billed in credits from the connected Pixel Dojo account. A job that runs past the 30-second wait returns a jobId to check later; check it rather than submitting again, since every new submission is charged. Generated asset URLs are deleted one hour after they are created: pixeldojo_save keeps one in the user's My Media permanently (after a safety check), and finished films stay in the account library. In any prompt, @Name uses a character, product, outfit, location, prop or style the user saved in Character Studio: its photos are attached automatically. pixeldojo_library source=characters lists them, and pixeldojo_save_character saves a new one from 3 to 5 photos.";
|
|
4170
5156
|
function buildServer(options) {
|
|
4171
5157
|
if (!options.apiKey) {
|