@pixeldojo/mcp 0.26.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -239,11 +239,11 @@ function nearestAspectFromEnum(requested, allowed) {
239
239
  }
240
240
  async function snapAspectRatio(apiId, requested, client, opts = {}) {
241
241
  if (!requested) return void 0;
242
- const props = await getFieldConstraints(apiId, client);
243
- if (props === null) {
242
+ const props2 = await getFieldConstraints(apiId, client);
243
+ if (props2 === null) {
244
244
  return opts.onUnknown === "omit" ? void 0 : requested;
245
245
  }
246
- const field = props.aspect_ratio;
246
+ const field = props2.aspect_ratio;
247
247
  if (!field) return void 0;
248
248
  if (!field.enum || field.enum.length === 0) return requested;
249
249
  return nearestAspectFromEnum(requested, field.enum);
@@ -274,11 +274,11 @@ function resolutionHeight(value) {
274
274
  }
275
275
  async function snapResolution(apiId, requested, client, opts = {}) {
276
276
  if (!requested) return void 0;
277
- const props = await getFieldConstraints(apiId, client);
278
- if (props === null) {
277
+ const props2 = await getFieldConstraints(apiId, client);
278
+ if (props2 === null) {
279
279
  return opts.onUnknown === "omit" ? void 0 : requested;
280
280
  }
281
- const field = props.resolution;
281
+ const field = props2.resolution;
282
282
  if (!field) return void 0;
283
283
  if (!field.enum || field.enum.length === 0) return requested;
284
284
  if (field.enum.includes(requested)) return requested;
@@ -299,11 +299,11 @@ async function snapResolution(apiId, requested, client, opts = {}) {
299
299
  }
300
300
  async function snapDuration(apiId, requested, client, opts = {}) {
301
301
  if (requested === void 0) return void 0;
302
- const props = await getFieldConstraints(apiId, client);
303
- if (props === null) {
302
+ const props2 = await getFieldConstraints(apiId, client);
303
+ if (props2 === null) {
304
304
  return opts.onUnknown === "omit" ? void 0 : requested;
305
305
  }
306
- const field = props.duration;
306
+ const field = props2.duration;
307
307
  if (!field) return void 0;
308
308
  if (field.enum && field.enum.length > 0) {
309
309
  const allowed = field.enum.map(Number).filter((n) => Number.isFinite(n));
@@ -331,11 +331,11 @@ async function resolveImageRefs(apiId, urls, client) {
331
331
  if (urls.length === 0) return null;
332
332
  const info = await getModelInfo(apiId, client);
333
333
  if (info === null) return null;
334
- const props = info.props;
335
- const key = info.media ? info.media.image?.key : REF_KEYS.find((k) => props[k] !== void 0);
334
+ const props2 = info.props;
335
+ const key = info.media ? info.media.image?.key : REF_KEYS.find((k) => props2[k] !== void 0);
336
336
  if (!key) return null;
337
337
  const also = info.media?.image?.also;
338
- const field = props[key] ?? {};
338
+ const field = props2[key] ?? {};
339
339
  if (field.type === "array") {
340
340
  const cap = typeof field.maxItems === "number" ? field.maxItems : urls.length;
341
341
  const kept = urls.slice(0, Math.max(1, cap));
@@ -1673,7 +1673,7 @@ function needsSave(url) {
1673
1673
  return Boolean(url && url.startsWith("https://temp.pixeldojo.ai/"));
1674
1674
  }
1675
1675
  async function filmApi(ctx, method, path, body) {
1676
- const res = await fetch(`${ctx.baseUrl}${path}`, {
1676
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}${path}`, {
1677
1677
  method,
1678
1678
  headers: {
1679
1679
  Authorization: `Bearer ${ctx.apiKey}`,
@@ -2255,7 +2255,7 @@ var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, InputSchema10, InputJsonSchema10, sho
2255
2255
  var init_film = __esm({
2256
2256
  "src/skills/film.ts"() {
2257
2257
  init_upload_helpers();
2258
- 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"];
2259
2259
  MUSIC_VIDEO_LOOKS = ["rap-video", "neon-club", "stage-performance", "retro-camcorder", "y2k-pop", "moody-rnb", "rock-grit", "dream-pop", "k-pop"];
2260
2260
  LOOKS = [...STORY_LOOKS, ...MUSIC_VIDEO_LOOKS];
2261
2261
  InputSchema10 = zod.z.object({
@@ -2679,7 +2679,7 @@ var init_video = __esm({
2679
2679
  }
2680
2680
  });
2681
2681
  async function episodeApi(ctx, method, path, body) {
2682
- const res = await fetch(`${ctx.baseUrl}/api/faceless-studio/episodes${path}`, {
2682
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/faceless-studio/episodes${path}`, {
2683
2683
  method,
2684
2684
  headers: {
2685
2685
  Authorization: `Bearer ${ctx.apiKey}`,
@@ -2919,6 +2919,479 @@ var init_episode = __esm({
2919
2919
  };
2920
2920
  }
2921
2921
  });
2922
+ async function dramaApi(ctx, method, path, body) {
2923
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/drama${path}`, {
2924
+ method,
2925
+ headers: {
2926
+ Authorization: `Bearer ${ctx.apiKey}`,
2927
+ "Content-Type": "application/json",
2928
+ "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
2929
+ "x-pixeldojo-skill": "drama"
2930
+ },
2931
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2932
+ });
2933
+ const data = await res.json().catch(() => ({}));
2934
+ return { status: res.status, data };
2935
+ }
2936
+ function describeDrama(v, o = DRAMA_OPTS) {
2937
+ const id = v.id;
2938
+ const stubborn = Array.from(new Set(v.retaken.filter((b, i, all) => all.indexOf(b) !== i)));
2939
+ 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.` : "";
2940
+ const next2 = (action) => `${o.tool} with {"${o.idField}": "${id}", "action": "${action}"}`;
2941
+ const music = o.unit === "section";
2942
+ const what = music ? "music video" : "episode";
2943
+ 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".` : ""}` };
2944
+ if (v.status === "quote") {
2945
+ 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).`;
2946
+ return {
2947
+ kind: "text",
2948
+ 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")
2949
+ };
2950
+ }
2951
+ if (v.status === "notes") {
2952
+ const flagged = v.beats.filter((b) => b.note && (b.needs_redo ?? true));
2953
+ const other = v.beats.filter((b) => b.note && b.needs_redo === false);
2954
+ return {
2955
+ kind: "text",
2956
+ text: [
2957
+ `Every ${o.unit} is filmed. Before the cut, the review flagged ${o.unit}s ${flagged.map((b) => b.beat).join(", ")}:`,
2958
+ ...flagged.map((b) => `- ${o.unit} ${b.beat}: ${b.note}${b.redo_films ? ` The redo films: ${b.redo_films}` : ""}`),
2959
+ other.length ? `Also noted, no redo needed: ${other.map((b) => `${o.unit} ${b.beat}: ${b.note}`).join(" ")}` : "",
2960
+ simplify,
2961
+ `Show these to the user and ask which they want: redo those ${o.unit}s with these notes (${o.tool} with {"${o.idField}": "${id}", "action": "redo", "beats": [${flagged.map((b) => b.beat).join(", ")}], "notes": [the notes, same order]}, each at its own price), or cut it as it is (action "cut", free). Nothing more is filmed until they choose.`
2962
+ ].filter(Boolean).join("\n")
2963
+ };
2964
+ }
2965
+ if (v.status === "done" && v.final_url) {
2966
+ const redo = v.beats.filter((b) => (b.note || b.missing_lines.length) && (b.needs_redo ?? true));
2967
+ const notes = redo.map((b) => `${o.unit} ${b.beat}: ${b.note ?? `did not say ${b.missing_lines.map((l) => `"${l}"`).join(", ")}`}${b.redo_films ? ` The redo films: ${b.redo_films}` : ""}`);
2968
+ const other = v.beats.filter((b) => b.note && b.needs_redo === false).map((b) => `${o.unit} ${b.beat}: ${b.note}`);
2969
+ return {
2970
+ kind: "assets",
2971
+ text: [
2972
+ `"${v.title}" is finished and saved to the Library: ${v.final_url}`,
2973
+ v.retaken.length ? `Filmed again: ${o.unit} ${v.retaken.join(", ")}.` : "",
2974
+ notes.length ? `Review notes: ${notes.join("; ")}. Offer a redo of those ${o.unit}s (action "redo" with beats and notes).` : "",
2975
+ other.length ? `Also noted, no redo needed: ${other.join("; ")}.` : "",
2976
+ simplify
2977
+ ].filter(Boolean).join("\n"),
2978
+ assetUrls: [v.final_url]
2979
+ };
2980
+ }
2981
+ 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;
2982
+ const ready = v.beats.filter((b) => b.url).length;
2983
+ const total = v.beats.length || (music ? v.sections?.length ?? 0 : 5);
2984
+ 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.missing_lines.length ? ` (it dropped the line "${b.missing_lines[0]}")` : b.note ? ` (the review asked: ${b.note.split(/(?<=\.) /)[0]})` : ""}`);
2985
+ return {
2986
+ kind: "text",
2987
+ 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.`,
2988
+ followUp: { input: { action: "status", [o.idField]: id }, afterSec: v.status === "scripting" ? 20 : 45 }
2989
+ };
2990
+ }
2991
+ async function waitForPlan2(ctx, id) {
2992
+ const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
2993
+ let last = await dramaApi(ctx, "GET", `/${id}`);
2994
+ for (let i = 0; i < 8 && last.data.status === "scripting"; i += 1) {
2995
+ await sleep(3e3);
2996
+ last = await dramaApi(ctx, "GET", `/${id}`);
2997
+ }
2998
+ return last;
2999
+ }
3000
+ var KINDS, LOOKS2, STORY_TYPES, InputSchema13, InputJsonSchema13, errorText, STEP, DRAMA_OPTS, dramaSkill;
3001
+ var init_drama = __esm({
3002
+ "src/skills/drama.ts"() {
3003
+ KINDS = ["micro-drama", "short-film", "trailer"];
3004
+ LOOKS2 = ["live-action", "action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
3005
+ STORY_TYPES = ["billionaire", "werewolf", "mafia", "revenge", "secret-identity", "vampire", "second-chance"];
3006
+ InputSchema13 = zod.z.object({
3007
+ action: zod.z.enum(["create", "produce", "status", "redo", "cut"]).default("create"),
3008
+ idea: zod.z.string().min(8).max(1500).optional(),
3009
+ format: zod.z.enum(["9:16", "16:9"]).default("9:16"),
3010
+ mature: zod.z.boolean().optional(),
3011
+ kind: zod.z.enum(KINDS).default("micro-drama"),
3012
+ look: zod.z.enum(LOOKS2).optional(),
3013
+ story_type: zod.z.enum(STORY_TYPES).optional(),
3014
+ title_card: zod.z.boolean().optional(),
3015
+ name_tags: zod.z.boolean().optional(),
3016
+ end_screen: zod.z.boolean().optional(),
3017
+ end_text: zod.z.string().max(60).optional(),
3018
+ brand_color: zod.z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
3019
+ logo_url: zod.z.string().url().optional(),
3020
+ cast: zod.z.array(zod.z.object({ name: zod.z.string().min(1).max(40), image_url: zod.z.string().url() })).max(5).optional(),
3021
+ scenes: zod.z.number().int().min(1).max(15).optional(),
3022
+ score: zod.z.boolean().optional(),
3023
+ drama_id: zod.z.string().max(60).optional(),
3024
+ beats: zod.z.array(zod.z.number().int().min(1).max(15)).min(1).max(15).optional(),
3025
+ notes: zod.z.array(zod.z.string().max(300)).max(15).optional()
3026
+ });
3027
+ InputJsonSchema13 = {
3028
+ type: "object",
3029
+ properties: {
3030
+ action: {
3031
+ type: "string",
3032
+ enum: ["create", "produce", "status", "redo", "cut"],
3033
+ 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."
3034
+ },
3035
+ idea: { type: "string", description: "Create: the story in a line or two (who, the conflict, the twist)." },
3036
+ format: { type: "string", enum: ["9:16", "16:9"], description: "Create: vertical 9:16 (default) or widescreen 16:9." },
3037
+ mature: { type: "boolean", description: "Create: true for adult work (the server also detects it from the idea). Runs on the less-restricted engines." },
3038
+ kind: {
3039
+ type: "string",
3040
+ enum: [...KINDS],
3041
+ 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)."
3042
+ },
3043
+ look: { type: "string", enum: [...LOOKS2], description: "Create (optional): the visual style and genre. Default live-action (a micro-drama story type brings its own)." },
3044
+ story_type: {
3045
+ type: "string",
3046
+ enum: [...STORY_TYPES],
3047
+ 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."
3048
+ },
3049
+ title_card: { type: "boolean", description: "Create: the episode title over the opening hook. Default on." },
3050
+ name_tags: { type: "boolean", description: "Create: a name and role tag the first time each character speaks. Default on." },
3051
+ end_screen: { type: "boolean", description: 'Create: a 3.5-second "To be continued" end screen after the last line. Default on.' },
3052
+ end_text: { type: "string", description: `Create: the end screen's line, up to 60 characters. Default "Episode 2 coming soon".` },
3053
+ brand_color: { type: "string", description: "Create: accent colour for the graphics, #RRGGBB. Default a warm gold." },
3054
+ logo_url: { type: "string", description: "Create: a logo on the PixelDojo CDN for the end screen." },
3055
+ cast: {
3056
+ type: "array",
3057
+ items: {
3058
+ type: "object",
3059
+ properties: {
3060
+ name: { type: "string", description: "The character's name as the story uses it." },
3061
+ image_url: { type: "string", description: "A clear front-facing portrait on the PixelDojo CDN." }
3062
+ },
3063
+ required: ["name", "image_url"]
3064
+ },
3065
+ 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."
3066
+ },
3067
+ 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)." },
3068
+ 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)." },
3069
+ drama_id: { type: "string", description: "From create. Required for produce, status and redo." },
3070
+ beats: { type: "array", items: { type: "integer", minimum: 1, maximum: 15 }, description: "Redo: the beat numbers to film again." },
3071
+ notes: { type: "array", items: { type: "string" }, description: "Redo (optional): what to fix in each of those beats, in the same order." }
3072
+ }
3073
+ };
3074
+ errorText = (d, status2) => (typeof d.error === "string" ? d.error : d.error?.message) ?? `HTTP ${status2}`;
3075
+ STEP = {
3076
+ scripting: "writing the episode",
3077
+ keyframes: "making the cast portraits and the location",
3078
+ shots: "filming the beats",
3079
+ cutting: "cutting the episode"
3080
+ };
3081
+ DRAMA_OPTS = { tool: "pixeldojo_drama", idField: "drama_id", unit: "beat" };
3082
+ dramaSkill = {
3083
+ name: "pixeldojo_drama",
3084
+ 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).",
3085
+ inputZod: InputSchema13,
3086
+ inputJsonSchema: InputJsonSchema13,
3087
+ allowedModels: ["seedance-2-5", "seedream-4"],
3088
+ defaultModel: "seedance-2-5",
3089
+ async run(input, ctx) {
3090
+ if (input.action === "create") {
3091
+ if (!input.idea?.trim()) return { kind: "text", text: "Provide an `idea` (a line or two) to plan an episode." };
3092
+ const created = await dramaApi(ctx, "POST", "", {
3093
+ idea: input.idea.trim(),
3094
+ format: input.format,
3095
+ kind: input.kind,
3096
+ ...input.mature ? { mature: true } : {},
3097
+ ...input.look ? { look: input.look } : {},
3098
+ ...input.story_type ? { story_type: input.story_type } : {},
3099
+ ...input.scenes ? { scenes: input.scenes } : {},
3100
+ ...input.score === false ? { score: false } : {},
3101
+ ...Object.fromEntries(
3102
+ ["title_card", "name_tags", "end_screen", "end_text", "brand_color", "logo_url"].filter((k) => input[k] !== void 0).map((k) => [k, input[k]])
3103
+ ),
3104
+ ...input.cast?.length ? { cast: input.cast } : {},
3105
+ // Stop before the cut when the review flags a scene: the user decides, not the agent.
3106
+ review: true
3107
+ });
3108
+ if (!created.data.id) return { kind: "text", text: `Could not start the episode: ${errorText(created.data, created.status)}` };
3109
+ const planned = await waitForPlan2(ctx, created.data.id);
3110
+ return describeDrama(planned.data);
3111
+ }
3112
+ if (!input.drama_id) return { kind: "text", text: `${input.action} needs drama_id (from create).` };
3113
+ 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;
3114
+ if (!res) return { kind: "text", text: "redo needs beats (the beat numbers to film again)." };
3115
+ if (!res.data.id) return { kind: "text", text: errorText(res.data, res.status) };
3116
+ return describeDrama(res.data);
3117
+ }
3118
+ };
3119
+ }
3120
+ });
3121
+ async function api(ctx, method, path, body) {
3122
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/music-video${path}`, {
3123
+ method,
3124
+ headers: {
3125
+ Authorization: `Bearer ${ctx.apiKey}`,
3126
+ "Content-Type": "application/json",
3127
+ "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
3128
+ "x-pixeldojo-skill": "music_video"
3129
+ },
3130
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
3131
+ });
3132
+ return { status: res.status, data: await res.json().catch(() => ({})) };
3133
+ }
3134
+ var MUSIC_LOOKS, InputSchema14, InputJsonSchema14, errorText2, describe, musicVideoSkill;
3135
+ var init_music_video = __esm({
3136
+ "src/skills/music-video.ts"() {
3137
+ init_drama();
3138
+ MUSIC_LOOKS = [
3139
+ "live-action",
3140
+ "action",
3141
+ "anime",
3142
+ "3d-animation",
3143
+ "noir",
3144
+ "horror",
3145
+ "sci-fi",
3146
+ "documentary",
3147
+ "western",
3148
+ "romance",
3149
+ "romantic-comedy",
3150
+ "comedy",
3151
+ "rap-video",
3152
+ "neon-club",
3153
+ "stage-performance",
3154
+ "retro-camcorder",
3155
+ "y2k-pop",
3156
+ "moody-rnb",
3157
+ "rock-grit",
3158
+ "dream-pop",
3159
+ "k-pop"
3160
+ ];
3161
+ InputSchema14 = zod.z.object({
3162
+ action: zod.z.enum(["create", "produce", "status", "redo"]).default("create"),
3163
+ song_url: zod.z.string().url().optional(),
3164
+ idea: zod.z.string().min(8).max(1500).optional(),
3165
+ format: zod.z.enum(["9:16", "16:9"]).default("9:16"),
3166
+ mature: zod.z.boolean().optional(),
3167
+ look: zod.z.enum(MUSIC_LOOKS).optional(),
3168
+ start_sec: zod.z.number().min(0).optional(),
3169
+ length_sec: zod.z.number().int().min(10).max(60).optional(),
3170
+ artist: zod.z.string().max(60).optional(),
3171
+ song_title: zod.z.string().max(80).optional(),
3172
+ title_card: zod.z.boolean().optional(),
3173
+ end_screen: zod.z.boolean().optional(),
3174
+ end_text: zod.z.string().max(60).optional(),
3175
+ cast: zod.z.array(zod.z.object({ name: zod.z.string().min(1).max(40), image_url: zod.z.string().url() })).max(3).optional(),
3176
+ music_video_id: zod.z.string().max(60).optional(),
3177
+ beats: zod.z.array(zod.z.number().int().min(1).max(12)).min(1).max(12).optional(),
3178
+ notes: zod.z.array(zod.z.string().max(300)).max(12).optional()
3179
+ });
3180
+ InputJsonSchema14 = {
3181
+ type: "object",
3182
+ properties: {
3183
+ action: {
3184
+ type: "string",
3185
+ enum: ["create", "produce", "status", "redo"],
3186
+ 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."
3187
+ },
3188
+ song_url: { type: "string", description: "Create: the song on the PixelDojo CDN (attach or upload it first)." },
3189
+ idea: { type: "string", description: "Create: the story or performance concept in a line or two." },
3190
+ format: { type: "string", enum: ["9:16", "16:9"], description: "Create: vertical 9:16 (default) or widescreen 16:9." },
3191
+ mature: { type: "boolean", description: "Create: true for adult work (the server also detects it from the idea). Runs on the less-restricted engines." },
3192
+ look: { type: "string", enum: [...MUSIC_LOOKS], description: "Create (optional): the visual style, e.g. neon-club, rap-video, stage-performance, retro-camcorder, k-pop." },
3193
+ start_sec: { type: "number", description: "Create (optional): where in the song to start, in seconds. Default 0." },
3194
+ 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." },
3195
+ artist: { type: "string", description: "Create (optional): the artist name for the title card." },
3196
+ song_title: { type: "string", description: "Create (optional): the song title for the title card." },
3197
+ title_card: { type: "boolean", description: "Create: the song title over the opening. Default on." },
3198
+ end_screen: { type: "boolean", description: "Create: an end screen after the song. Default off." },
3199
+ end_text: { type: "string", description: "Create: the end screen's line, up to 60 characters." },
3200
+ cast: {
3201
+ type: "array",
3202
+ items: {
3203
+ type: "object",
3204
+ properties: {
3205
+ name: { type: "string", description: "The character's name; the first one is the performer." },
3206
+ image_url: { type: "string", description: "A clear front-facing portrait on the PixelDojo CDN." }
3207
+ },
3208
+ required: ["name", "image_url"]
3209
+ },
3210
+ description: "Create (optional): up to 3 characters, the performer first, each with a portrait on the PixelDojo CDN."
3211
+ },
3212
+ music_video_id: { type: "string", description: "From create. Required for produce, status and redo." },
3213
+ beats: { type: "array", items: { type: "integer", minimum: 1 }, description: "Redo: the section numbers to film again." },
3214
+ notes: { type: "array", items: { type: "string" }, description: "Redo (optional): what to fix in each of those sections, in the same order." }
3215
+ }
3216
+ };
3217
+ errorText2 = (d, status2) => (typeof d.error === "string" ? d.error : d.error?.message) ?? `HTTP ${status2}`;
3218
+ describe = (v) => describeDrama(v, { tool: "pixeldojo_music_video", idField: "music_video_id", unit: "section" });
3219
+ musicVideoSkill = {
3220
+ name: "pixeldojo_music_video",
3221
+ 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).",
3222
+ inputZod: InputSchema14,
3223
+ inputJsonSchema: InputJsonSchema14,
3224
+ allowedModels: ["p-video-2-pro-reference", "seedream-4"],
3225
+ defaultModel: "p-video-2-pro-reference",
3226
+ async run(input, ctx) {
3227
+ if (input.action === "create") {
3228
+ 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." };
3229
+ const { action: _a, music_video_id: _m, beats: _b, notes: _n, ...rest } = input;
3230
+ const created = await api(ctx, "POST", "", { ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== void 0)), idea: input.idea.trim() });
3231
+ if (!created.data.id) return { kind: "text", text: `Could not start the music video: ${errorText2(created.data, created.status)}` };
3232
+ const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
3233
+ let last = await api(ctx, "GET", `/${created.data.id}`);
3234
+ for (let i = 0; i < 8 && last.data.status === "scripting"; i += 1) {
3235
+ await sleep(3e3);
3236
+ last = await api(ctx, "GET", `/${created.data.id}`);
3237
+ }
3238
+ return describe(last.data);
3239
+ }
3240
+ if (!input.music_video_id) return { kind: "text", text: `${input.action} needs music_video_id (from create).` };
3241
+ const id = input.music_video_id;
3242
+ 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;
3243
+ if (!res) return { kind: "text", text: "redo needs beats (the section numbers to film again)." };
3244
+ if (!res.data.id) return { kind: "text", text: errorText2(res.data, res.status) };
3245
+ return describe(res.data);
3246
+ }
3247
+ };
3248
+ }
3249
+ });
3250
+ async function kataApi(ctx, method, path, body) {
3251
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/kata${path}`, {
3252
+ method,
3253
+ headers: {
3254
+ Authorization: `Bearer ${ctx.apiKey}`,
3255
+ "Content-Type": "application/json",
3256
+ "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
3257
+ "x-pixeldojo-skill": "kata"
3258
+ },
3259
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
3260
+ });
3261
+ const data = await res.json().catch(() => ({}));
3262
+ return { status: res.status, data };
3263
+ }
3264
+ function describeKata(v) {
3265
+ const next2 = (action) => `pixeldojo_kata with {"kata_id": "${v.id}", "action": "${action}"}`;
3266
+ if (v.status === "failed") return { kind: "text", text: `The Kata stopped: ${v.error ?? "unknown error"}. Anything that failed was refunded.` };
3267
+ if (v.status === "quote") {
3268
+ 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)}` : ""}`);
3269
+ const words = v.shots.filter((s) => s.text).map((s) => `${s.from.toFixed(1)}s "${s.text}"`);
3270
+ return {
3271
+ kind: "text",
3272
+ text: [
3273
+ `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:`,
3274
+ ...scenes,
3275
+ words.length ? `On screen: ${words.join(", ")}.` : "",
3276
+ `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.)`
3277
+ ].filter(Boolean).join("\n")
3278
+ };
3279
+ }
3280
+ if (v.status === "done" && v.final_url) {
3281
+ return { kind: "assets", text: `"${v.title}" is finished and saved to the Library: ${v.final_url}`, assetUrls: [v.final_url] };
3282
+ }
3283
+ return {
3284
+ kind: "text",
3285
+ text: `Still ${STEP2[v.status] ?? v.status}. It takes about 5 to 15 minutes in all. Call ${next2("status")} in about a minute.`,
3286
+ followUp: { input: { action: "status", kata_id: v.id }, afterSec: v.status === "analyzing" || v.status === "scripting" ? 20 : 45 }
3287
+ };
3288
+ }
3289
+ async function waitForPlan3(ctx, id) {
3290
+ const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
3291
+ let last = await kataApi(ctx, "GET", `/${id}`);
3292
+ for (let i = 0; i < 10 && (last.data.status === "analyzing" || last.data.status === "scripting"); i += 1) {
3293
+ await sleep(4e3);
3294
+ last = await kataApi(ctx, "GET", `/${id}`);
3295
+ }
3296
+ return last;
3297
+ }
3298
+ var InputSchema15, InputJsonSchema15, errorText3, STEP2, KIND, kataSkill;
3299
+ var init_kata = __esm({
3300
+ "src/skills/kata.ts"() {
3301
+ InputSchema15 = zod.z.object({
3302
+ action: zod.z.enum(["create", "produce", "status", "presets"]).default("create"),
3303
+ reference_url: zod.z.string().url().optional(),
3304
+ preset: zod.z.string().max(80).optional(),
3305
+ cast: zod.z.array(zod.z.object({ name: zod.z.string().min(1).max(40), image_url: zod.z.string().url().optional(), description: zod.z.string().max(200).optional() })).max(3).optional(),
3306
+ photos: zod.z.array(zod.z.object({ url: zod.z.string().url(), note: zod.z.string().max(80).optional() })).max(4).optional(),
3307
+ instructions: zod.z.string().max(1500).optional(),
3308
+ look: zod.z.string().max(80).optional(),
3309
+ kata_id: zod.z.string().max(60).optional()
3310
+ });
3311
+ InputJsonSchema15 = {
3312
+ type: "object",
3313
+ properties: {
3314
+ action: {
3315
+ type: "string",
3316
+ enum: ["create", "produce", "status", "presets"],
3317
+ 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."
3318
+ },
3319
+ 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." },
3320
+ preset: { type: "string", description: 'Create: a Kata library preset slug (from action "presets") instead of a reference.' },
3321
+ cast: {
3322
+ type: "array",
3323
+ items: {
3324
+ type: "object",
3325
+ properties: {
3326
+ name: { type: "string", description: "Their name. Without image_url, the saved Character Studio character of that name." },
3327
+ image_url: { type: "string", description: "A clear photo of them on the PixelDojo CDN." },
3328
+ description: { type: "string", description: "Optional: who they are, in a few words." }
3329
+ },
3330
+ required: ["name"]
3331
+ },
3332
+ description: 'Create: up to 3 people who replace the people in the edit. Use saved characters by name (pixeldojo_library source "characters").'
3333
+ },
3334
+ photos: {
3335
+ type: "array",
3336
+ items: { type: "object", properties: { url: { type: "string" }, note: { type: "string", description: 'What it is (e.g. "the product").' } }, required: ["url"] },
3337
+ description: "Create: up to 4 products or photos to put in the edit, on the PixelDojo CDN."
3338
+ },
3339
+ instructions: { type: "string", description: "Create (optional): what to change, e.g. the setting, the words on screen, where the product goes." },
3340
+ look: { type: "string", description: 'Create (optional): style transfer, one look for every new shot, e.g. "hand-drawn anime".' },
3341
+ kata_id: { type: "string", description: "From create. Required for produce and status." }
3342
+ }
3343
+ };
3344
+ errorText3 = (d, status2) => d.message ?? (typeof d.error === "string" ? d.error : d.error?.message) ?? `HTTP ${status2}`;
3345
+ STEP2 = {
3346
+ analyzing: "reading the edit cut by cut",
3347
+ scripting: "planning the remake",
3348
+ music: "scoring a new track on the same tempo",
3349
+ beats: "finding the beat",
3350
+ keyframes: "making the keyframes",
3351
+ animating: "filming the shots",
3352
+ cutting: "cutting it where the reference cuts",
3353
+ rendering: "setting the words and transitions",
3354
+ reviewing: "checking the final cut"
3355
+ };
3356
+ KIND = { clip: "new shot", still: "still", swap: "motion transfer", photo: "your photo" };
3357
+ kataSkill = {
3358
+ name: "pixeldojo_kata",
3359
+ 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.`,
3360
+ inputZod: InputSchema15,
3361
+ inputJsonSchema: InputJsonSchema15,
3362
+ allowedModels: ["seedance-2-5", "google-nano-banana", "kling-motion-control"],
3363
+ defaultModel: "seedance-2-5",
3364
+ async run(input, ctx) {
3365
+ if (input.action === "presets") {
3366
+ const res2 = await kataApi(ctx, "GET", "/presets");
3367
+ const list = res2.data.presets ?? [];
3368
+ if (!list.length) return { kind: "text", text: "The Kata library is empty right now. Recreate any edit with a reference_url." };
3369
+ return {
3370
+ kind: "text",
3371
+ 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")
3372
+ };
3373
+ }
3374
+ if (input.action === "create") {
3375
+ 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".' };
3376
+ const created = await kataApi(ctx, "POST", "", {
3377
+ ...input.preset ? { preset: input.preset } : { reference_url: input.reference_url },
3378
+ ...input.cast?.length ? { cast: input.cast } : {},
3379
+ ...input.photos?.length ? { photos: input.photos } : {},
3380
+ ...input.instructions ? { instructions: input.instructions } : {},
3381
+ ...input.look ? { look: input.look } : {}
3382
+ });
3383
+ if (!created.data.id) return { kind: "text", text: `Could not start the Kata: ${errorText3(created.data, created.status)}` };
3384
+ const planned = await waitForPlan3(ctx, created.data.id);
3385
+ return describeKata(planned.data);
3386
+ }
3387
+ if (!input.kata_id) return { kind: "text", text: `${input.action} needs kata_id (from create).` };
3388
+ const res = input.action === "status" ? await kataApi(ctx, "GET", `/${input.kata_id}`) : await kataApi(ctx, "POST", `/${input.kata_id}`, { action: "produce" });
3389
+ if (!res.data.id) return { kind: "text", text: errorText3(res.data, res.status) };
3390
+ return describeKata(res.data);
3391
+ }
3392
+ };
3393
+ }
3394
+ });
2922
3395
  async function run13(input, ctx) {
2923
3396
  const productImages = [
2924
3397
  ...input.product_image_urls ?? [],
@@ -2943,7 +3416,9 @@ async function run13(input, ctx) {
2943
3416
  quality: input.quality,
2944
3417
  resolution: input.resolution,
2945
3418
  aspect_ratio: input.aspect_ratio,
2946
- duration: input.duration
3419
+ duration: input.duration,
3420
+ reference_video_url: input.reference_video_url,
3421
+ brand_kit: input.brand_kit
2947
3422
  };
2948
3423
  const client = ctx.client;
2949
3424
  const submitted = await client.runAd(body);
@@ -2959,16 +3434,16 @@ async function run13(input, ctx) {
2959
3434
  const styleLabel = submitted.ad?.style ?? input.style ?? "ugc-testimonial";
2960
3435
  return pollResultToSkillResult(
2961
3436
  result,
2962
- (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]}`
3437
+ (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.`
2963
3438
  );
2964
3439
  }
2965
- var InputSchema13, InputJsonSchema13, adSkill;
3440
+ var InputSchema16, InputJsonSchema16, adSkill;
2966
3441
  var init_ad = __esm({
2967
3442
  "src/skills/ad.ts"() {
2968
3443
  init_types();
2969
3444
  init_poll();
2970
3445
  init_upload_helpers();
2971
- InputSchema13 = zod.z.object({
3446
+ InputSchema16 = zod.z.object({
2972
3447
  style: zod.z.string().max(80).optional().describe(
2973
3448
  "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."
2974
3449
  ),
@@ -2986,7 +3461,9 @@ var init_ad = __esm({
2986
3461
  quality: zod.z.enum(["mini", "fast", "high"]).optional(),
2987
3462
  resolution: zod.z.enum(["480p", "720p", "1080p", "4k"]).optional().describe('1080p/4k require quality "high".'),
2988
3463
  aspect_ratio: zod.z.enum(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]).optional(),
2989
- duration: zod.z.number().int().min(4).max(15).optional()
3464
+ duration: zod.z.number().int().min(4).max(15).optional(),
3465
+ reference_video_url: zod.z.string().url().optional(),
3466
+ brand_kit: zod.z.boolean().optional()
2990
3467
  }).refine(
2991
3468
  (v) => v.product_url || (v.product_image_urls?.length || v.product_image_paths?.length) && v.product_name,
2992
3469
  {
@@ -2994,7 +3471,7 @@ var init_ad = __esm({
2994
3471
  path: ["product_url"]
2995
3472
  }
2996
3473
  );
2997
- InputJsonSchema13 = {
3474
+ InputJsonSchema16 = {
2998
3475
  type: "object",
2999
3476
  properties: {
3000
3477
  style: {
@@ -3072,6 +3549,15 @@ var init_ad = __esm({
3072
3549
  minimum: 4,
3073
3550
  maximum: 15,
3074
3551
  description: "Ad length in seconds. Defaults to the style's length; clamped to what the engine allows."
3552
+ },
3553
+ reference_video_url: {
3554
+ type: "string",
3555
+ format: "uri",
3556
+ 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."
3557
+ },
3558
+ brand_kit: {
3559
+ type: "boolean",
3560
+ description: "The account's brand kit (palette, voice, selling points; see pixeldojo_brand_kit) rides on every ad. false skips it."
3075
3561
  }
3076
3562
  }
3077
3563
  // "product_url OR product_name+images" is enforced by the server and the
@@ -3081,8 +3567,8 @@ var init_ad = __esm({
3081
3567
  name: "pixeldojo_ad",
3082
3568
  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.",
3083
3569
  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.",
3084
- inputZod: InputSchema13,
3085
- inputJsonSchema: InputJsonSchema13,
3570
+ inputZod: InputSchema16,
3571
+ inputJsonSchema: InputJsonSchema16,
3086
3572
  // Composition skill — the server routes to the ad engine internally.
3087
3573
  allowedModels: [],
3088
3574
  defaultModel: "",
@@ -3117,10 +3603,10 @@ async function run14(input, ctx) {
3117
3603
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
3118
3604
  };
3119
3605
  }
3120
- var InputSchema14, InputJsonSchema14, campaignSkill;
3606
+ var InputSchema17, InputJsonSchema17, campaignSkill;
3121
3607
  var init_campaign = __esm({
3122
3608
  "src/skills/campaign.ts"() {
3123
- InputSchema14 = zod.z.object({
3609
+ InputSchema17 = zod.z.object({
3124
3610
  productUrl: zod.z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
3125
3611
  product: zod.z.object({
3126
3612
  name: zod.z.string().min(1).max(200),
@@ -3136,7 +3622,7 @@ var init_campaign = __esm({
3136
3622
  message: "productUrl or product is required",
3137
3623
  path: ["productUrl"]
3138
3624
  });
3139
- InputJsonSchema14 = {
3625
+ InputJsonSchema17 = {
3140
3626
  type: "object",
3141
3627
  properties: {
3142
3628
  productUrl: {
@@ -3187,8 +3673,8 @@ var init_campaign = __esm({
3187
3673
  campaignSkill = {
3188
3674
  name: "pixeldojo_campaign",
3189
3675
  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.",
3190
- inputZod: InputSchema14,
3191
- inputJsonSchema: InputJsonSchema14,
3676
+ inputZod: InputSchema17,
3677
+ inputJsonSchema: InputJsonSchema17,
3192
3678
  // Composition skill — internal sub-jobs route to per-tool models, no
3193
3679
  // single allowed-models list applies here.
3194
3680
  allowedModels: [],
@@ -3227,13 +3713,13 @@ async function run15(input, ctx) {
3227
3713
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
3228
3714
  };
3229
3715
  }
3230
- var InputSchema15, InputJsonSchema15, campaignStatusSkill;
3716
+ var InputSchema18, InputJsonSchema18, campaignStatusSkill;
3231
3717
  var init_campaign_status = __esm({
3232
3718
  "src/skills/campaign-status.ts"() {
3233
- InputSchema15 = zod.z.object({
3719
+ InputSchema18 = zod.z.object({
3234
3720
  campaignId: zod.z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo_campaign handoff.")
3235
3721
  });
3236
- InputJsonSchema15 = {
3722
+ InputJsonSchema18 = {
3237
3723
  type: "object",
3238
3724
  properties: {
3239
3725
  campaignId: {
@@ -3246,8 +3732,8 @@ var init_campaign_status = __esm({
3246
3732
  campaignStatusSkill = {
3247
3733
  name: "pixeldojo_campaign_status",
3248
3734
  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.",
3249
- inputZod: InputSchema15,
3250
- inputJsonSchema: InputJsonSchema15,
3735
+ inputZod: InputSchema18,
3736
+ inputJsonSchema: InputJsonSchema18,
3251
3737
  allowedModels: [],
3252
3738
  defaultModel: "",
3253
3739
  run: run15
@@ -3270,16 +3756,16 @@ async function run16(input, ctx) {
3270
3756
  (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.)`
3271
3757
  };
3272
3758
  }
3273
- var InputSchema16, InputJsonSchema16, uploadSkill;
3759
+ var InputSchema19, InputJsonSchema19, uploadSkill;
3274
3760
  var init_upload = __esm({
3275
3761
  "src/skills/upload.ts"() {
3276
3762
  init_upload_helpers();
3277
- InputSchema16 = zod.z.object({
3763
+ InputSchema19 = zod.z.object({
3278
3764
  path: zod.z.string().min(1).max(4096).describe(
3279
3765
  "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."
3280
3766
  )
3281
3767
  });
3282
- InputJsonSchema16 = {
3768
+ InputJsonSchema19 = {
3283
3769
  type: "object",
3284
3770
  properties: {
3285
3771
  path: {
@@ -3292,8 +3778,8 @@ var init_upload = __esm({
3292
3778
  uploadSkill = {
3293
3779
  name: "pixeldojo_upload",
3294
3780
  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.",
3295
- inputZod: InputSchema16,
3296
- inputJsonSchema: InputJsonSchema16,
3781
+ inputZod: InputSchema19,
3782
+ inputJsonSchema: InputJsonSchema19,
3297
3783
  allowedModels: [],
3298
3784
  defaultModel: "",
3299
3785
  run: run16
@@ -3338,14 +3824,14 @@ async function run17(input, ctx) {
3338
3824
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
3339
3825
  );
3340
3826
  }
3341
- var ALLOWED4, DEFAULT_MODEL6, InputSchema17, InputJsonSchema17, audioSkill;
3827
+ var ALLOWED4, DEFAULT_MODEL6, InputSchema20, InputJsonSchema20, audioSkill;
3342
3828
  var init_audio = __esm({
3343
3829
  "src/skills/audio.ts"() {
3344
3830
  init_poll();
3345
3831
  init_types();
3346
3832
  ALLOWED4 = ["seed-audio", "text-to-speech"];
3347
3833
  DEFAULT_MODEL6 = "seed-audio";
3348
- InputSchema17 = zod.z.object({
3834
+ InputSchema20 = zod.z.object({
3349
3835
  prompt: zod.z.string().min(1).describe(
3350
3836
  "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."
3351
3837
  ),
@@ -3355,7 +3841,7 @@ var init_audio = __esm({
3355
3841
  output_format: zod.z.string().optional().describe("Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."),
3356
3842
  speed: zod.z.number().min(0.5).max(2).optional().describe("Speech speed multiplier, 0.5-2 (default 1). Both models.")
3357
3843
  });
3358
- InputJsonSchema17 = {
3844
+ InputJsonSchema20 = {
3359
3845
  type: "object",
3360
3846
  properties: {
3361
3847
  prompt: {
@@ -3393,8 +3879,8 @@ var init_audio = __esm({
3393
3879
  audioSkill = {
3394
3880
  name: "pixeldojo_audio",
3395
3881
  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.",
3396
- inputZod: InputSchema17,
3397
- inputJsonSchema: InputJsonSchema17,
3882
+ inputZod: InputSchema20,
3883
+ inputJsonSchema: InputJsonSchema20,
3398
3884
  allowedModels: [...ALLOWED4],
3399
3885
  defaultModel: DEFAULT_MODEL6,
3400
3886
  run: run17
@@ -3452,17 +3938,17 @@ ${lines.join("\n")}
3452
3938
  Pass any URL back into pixeldojo_generate, pixeldojo_edit, pixeldojo_character, or pixeldojo_upscale as a reference input.`
3453
3939
  };
3454
3940
  }
3455
- var InputSchema18, InputJsonSchema18, librarySkill;
3941
+ var InputSchema21, InputJsonSchema21, librarySkill;
3456
3942
  var init_library = __esm({
3457
3943
  "src/skills/library.ts"() {
3458
- InputSchema18 = zod.z.object({
3944
+ InputSchema21 = zod.z.object({
3459
3945
  source: zod.z.enum(["media", "characters"]).optional().describe("media (default): saved and recent results. characters: saved Character Studio characters and elements, usable as @Name in prompts."),
3460
3946
  q: zod.z.string().optional().describe("Substring match against the prompt that produced the asset."),
3461
3947
  model: zod.z.string().optional().describe("Restrict to a single model (apiId)."),
3462
3948
  modality: zod.z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
3463
3949
  limit: zod.z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
3464
3950
  });
3465
- InputJsonSchema18 = {
3951
+ InputJsonSchema21 = {
3466
3952
  type: "object",
3467
3953
  properties: {
3468
3954
  source: {
@@ -3487,8 +3973,8 @@ var init_library = __esm({
3487
3973
  librarySkill = {
3488
3974
  name: "pixeldojo_library",
3489
3975
  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.",
3490
- inputZod: InputSchema18,
3491
- inputJsonSchema: InputJsonSchema18,
3976
+ inputZod: InputSchema21,
3977
+ inputJsonSchema: InputJsonSchema21,
3492
3978
  allowedModels: [],
3493
3979
  defaultModel: "",
3494
3980
  run: run18
@@ -3509,17 +3995,17 @@ async function run19(input, ctx) {
3509
3995
  It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
3510
3996
  };
3511
3997
  }
3512
- var InputSchema19, InputJsonSchema19, saveSkill;
3998
+ var InputSchema22, InputJsonSchema22, saveSkill;
3513
3999
  var init_save = __esm({
3514
4000
  "src/skills/save.ts"() {
3515
- InputSchema19 = zod.z.object({
4001
+ InputSchema22 = zod.z.object({
3516
4002
  url: zod.z.string().url().describe("The asset URL to save (image or video)."),
3517
4003
  prompt: zod.z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
3518
4004
  tool_name: zod.z.string().optional().describe("Tool/model slug that produced it."),
3519
4005
  modality: zod.z.enum(["image", "video", "audio"]).optional(),
3520
4006
  job_id: zod.z.string().optional().describe("The jobId that produced this asset, when known.")
3521
4007
  });
3522
- InputJsonSchema19 = {
4008
+ InputJsonSchema22 = {
3523
4009
  type: "object",
3524
4010
  properties: {
3525
4011
  url: { type: "string", format: "uri", description: "The result URL to keep (image or video from a PixelDojo job)." },
@@ -3533,8 +4019,8 @@ var init_save = __esm({
3533
4019
  saveSkill = {
3534
4020
  name: "pixeldojo_save",
3535
4021
  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.`,
3536
- inputZod: InputSchema19,
3537
- inputJsonSchema: InputJsonSchema19,
4022
+ inputZod: InputSchema22,
4023
+ inputJsonSchema: InputJsonSchema22,
3538
4024
  allowedModels: [],
3539
4025
  defaultModel: "",
3540
4026
  run: run19
@@ -3553,21 +4039,21 @@ async function run20(input, ctx) {
3553
4039
  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.`
3554
4040
  };
3555
4041
  }
3556
- var KINDS, InputSchema20, InputJsonSchema20, saveCharacterSkill;
4042
+ var KINDS2, InputSchema23, InputJsonSchema23, saveCharacterSkill;
3557
4043
  var init_save_character = __esm({
3558
4044
  "src/skills/save-character.ts"() {
3559
- KINDS = ["character", "product", "outfit", "location", "object", "style"];
3560
- InputSchema20 = zod.z.object({
4045
+ KINDS2 = ["character", "product", "outfit", "location", "object", "style"];
4046
+ InputSchema23 = zod.z.object({
3561
4047
  name: zod.z.string().min(1).max(60).describe("What to call it. Used as @Name in prompts."),
3562
- kind: zod.z.enum(KINDS).optional().describe("Default character."),
4048
+ kind: zod.z.enum(KINDS2).optional().describe("Default character."),
3563
4049
  image_urls: zod.z.array(zod.z.string().url()).max(8).optional().describe("3 to 5 photos from different angles works best for a character."),
3564
4050
  description: zod.z.string().max(1500).optional().describe("Appearance notes, or the scene text for an element.")
3565
4051
  });
3566
- InputJsonSchema20 = {
4052
+ InputJsonSchema23 = {
3567
4053
  type: "object",
3568
4054
  properties: {
3569
4055
  name: { type: "string", description: "What to call it. Used as @Name in prompts afterwards." },
3570
- kind: { type: "string", enum: KINDS, description: "character (default), product, outfit, location, object (a prop) or style." },
4056
+ kind: { type: "string", enum: KINDS2, description: "character (default), product, outfit, location, object (a prop) or style." },
3571
4057
  image_urls: {
3572
4058
  type: "array",
3573
4059
  items: { type: "string", format: "uri" },
@@ -3581,8 +4067,8 @@ var init_save_character = __esm({
3581
4067
  saveCharacterSkill = {
3582
4068
  name: "pixeldojo_save_character",
3583
4069
  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.",
3584
- inputZod: InputSchema20,
3585
- inputJsonSchema: InputJsonSchema20,
4070
+ inputZod: InputSchema23,
4071
+ inputJsonSchema: InputJsonSchema23,
3586
4072
  allowedModels: [],
3587
4073
  defaultModel: "",
3588
4074
  run: run20
@@ -3607,7 +4093,7 @@ async function listRun(input, ctx) {
3607
4093
  }
3608
4094
  const lines = effects.map((e) => {
3609
4095
  const ins = e.inputs.map(
3610
- (i) => i.kind === "image" ? `photo "${i.label}"` : `text "${i.label}"${i.options ? ` [${i.options.join(" | ")}]` : ""}${i.optional ? " (optional)" : ""}`
4096
+ (i) => i.kind === "image" ? `photo "${i.label}"${i.optional ? " (optional)" : ""}` : `text "${i.label}"${i.options ? ` [${i.options.join(" | ")}]` : ""}${i.optional ? " (optional)" : ""}`
3611
4097
  );
3612
4098
  return `- ${e.slug} (${e.family}, ${e.output}, ${priceOf(e)}): ${e.title}. ${e.tagline} Inputs: ${ins.join(", ")}.`;
3613
4099
  });
@@ -3731,6 +4217,446 @@ var init_effects = __esm({
3731
4217
  };
3732
4218
  }
3733
4219
  });
4220
+ async function call2(ctx, skill, path, init = {}) {
4221
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}${path}`, {
4222
+ method: init.method ?? "GET",
4223
+ headers: headers2(ctx, skill),
4224
+ ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {}
4225
+ });
4226
+ const data = await res.json().catch(() => ({}));
4227
+ const err = data.error;
4228
+ return { ok: res.ok, data, error: typeof err === "string" ? err : err?.message ?? `HTTP ${res.status}` };
4229
+ }
4230
+ async function kitRun(input, ctx) {
4231
+ const path = "/api/v1/marketing/brand-kit";
4232
+ if (input.action === "import") {
4233
+ if (!input.url) return { kind: "text", text: "Pass url: the brand's website, like yourbrand.com." };
4234
+ const r2 = await call2(ctx, "pixeldojo_brand_kit", path, { method: "POST", body: { url: input.url, save: input.save } });
4235
+ if (!r2.ok) return { kind: "text", text: `Brand kit not imported: ${r2.error}` };
4236
+ return { kind: "text", text: `${r2.data.saved === false ? "Imported (not saved)." : "Imported and saved."}
4237
+ ${describeKit(r2.data.brand_kit)}` };
4238
+ }
4239
+ if (input.action === "update") {
4240
+ const patch = {
4241
+ ...input.name !== void 0 ? { name: input.name } : {},
4242
+ ...input.tagline !== void 0 ? { tagline: input.tagline } : {},
4243
+ ...input.voice !== void 0 ? { voice: input.voice } : {},
4244
+ ...input.colors ? { colors: input.colors.map((c) => c.trim().toLowerCase()) } : {},
4245
+ ...input.fonts ? { fonts: input.fonts } : {},
4246
+ ...input.selling_points ? { sellingPoints: input.selling_points } : {},
4247
+ ...input.logo_url ? { logoUrl: input.logo_url } : {}
4248
+ };
4249
+ const r2 = await call2(ctx, "pixeldojo_brand_kit", path, { method: "PATCH", body: patch });
4250
+ if (!r2.ok) return { kind: "text", text: `Brand kit not updated: ${r2.error}` };
4251
+ return { kind: "text", text: `Updated.
4252
+ ${describeKit(r2.data.brand_kit)}` };
4253
+ }
4254
+ const r = await call2(ctx, "pixeldojo_brand_kit", path);
4255
+ if (!r.ok) return { kind: "text", text: `Could not read the brand kit: ${r.error}` };
4256
+ return { kind: "text", text: describeKit(r.data.brand_kit ?? null) };
4257
+ }
4258
+ async function templatesRun(input, ctx) {
4259
+ const q = new URLSearchParams();
4260
+ if (input.kind) q.set("kind", input.kind);
4261
+ if (input.category) q.set("category", input.category);
4262
+ const r = await call2(ctx, "pixeldojo_marketing", `/api/v1/marketing/templates?${q}`);
4263
+ if (!r.ok) return { kind: "text", text: `Could not list templates: ${r.error}` };
4264
+ const rows = r.data.templates ?? [];
4265
+ const lines = rows.map((t) => {
4266
+ const how = t.kind === "image" ? "action image" : t.kind === "motion" ? `action motion, fields ${t.fields?.join(", ")}, ${t.credits} credits` : "pixeldojo_ad style";
4267
+ return `- ${t.id} (${t.label}, ${t.category}): ${t.name}. ${how}. Example: ${t.preview ?? "none"}`;
4268
+ });
4269
+ const engines = r.data.image_engines ?? [];
4270
+ return {
4271
+ kind: "text",
4272
+ text: [
4273
+ `${rows.length} templates. Image templates run with action image (template = id), motion templates with action motion, video ad styles with pixeldojo_ad (style = id).`,
4274
+ ...lines,
4275
+ "",
4276
+ `Image engines: ${engines.map((e) => `${e.id} ${e.credits_per_image} credits per image (${e.note})`).join("; ")}.`,
4277
+ ...r.data.notes ?? []
4278
+ ].join("\n")
4279
+ };
4280
+ }
4281
+ async function imageRun(input, ctx) {
4282
+ const local = input.product_image_paths?.length ? await uploadLocalFiles(input.product_image_paths, ctx) : [];
4283
+ const images = [...input.product_image_urls ?? [], ...local];
4284
+ if (!input.product_url && !(input.product_name && images.length)) {
4285
+ return { kind: "text", text: "Pass product_url, or product_name plus product_image_urls (or product_image_paths)." };
4286
+ }
4287
+ const r = await call2(ctx, "pixeldojo_marketing", "/api/v1/marketing/image", {
4288
+ method: "POST",
4289
+ body: {
4290
+ template: input.template,
4291
+ product_url: input.product_url,
4292
+ ...!input.product_url ? { product: { name: input.product_name, description: input.product_description, images } } : {},
4293
+ character_images: input.character_image_urls,
4294
+ headline: input.headline,
4295
+ points: input.points,
4296
+ cta: input.cta,
4297
+ direction: input.direction,
4298
+ aspect_ratio: input.aspect_ratio,
4299
+ engine: input.engine,
4300
+ count: input.count,
4301
+ reference_ad_url: input.reference_ad_url,
4302
+ brand_kit: input.brand_kit
4303
+ }
4304
+ });
4305
+ if (!r.ok) return { kind: "text", text: `Images not started: ${r.error}` };
4306
+ const jobs = r.data.jobs ?? [];
4307
+ const polled = await Promise.all(
4308
+ jobs.map(
4309
+ (j) => pollJobUntilDoneOrHandoff(j.jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
4310
+ now: ctx.pollOverrides?.now,
4311
+ sleep: ctx.pollOverrides?.sleep
4312
+ })
4313
+ )
4314
+ );
4315
+ const urls = polled.flatMap((p) => p.kind === "completed" ? extractAssetUrls(p.job) : []);
4316
+ const pending = polled.filter((p) => p.kind === "handoff").map((p) => p.jobId);
4317
+ const failed = polled.filter((p) => p.kind === "failed").length;
4318
+ const credits = jobs.reduce((n, j) => n + (j.creditCost ?? 0), 0);
4319
+ 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` : ""}.`;
4320
+ if (pending.length === 1 && !urls.length) {
4321
+ return { kind: "handoff", text: `${head} Check it with pixeldojo_status.`, jobId: pending[0], statusUrl: ctx.buildStatusUrl(pending[0]) };
4322
+ }
4323
+ const tail = pending.length ? ` Still rendering: ${pending.join(", ")} (check each with pixeldojo_status).` : "";
4324
+ if (urls.length) return { kind: "assets", text: `${head}${tail}`, assetUrls: urls };
4325
+ return { kind: "text", text: `${head}${tail}` };
4326
+ }
4327
+ async function settleMotion(view, ctx) {
4328
+ const now = ctx.pollOverrides?.now ?? Date.now;
4329
+ const sleep = ctx.pollOverrides?.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
4330
+ const until = now() + 7e4;
4331
+ let v = view;
4332
+ while (v.status === "processing" && now() < until) {
4333
+ await sleep(5e3);
4334
+ const r = await call2(ctx, "pixeldojo_marketing", `/api/v1/marketing/motion/${encodeURIComponent(v.id)}`);
4335
+ if (r.ok) v = r.data;
4336
+ }
4337
+ if (v.status === "completed" && v.video_url) {
4338
+ 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] };
4339
+ }
4340
+ if (v.status === "failed") return { kind: "text", text: `Render failed, nothing charged: ${v.error}` };
4341
+ return {
4342
+ kind: "text",
4343
+ text: `Still rendering (${v.step}). Check it with pixeldojo_marketing action status motion_id ${v.id} in about 15 seconds.`,
4344
+ followUp: { input: { action: "status", motion_id: v.id }, afterSec: 15 }
4345
+ };
4346
+ }
4347
+ async function motionRun(input, ctx) {
4348
+ if (input.action === "status") {
4349
+ if (!input.motion_id) return { kind: "text", text: "Pass motion_id." };
4350
+ const r2 = await call2(ctx, "pixeldojo_marketing", `/api/v1/marketing/motion/${encodeURIComponent(input.motion_id)}`);
4351
+ if (!r2.ok) return { kind: "text", text: `Could not read that render: ${r2.error}` };
4352
+ return settleMotion(r2.data, ctx);
4353
+ }
4354
+ const endCard = input.action === "end_card";
4355
+ 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);
4356
+ const r = await call2(ctx, "pixeldojo_marketing", "/api/v1/marketing/motion", {
4357
+ method: "POST",
4358
+ body: {
4359
+ template: endCard ? "end-card" : input.template,
4360
+ aspect_ratio: input.aspect_ratio === "4:5" ? "9:16" : input.aspect_ratio,
4361
+ headline: input.headline,
4362
+ points: input.points,
4363
+ cta: input.cta,
4364
+ stat: input.stat,
4365
+ stat_label: input.stat_label,
4366
+ price_was: input.price_was,
4367
+ price_now: input.price_now,
4368
+ product_image_url: product,
4369
+ video_url: input.video_url,
4370
+ brand_name: input.brand_name,
4371
+ colors: input.colors?.map((c) => c.trim().toLowerCase()),
4372
+ brand_kit: input.brand_kit
4373
+ }
4374
+ });
4375
+ if (!r.ok) return { kind: "text", text: `Not started: ${r.error}` };
4376
+ return settleMotion(r.data, ctx);
4377
+ }
4378
+ async function marketingRun(input, ctx) {
4379
+ if (input.action === "templates") return templatesRun(input, ctx);
4380
+ if (input.action === "image") return imageRun(input, ctx);
4381
+ return motionRun(input, ctx);
4382
+ }
4383
+ var headers2, KitSchema, describeKit, brandKitSkill, ACTIONS, MOTION, MarketingSchema, props, marketingSkill;
4384
+ var init_marketing = __esm({
4385
+ "src/skills/marketing.ts"() {
4386
+ init_poll();
4387
+ init_upload_helpers();
4388
+ init_types();
4389
+ headers2 = (ctx, skill) => ({
4390
+ Authorization: `Bearer ${ctx.apiKey}`,
4391
+ "Content-Type": "application/json",
4392
+ "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
4393
+ "x-pixeldojo-skill": skill
4394
+ });
4395
+ KitSchema = zod.z.object({
4396
+ action: zod.z.enum(["get", "import", "update"]).optional(),
4397
+ url: zod.z.string().max(2048).optional(),
4398
+ save: zod.z.boolean().optional(),
4399
+ name: zod.z.string().max(60).optional(),
4400
+ tagline: zod.z.string().max(80).optional(),
4401
+ voice: zod.z.string().max(240).optional(),
4402
+ colors: zod.z.array(zod.z.string()).max(5).optional(),
4403
+ fonts: zod.z.array(zod.z.string()).max(2).optional(),
4404
+ selling_points: zod.z.array(zod.z.string()).max(5).optional(),
4405
+ logo_url: zod.z.string().url().optional()
4406
+ });
4407
+ describeKit = (k) => !k ? "No brand kit yet. Import one from the website with action import and url." : [
4408
+ `Brand kit: ${k.name || "(no name)"}${k.url ? ` (${k.url})` : ""}`,
4409
+ `Logo: ${k.logoUrl ?? "none (add logo_url, a PixelDojo upload)"}`,
4410
+ `Colors: ${k.colors.join(" ") || "none"}`,
4411
+ `Fonts: ${k.fonts.join(", ") || "none"}`,
4412
+ `Tagline: ${k.tagline || "none"}`,
4413
+ `Voice: ${k.voice || "none"}`,
4414
+ `Selling points: ${k.sellingPoints.join("; ") || "none"}`,
4415
+ "Every ad, campaign, image and motion template uses it unless that call passes brand_kit: false."
4416
+ ].join("\n");
4417
+ brandKitSkill = {
4418
+ name: "pixeldojo_brand_kit",
4419
+ 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.",
4420
+ inputZod: KitSchema,
4421
+ inputJsonSchema: {
4422
+ type: "object",
4423
+ properties: {
4424
+ action: { type: "string", enum: ["get", "import", "update"], description: "Default get." },
4425
+ url: { type: "string", description: "import: the brand website, e.g. yourbrand.com." },
4426
+ save: { type: "boolean", description: "import: false returns the kit without saving it. Default true." },
4427
+ name: { type: "string" },
4428
+ tagline: { type: "string" },
4429
+ voice: { type: "string", description: "How the brand talks, one or two sentences." },
4430
+ colors: { type: "array", items: { type: "string" }, maxItems: 5, description: "#RRGGBB, lead color first." },
4431
+ fonts: { type: "array", items: { type: "string" }, maxItems: 2 },
4432
+ selling_points: { type: "array", items: { type: "string" }, maxItems: 5, description: "Short product claims, 2-7 words each." },
4433
+ logo_url: { type: "string", format: "uri", description: "A logo on PixelDojo (upload it first); PNG with transparency is best." }
4434
+ }
4435
+ },
4436
+ allowedModels: [],
4437
+ defaultModel: "",
4438
+ run: kitRun
4439
+ };
4440
+ ACTIONS = ["templates", "image", "motion", "end_card", "status"];
4441
+ MOTION = ["kinetic-headline", "feature-callouts", "count-up", "price-drop", "logo-sting"];
4442
+ MarketingSchema = zod.z.object({
4443
+ action: zod.z.enum(ACTIONS),
4444
+ kind: zod.z.enum(["image", "motion", "video"]).optional(),
4445
+ category: zod.z.string().max(40).optional(),
4446
+ template: zod.z.string().max(60).optional(),
4447
+ product_url: zod.z.string().url().optional(),
4448
+ product_name: zod.z.string().max(200).optional(),
4449
+ product_description: zod.z.string().max(2e3).optional(),
4450
+ product_image_urls: zod.z.array(zod.z.string().url()).max(5).optional(),
4451
+ product_image_paths: zod.z.array(zod.z.string()).max(5).optional(),
4452
+ character_image_urls: zod.z.array(zod.z.string().url()).max(2).optional(),
4453
+ headline: zod.z.string().max(80).optional(),
4454
+ points: zod.z.array(zod.z.string().max(70)).max(3).optional(),
4455
+ cta: zod.z.string().max(30).optional(),
4456
+ direction: zod.z.string().max(1500).optional(),
4457
+ aspect_ratio: zod.z.enum(["1:1", "4:5", "9:16", "16:9"]).optional(),
4458
+ engine: zod.z.enum(["nano-banana-pro", "nano-banana-2-1", "ideogram-4-5"]).optional(),
4459
+ count: zod.z.number().int().min(1).max(8).optional(),
4460
+ reference_ad_url: zod.z.string().url().optional(),
4461
+ stat: zod.z.string().max(16).optional(),
4462
+ stat_label: zod.z.string().max(40).optional(),
4463
+ price_was: zod.z.string().max(16).optional(),
4464
+ price_now: zod.z.string().max(16).optional(),
4465
+ video_url: zod.z.string().url().optional(),
4466
+ motion_id: zod.z.string().max(80).optional(),
4467
+ brand_name: zod.z.string().max(60).optional(),
4468
+ colors: zod.z.array(zod.z.string()).max(5).optional(),
4469
+ brand_kit: zod.z.boolean().optional()
4470
+ });
4471
+ props = {
4472
+ action: {
4473
+ type: "string",
4474
+ enum: ACTIONS,
4475
+ 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)."
4476
+ },
4477
+ kind: { type: "string", enum: ["image", "motion", "video"], description: "templates: filter." },
4478
+ category: { type: "string", description: "templates: product-shot, motion, ugc, tiktok, ads, posters or marketplace." },
4479
+ template: {
4480
+ type: "string",
4481
+ description: `image: an image template id from templates (e.g. stacked-shout, pure-white, ice-float). motion: ${MOTION.join(", ")}.`
4482
+ },
4483
+ product_url: { type: "string", format: "uri", description: "image: product page; name, description and photos are read from it." },
4484
+ product_name: { type: "string" },
4485
+ product_description: { type: "string" },
4486
+ product_image_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 5, description: "Product photos. motion uses the first, cut out automatically." },
4487
+ product_image_paths: { type: "array", items: { type: "string" }, maxItems: 5, description: "Local product photos instead of URLs; uploaded first." },
4488
+ 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." },
4489
+ headline: { type: "string", description: "Exact words on the ad. Blank uses the brand kit tagline." },
4490
+ points: { type: "array", items: { type: "string" }, maxItems: 3, description: "Selling points on the ad. Blank uses the brand kit." },
4491
+ cta: { type: "string", description: 'Button text. Default "Shop now".' },
4492
+ direction: { type: "string", description: "image: extra art direction added to the template." },
4493
+ 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)." },
4494
+ 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." },
4495
+ count: { type: "integer", minimum: 1, maximum: 8, description: "image: how many (each charged)." },
4496
+ 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." },
4497
+ stat: { type: "string", description: 'count-up: the number, e.g. "10,000+", "4.9", "98%".' },
4498
+ stat_label: { type: "string", description: "count-up: what it counts." },
4499
+ price_was: { type: "string", description: 'price-drop: old price, e.g. "$140".' },
4500
+ price_now: { type: "string", description: 'price-drop: new price, e.g. "$98".' },
4501
+ video_url: { type: "string", format: "uri", description: "end_card: the finished ad (a PixelDojo result)." },
4502
+ motion_id: { type: "string", description: "status: the id a motion or end_card call returned." },
4503
+ brand_name: { type: "string", description: "motion: the brand shown when there is no logo. Default: the brand kit." },
4504
+ colors: { type: "array", items: { type: "string" }, maxItems: 5, description: "motion: #RRGGBB, lead color first. Default: the brand kit." },
4505
+ brand_kit: { type: "boolean", description: "false skips the brand kit for this call." }
4506
+ };
4507
+ marketingSkill = {
4508
+ name: "pixeldojo_marketing",
4509
+ 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.",
4510
+ inputZod: MarketingSchema,
4511
+ inputJsonSchema: { type: "object", required: ["action"], properties: props },
4512
+ allowedModels: [],
4513
+ defaultModel: "",
4514
+ run: marketingRun
4515
+ };
4516
+ }
4517
+ });
4518
+ async function pollToResult(jobId, ctx, successText) {
4519
+ const result = await pollJobUntilDoneOrHandoff(jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
4520
+ now: ctx.pollOverrides?.now,
4521
+ sleep: ctx.pollOverrides?.sleep
4522
+ });
4523
+ return pollResultToSkillResult(result, (urls) => urls.length === 0 ? `Job ${jobId} completed but returned no assets.` : successText(urls));
4524
+ }
4525
+ async function buildRun(input, ctx) {
4526
+ const doFetch = ctx.fetch ?? fetch;
4527
+ if (input.action === "traits") {
4528
+ const res2 = await doFetch(`${ctx.baseUrl}/api/v1/characters/build`, { headers: headers3(ctx, "pixeldojo_build_character") });
4529
+ const data2 = await res2.json().catch(() => ({}));
4530
+ if (!res2.ok || !data2.traits) return { kind: "text", text: `Could not list traits: ${data2.error ?? `HTTP ${res2.status}`}` };
4531
+ return {
4532
+ kind: "text",
4533
+ text: [
4534
+ `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.`,
4535
+ ...data2.traits.map(
4536
+ (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(", ")}`
4537
+ )
4538
+ ].join("\n")
4539
+ };
4540
+ }
4541
+ const res = await doFetch(`${ctx.baseUrl}/api/v1/characters/build`, {
4542
+ method: "POST",
4543
+ headers: headers3(ctx, "pixeldojo_build_character"),
4544
+ body: JSON.stringify({
4545
+ ...input.picks ? { picks: input.picks } : {},
4546
+ ...input.randomize ? { randomize: true } : {},
4547
+ ...input.details ? { details: input.details } : {},
4548
+ ...input.takes ? { takes: input.takes } : {}
4549
+ })
4550
+ });
4551
+ const data = await res.json().catch(() => ({}));
4552
+ if (!res.ok || !data.jobId) {
4553
+ const err = typeof data.error === "string" ? data.error : data.error?.message;
4554
+ return { kind: "text", text: `Character not built (nothing charged): ${err ?? `HTTP ${res.status}`}` };
4555
+ }
4556
+ return pollToResult(
4557
+ data.jobId,
4558
+ ctx,
4559
+ (urls) => [
4560
+ `${urls.length} take${urls.length === 1 ? "" : "s"} of ${data.summary ?? "the character"} (${data.creditCost ?? "?"} credits):`,
4561
+ ...urls.map((u, i) => `${i + 1}. ${u}`),
4562
+ `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.`
4563
+ ].join("\n")
4564
+ );
4565
+ }
4566
+ async function performRun(input, ctx) {
4567
+ const doFetch = ctx.fetch ?? fetch;
4568
+ if (input.action === "list") {
4569
+ const res2 = await doFetch(`${ctx.baseUrl}/api/v1/characters/perform`, { headers: headers3(ctx, "pixeldojo_perform") });
4570
+ const data2 = await res2.json().catch(() => ({}));
4571
+ if (!res2.ok || !data2.presets) return { kind: "text", text: `Could not list presets: ${data2.error ?? `HTTP ${res2.status}`}` };
4572
+ return {
4573
+ kind: "text",
4574
+ text: [
4575
+ 'Perform presets (a 5 s video of a saved character doing it). Run one with action "run", character and preset:',
4576
+ ...data2.presets.map((p) => `- ${p.slug} (${p.group}, ${p.credits} credits): ${p.title}. ${p.tagline}`)
4577
+ ].join("\n")
4578
+ };
4579
+ }
4580
+ if (!input.character || !input.preset) {
4581
+ return { kind: "text", text: `Pass character (a saved character's name or @Name) and preset (from action "list").` };
4582
+ }
4583
+ const res = await doFetch(`${ctx.baseUrl}/api/v1/characters/perform`, {
4584
+ method: "POST",
4585
+ headers: headers3(ctx, "pixeldojo_perform"),
4586
+ body: JSON.stringify({ character: input.character, preset: input.preset })
4587
+ });
4588
+ const data = await res.json().catch(() => ({}));
4589
+ if (!res.ok || !data.jobId) {
4590
+ const err = typeof data.error === "string" ? data.error : data.error?.message;
4591
+ return { kind: "text", text: `Not started (nothing charged): ${err ?? `HTTP ${res.status}`}` };
4592
+ }
4593
+ return pollToResult(data.jobId, ctx, (urls) => `${input.character} performed ${input.preset} (${data.creditCost ?? "?"} credits). Asset: ${urls[0]}`);
4594
+ }
4595
+ var headers3, BuildSchema, buildCharacterSkill, PerformSchema, performSkill;
4596
+ var init_character_builder = __esm({
4597
+ "src/skills/character-builder.ts"() {
4598
+ init_poll();
4599
+ init_types();
4600
+ headers3 = (ctx, skill) => ({
4601
+ Authorization: `Bearer ${ctx.apiKey}`,
4602
+ "Content-Type": "application/json",
4603
+ "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
4604
+ "x-pixeldojo-skill": skill
4605
+ });
4606
+ BuildSchema = zod.z.object({
4607
+ action: zod.z.enum(["traits", "build"]),
4608
+ picks: zod.z.record(zod.z.union([zod.z.string(), zod.z.array(zod.z.string())])).optional(),
4609
+ randomize: zod.z.boolean().optional(),
4610
+ details: zod.z.string().max(200).optional(),
4611
+ takes: zod.z.number().int().min(1).max(4).optional()
4612
+ });
4613
+ buildCharacterSkill = {
4614
+ name: "pixeldojo_build_character",
4615
+ 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.`,
4616
+ inputZod: BuildSchema,
4617
+ inputJsonSchema: {
4618
+ type: "object",
4619
+ required: ["action"],
4620
+ properties: {
4621
+ action: { type: "string", enum: ["traits", "build"], description: "traits = list trait and option ids (free). build = render the takes (paid)." },
4622
+ picks: {
4623
+ type: "object",
4624
+ additionalProperties: { anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }] },
4625
+ 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.'
4626
+ },
4627
+ randomize: { type: "boolean", description: "build: fill every trait not in picks at random." },
4628
+ details: { type: "string", description: 'build: up to 200 characters of extra look, e.g. "freckles, gold hoops, a vintage denim jacket".' },
4629
+ takes: { type: "integer", minimum: 1, maximum: 4, description: "build: how many takes (default 4)." }
4630
+ }
4631
+ },
4632
+ allowedModels: [],
4633
+ defaultModel: "",
4634
+ run: buildRun
4635
+ };
4636
+ PerformSchema = zod.z.object({
4637
+ action: zod.z.enum(["list", "run"]),
4638
+ character: zod.z.string().max(100).optional(),
4639
+ preset: zod.z.string().max(80).optional()
4640
+ });
4641
+ performSkill = {
4642
+ name: "pixeldojo_perform",
4643
+ 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.',
4644
+ inputZod: PerformSchema,
4645
+ inputJsonSchema: {
4646
+ type: "object",
4647
+ required: ["action"],
4648
+ properties: {
4649
+ action: { type: "string", enum: ["list", "run"], description: "list = presets with prices (free). run = make the video (paid)." },
4650
+ character: { type: "string", description: "run: the saved character's name or @Name (pixeldojo_library source=characters lists them)." },
4651
+ preset: { type: "string", description: 'run: a preset slug from action "list", e.g. "ai-dance", "shoulder-shimmy", "red-carpet".' }
4652
+ }
4653
+ },
4654
+ allowedModels: [],
4655
+ defaultModel: "",
4656
+ run: performRun
4657
+ };
4658
+ }
4659
+ });
3734
4660
  function substitutePlaceholders(args, inputs, prevUrl) {
3735
4661
  const missing = [];
3736
4662
  const fill = (value) => {
@@ -3974,10 +4900,13 @@ __export(skills_exports, {
3974
4900
  TOOL_ANNOTATIONS: () => TOOL_ANNOTATIONS,
3975
4901
  adSkill: () => adSkill,
3976
4902
  audioSkill: () => audioSkill,
4903
+ brandKitSkill: () => brandKitSkill,
4904
+ buildCharacterSkill: () => buildCharacterSkill,
3977
4905
  campaignSkill: () => campaignSkill,
3978
4906
  campaignStatusSkill: () => campaignStatusSkill,
3979
4907
  captionSkill: () => captionSkill,
3980
4908
  characterSkill: () => characterSkill,
4909
+ dramaSkill: () => dramaSkill,
3981
4910
  editSkill: () => editSkill,
3982
4911
  effectRunSkill: () => effectRunSkill,
3983
4912
  effectsListSkill: () => effectsListSkill,
@@ -3987,9 +4916,13 @@ __export(skills_exports, {
3987
4916
  findSkill: () => findSkill,
3988
4917
  fromUrlSkill: () => fromUrlSkill,
3989
4918
  generateSkill: () => generateSkill,
4919
+ kataSkill: () => kataSkill,
3990
4920
  librarySkill: () => librarySkill,
3991
4921
  listWorkflowsSkill: () => listWorkflowsSkill,
4922
+ marketingSkill: () => marketingSkill,
4923
+ musicVideoSkill: () => musicVideoSkill,
3992
4924
  overlaySkill: () => overlaySkill,
4925
+ performSkill: () => performSkill,
3993
4926
  runWorkflowSkill: () => runWorkflowSkill,
3994
4927
  saveCharacterSkill: () => saveCharacterSkill,
3995
4928
  saveSkill: () => saveSkill,
@@ -4034,6 +4967,9 @@ var init_skills = __esm({
4034
4967
  init_film();
4035
4968
  init_video();
4036
4969
  init_episode();
4970
+ init_drama();
4971
+ init_music_video();
4972
+ init_kata();
4037
4973
  init_ad();
4038
4974
  init_campaign();
4039
4975
  init_campaign_status();
@@ -4043,6 +4979,8 @@ var init_skills = __esm({
4043
4979
  init_save();
4044
4980
  init_save_character();
4045
4981
  init_effects();
4982
+ init_marketing();
4983
+ init_character_builder();
4046
4984
  init_workflows();
4047
4985
  ALL_SKILLS = [
4048
4986
  generateSkill,
@@ -4058,6 +4996,8 @@ var init_skills = __esm({
4058
4996
  librarySkill,
4059
4997
  saveSkill,
4060
4998
  saveCharacterSkill,
4999
+ buildCharacterSkill,
5000
+ performSkill,
4061
5001
  saveWorkflowSkill,
4062
5002
  runWorkflowSkill,
4063
5003
  listWorkflowsSkill,
@@ -4065,23 +5005,28 @@ var init_skills = __esm({
4065
5005
  fromUrlSkill,
4066
5006
  uploadSkill,
4067
5007
  adSkill,
5008
+ brandKitSkill,
5009
+ marketingSkill,
4068
5010
  campaignSkill,
4069
5011
  campaignStatusSkill,
4070
5012
  filmSkill,
4071
5013
  videoSkill,
4072
- episodeSkill
5014
+ episodeSkill,
5015
+ dramaSkill,
5016
+ musicVideoSkill,
5017
+ kataSkill
4073
5018
  ];
4074
5019
  isLocalPathField = (key) => /_paths?$/.test(key);
4075
5020
  HOSTED_SKILLS = ALL_SKILLS.filter(
4076
5021
  (s) => s.name !== uploadSkill.name
4077
5022
  ).map((s) => {
4078
- const props = s.inputJsonSchema.properties ?? {};
5023
+ const props2 = s.inputJsonSchema.properties ?? {};
4079
5024
  return {
4080
5025
  ...s,
4081
5026
  description: s.hostedDescription ?? s.description,
4082
5027
  inputJsonSchema: {
4083
5028
  ...s.inputJsonSchema,
4084
- properties: Object.fromEntries(Object.entries(props).filter(([k]) => !isLocalPathField(k)))
5029
+ properties: Object.fromEntries(Object.entries(props2).filter(([k]) => !isLocalPathField(k)))
4085
5030
  }
4086
5031
  };
4087
5032
  });
@@ -4100,9 +5045,14 @@ var init_skills = __esm({
4100
5045
  pixeldojo_effect: render("Run one-click effect"),
4101
5046
  pixeldojo_video: render("Change, animate or extend video"),
4102
5047
  pixeldojo_ad: render("Make video ad"),
5048
+ pixeldojo_brand_kit: render("Brand kit from website"),
5049
+ pixeldojo_marketing: render("Image ads, motion graphics and templates"),
4103
5050
  pixeldojo_campaign: render("Make product launch campaign"),
4104
5051
  pixeldojo_film: render("Make short film"),
4105
5052
  pixeldojo_episode: render("Make faceless video"),
5053
+ pixeldojo_drama: render("Make micro-drama, short film or trailer"),
5054
+ pixeldojo_music_video: render("Make music video for a song"),
5055
+ pixeldojo_kata: render("Recreate an edit with your cast"),
4106
5056
  pixeldojo_run_workflow: render("Run saved workflow"),
4107
5057
  pixeldojo_upload: render("Upload local file"),
4108
5058
  pixeldojo_save_workflow: { ...render("Save workflow"), destructiveHint: true },
@@ -4111,6 +5061,8 @@ var init_skills = __esm({
4111
5061
  pixeldojo_library: read("Search media library"),
4112
5062
  pixeldojo_save: render("Save to My Media"),
4113
5063
  pixeldojo_save_character: render("Save character or element"),
5064
+ pixeldojo_build_character: render("Build AI character from traits"),
5065
+ pixeldojo_perform: render("Make character dance or do a trend"),
4114
5066
  pixeldojo_list_workflows: read("List saved workflows"),
4115
5067
  pixeldojo_from_url: { ...read("Read product page"), openWorldHint: true }
4116
5068
  };
@@ -4120,10 +5072,10 @@ function makeAttributedClient(options) {
4120
5072
  const { mcpVersion, skillName, baseFetch, ...sdkOptions } = options;
4121
5073
  const real = baseFetch ?? globalThis.fetch.bind(globalThis);
4122
5074
  const wrappedFetch = (input, init) => {
4123
- const headers2 = new Headers(init?.headers);
4124
- headers2.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
4125
- if (skillName) headers2.set("x-pixeldojo-skill", skillName);
4126
- return real(input, { ...init, headers: headers2 });
5075
+ const headers4 = new Headers(init?.headers);
5076
+ headers4.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
5077
+ if (skillName) headers4.set("x-pixeldojo-skill", skillName);
5078
+ return real(input, { ...init, headers: headers4 });
4127
5079
  };
4128
5080
  return new sdk.PixelDojoClient({
4129
5081
  ...sdkOptions,
@@ -4167,7 +5119,7 @@ function makeRedactedLogger(target = console) {
4167
5119
  }
4168
5120
 
4169
5121
  // src/server.ts
4170
- var PACKAGE_VERSION = "0.26.0" ;
5122
+ var PACKAGE_VERSION = "0.30.0" ;
4171
5123
  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.";
4172
5124
  function buildServer(options) {
4173
5125
  if (!options.apiKey) {