@pixeldojo/mcp 0.31.0 → 0.34.1

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 CHANGED
@@ -40,7 +40,7 @@ Add to your editor's MCP config (e.g. `~/.config/claude-code/mcp.json`):
40
40
  }
41
41
  ```
42
42
 
43
- Restart your agent. You'll see 23 new tools.
43
+ Restart your agent. You'll see 32 new tools.
44
44
 
45
45
  ## Named skills
46
46
 
@@ -54,6 +54,13 @@ Restart your agent. You'll see 23 new tools.
54
54
  | `pixeldojo_video` | Video Studio verbs on an existing clip: `change` (restyle/edit), `move` (motion transfer or character swap), `extend`. Uses each verb's studio engine. |
55
55
  | `pixeldojo_episode` | Faceless Studio: a narrated faceless video episode from a topic (script, scenes, voice, music, export). |
56
56
  | `pixeldojo_ad` | Marketing Studio: a product video ad from a URL or product images. |
57
+ | `pixeldojo_drama` | A micro-drama episode, short film or trailer from an idea (free plan and quote first, then produce). |
58
+ | `pixeldojo_music_video` | Up to 60 s of your song as a music video with the cast lip-syncing to it. |
59
+ | `pixeldojo_kata` | Recreate a reference edit with your own cast and products: same cuts, transitions and caption style. |
60
+ | `pixeldojo_brand_kit` | Your Marketing Studio brand kit: import from a website, get, update. Free. |
61
+ | `pixeldojo_marketing` | Marketing Studio templates: image ads, motion graphics and the brand end card. |
62
+ | `pixeldojo_save` | Keep a result in My Media permanently after a safety check (outputs are deleted after one hour). |
63
+ | `pixeldojo_save_character` | Save a character, product or element from 3 to 5 photos so `@Name` works in any prompt. |
57
64
  | `pixeldojo_upscale` | Enhance any image (up to 16×) or video (up to 4K). |
58
65
  | `pixeldojo_overlay` | Ad text layouts on a video: hook title, lower-third callout, review card, progress bar, countdown timer, price tag, offer badge and a branded end card with logo. Exact text, your brand color, clear of the platform UI. |
59
66
  | `pixeldojo_caption` | Animated word-by-word captions on a video with speech, placed clear of the TikTok/Reels/Shorts buttons. Pop, Highlighter or Clean, any highlight color. |
package/dist/cli.cjs CHANGED
@@ -248,6 +248,14 @@ async function snapAspectRatio(apiId, requested, client, opts = {}) {
248
248
  if (!field.enum || field.enum.length === 0) return requested;
249
249
  return nearestAspectFromEnum(requested, field.enum);
250
250
  }
251
+ async function snapImageSizePreset(apiId, requested, client) {
252
+ if (!requested) return void 0;
253
+ const props2 = await getFieldConstraints(apiId, client);
254
+ const values = props2?.image_size?.enum;
255
+ if (!values || props2?.aspect_ratio) return void 0;
256
+ const key = requested.trim().replace(":", "_");
257
+ return values.find((v) => String(v).endsWith(`_${key}`));
258
+ }
251
259
  async function resolveMediaSlot(apiId, kind, client) {
252
260
  const info = await getModelInfo(apiId, client);
253
261
  if (info === null) return void 0;
@@ -506,20 +514,24 @@ async function run(input, ctx) {
506
514
  }
507
515
  }
508
516
  let refField;
517
+ let imageSizePreset;
509
518
  let audioSlot;
510
519
  if (!VIDEO_INPUT_MODELS.has(apiId)) {
511
- const [aspect_ratio, duration, resolution, ref, audio] = await Promise.all([
520
+ const [aspect_ratio, imageSize, duration, resolution, ref, audio] = await Promise.all([
512
521
  snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client),
522
+ snapImageSizePreset(apiId, resolved.aspect_ratio, ctx.client),
513
523
  snapDuration(apiId, resolved.duration, ctx.client),
514
524
  snapResolution(apiId, resolved.resolution, ctx.client),
515
525
  src.url ? resolveMediaSlot(apiId, src.isClip ? "video" : "image", ctx.client) : Promise.resolve(void 0),
516
526
  src.audioUrl ? resolveMediaSlot(apiId, "audio", ctx.client) : Promise.resolve(void 0)
517
527
  ]);
518
528
  resolved = { ...resolved, aspect_ratio, duration, resolution };
529
+ imageSizePreset = imageSize;
519
530
  refField = ref === void 0 && src.url ? fallbackSlot(apiId, src.isClip ? "video" : "image") : ref;
520
531
  audioSlot = audio === void 0 && src.audioUrl ? fallbackSlot(apiId, "audio") : audio;
521
532
  }
522
533
  const body = buildPayload(apiId, resolved, src, refField, audioSlot);
534
+ if (imageSizePreset) body.image_size = imageSizePreset;
523
535
  const paramsNote = applyParams(body, input.params, input.model, apiId);
524
536
  const note = [routeNote, paramsNote].filter(Boolean).join(" ");
525
537
  const submitted = await ctx.client.run(apiId, body);
@@ -551,7 +563,7 @@ var init_generate = __esm({
551
563
  InputSchema = zod.z.object({
552
564
  prompt: zod.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.'),
553
565
  model: zod.z.string().optional().describe(
554
- `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.`
566
+ `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 prompt alone, a photo or up to 9 reference images, "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.`
555
567
  ),
556
568
  preset: zod.z.string().optional().describe(
557
569
  "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."
@@ -590,7 +602,7 @@ var init_generate = __esm({
590
602
  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.' },
591
603
  model: {
592
604
  type: "string",
593
- 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.`
605
+ 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 prompt alone, a photo or up to 9 reference images, "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.`
594
606
  },
595
607
  preset: {
596
608
  type: "string",
@@ -638,8 +650,8 @@ var init_generate = __esm({
638
650
  };
639
651
  generateSkill = {
640
652
  name: "pixeldojo_generate",
641
- 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.",
642
- 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.",
653
+ 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, from a prompt alone, a photo or reference images. 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.",
654
+ 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, from a prompt alone, a photo or reference images. 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.",
643
655
  inputZod: InputSchema,
644
656
  inputJsonSchema: InputJsonSchema,
645
657
  allowedModels: null,
@@ -925,7 +937,8 @@ var init_character = __esm({
925
937
  "google-nano-banana",
926
938
  "wan-2.7-image-edit",
927
939
  "qwen-3-pro",
928
- "qwen-image-2.0-pro"
940
+ "qwen-image-2.0-pro",
941
+ "p-h3-image"
929
942
  ];
930
943
  DEFAULT_MODEL3 = "seedream-5";
931
944
  MAX_REFERENCES = 9;
@@ -937,7 +950,8 @@ var init_character = __esm({
937
950
  "google-nano-banana": "image_input",
938
951
  "wan-2.7-image-edit": "image_urls",
939
952
  "qwen-3-pro": "image",
940
- "qwen-image-2.0-pro": "reference_images"
953
+ "qwen-image-2.0-pro": "reference_images",
954
+ "p-h3-image": "reference_images"
941
955
  };
942
956
  InputSchema3 = zod.z.object({
943
957
  prompt: zod.z.string().min(1).max(8e3),
@@ -1681,7 +1695,7 @@ async function filmApi(ctx, method, path, body) {
1681
1695
  Authorization: `Bearer ${ctx.apiKey}`,
1682
1696
  "Content-Type": "application/json",
1683
1697
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
1684
- "x-pixeldojo-skill": "film"
1698
+ "x-pixeldojo-skill": "pixeldojo_film"
1685
1699
  },
1686
1700
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
1687
1701
  });
@@ -2010,7 +2024,7 @@ It is saved in the Library folder "Film: ${film.title}".`,
2010
2024
  }
2011
2025
  const startExport = async () => {
2012
2026
  const r = await filmApi(ctx, "POST", `/api/films/${filmId}/export`, {});
2013
- return r.status === 200 || r.status === 409 ? null : r.data.error ?? `HTTP ${r.status}`;
2027
+ return r.status === 200 || r.status === 409 && r.data.code === "export-running" ? null : r.data.error ?? `HTTP ${r.status}`;
2014
2028
  };
2015
2029
  if (film.exportError && all.length > 0 && all.every((sh) => sh.status === "clip-ready")) {
2016
2030
  const again = await startExport();
@@ -2440,8 +2454,6 @@ function buildVideoPayload(verb, apiId, i) {
2440
2454
  ...vd !== void 0 ? { source_duration_seconds: vd } : {}
2441
2455
  }
2442
2456
  };
2443
- case "flux-video-edit":
2444
- return { body: { prompt, video: i.videoUrl, video_duration: Math.ceil(vd) }, note: droppedImages };
2445
2457
  case "xai-video-edit":
2446
2458
  return { body: { prompt, video_url: i.videoUrl, video_duration: vd }, note: droppedImages };
2447
2459
  case "happyhorse-1.0-video-edit":
@@ -2472,6 +2484,17 @@ function buildVideoPayload(verb, apiId, i) {
2472
2484
  }
2473
2485
  }
2474
2486
  if (verb === "move") {
2487
+ if (apiId === "p-video-2-pro-replace") {
2488
+ return {
2489
+ body: {
2490
+ prompt: "",
2491
+ video: i.videoUrl,
2492
+ images: i.images.slice(0, 2),
2493
+ video_duration: vd,
2494
+ ...prompt ? { instruction_prompt: prompt } : {}
2495
+ }
2496
+ };
2497
+ }
2475
2498
  if (apiId === "wan-video-character-swap") {
2476
2499
  return { body: { prompt: "", image_url: i.images[0], video_url: i.videoUrl, video_duration: vd } };
2477
2500
  }
@@ -2593,8 +2616,6 @@ var init_video = __esm({
2593
2616
  VIDEO_STUDIO_ENGINES = {
2594
2617
  change: [
2595
2618
  "seedance-2-5-video-edit",
2596
- "flux-video-edit",
2597
- "kling-video-edit",
2598
2619
  "kling-4-0",
2599
2620
  "minimax-h3-turbo",
2600
2621
  "wan-video-edit",
@@ -2602,24 +2623,24 @@ var init_video = __esm({
2602
2623
  "happyhorse-1.0-video-edit",
2603
2624
  "xai-video-edit"
2604
2625
  ],
2605
- move: ["kling-motion-control", "wan-video-character-swap"],
2626
+ move: ["p-video-2-pro-replace", "kling-motion-control", "wan-video-character-swap"],
2606
2627
  extend: ["seedance-2-5-video-edit", "wan-2.7-i2v", "grok-video-extend"]
2607
2628
  };
2608
2629
  VERBS = ["change", "move", "extend"];
2609
2630
  BACKWARD_ENGINES = /* @__PURE__ */ new Set(["seedance-2-5-video-edit"]);
2610
2631
  clamp = (n, min, max) => Math.min(max, Math.max(min, Math.round(n)));
2611
2632
  NEEDS_VIDEO_DURATION = /* @__PURE__ */ new Set([
2612
- "flux-video-edit",
2613
- "kling-video-edit",
2614
2633
  "wan-video-edit",
2615
2634
  "seedance-video-edit",
2616
2635
  "happyhorse-1.0-video-edit",
2617
2636
  "xai-video-edit",
2618
2637
  "kling-motion-control",
2619
2638
  "wan-video-character-swap",
2620
- "kling-4-0"
2639
+ "kling-4-0",
2640
+ // Its schema defaults the length, but the price is per second: a missing length would under-charge.
2641
+ "p-video-2-pro-replace"
2621
2642
  ]);
2622
- NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["flux-video-edit", "xai-video-edit", "minimax-h3-turbo"]);
2643
+ NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["xai-video-edit", "minimax-h3-turbo"]);
2623
2644
  InputSchema11 = zod.z.object({
2624
2645
  verb: zod.z.enum(VERBS),
2625
2646
  prompt: zod.z.string().max(8e3).optional(),
@@ -2645,7 +2666,7 @@ var init_video = __esm({
2645
2666
  },
2646
2667
  prompt: {
2647
2668
  type: "string",
2648
- description: "What to change, how the character should move, or what happens next. Required for change and extend. Optional for move (wan-video-character-swap ignores it). @Name uses a character or element the user saved in Character Studio (photos attached automatically)."
2669
+ description: "What to change, how the character should move, or what happens next. Required for change and extend. Optional for move (p-video-2-pro-replace reads it as who becomes whom; wan-video-character-swap ignores it). @Name uses a character or element the user saved in Character Studio (photos attached automatically)."
2649
2670
  },
2650
2671
  video_url: {
2651
2672
  type: "string",
@@ -2711,7 +2732,7 @@ async function episodeApi(ctx, method, path, body) {
2711
2732
  Authorization: `Bearer ${ctx.apiKey}`,
2712
2733
  "Content-Type": "application/json",
2713
2734
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
2714
- "x-pixeldojo-skill": "episode"
2735
+ "x-pixeldojo-skill": "pixeldojo_episode"
2715
2736
  },
2716
2737
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2717
2738
  });
@@ -2952,7 +2973,7 @@ async function dramaApi(ctx, method, path, body) {
2952
2973
  Authorization: `Bearer ${ctx.apiKey}`,
2953
2974
  "Content-Type": "application/json",
2954
2975
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
2955
- "x-pixeldojo-skill": "drama"
2976
+ "x-pixeldojo-skill": "pixeldojo_drama"
2956
2977
  },
2957
2978
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2958
2979
  });
@@ -2966,9 +2987,10 @@ function describeDrama(v, o = DRAMA_OPTS) {
2966
2987
  const next2 = (action) => `${o.tool} with {"${o.idField}": "${id}", "action": "${action}"}`;
2967
2988
  const music = o.unit === "section";
2968
2989
  const what = music ? "music video" : "episode";
2990
+ if (v.status === "failed" && v.error?.startsWith("Prohibited content")) return { kind: "text", text: `${v.error} Tell the user this idea is prohibited content; do not rewrite it to get around the refusal.` };
2969
2991
  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".` : ""}` };
2970
2992
  if (v.status === "quote") {
2971
- 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).`;
2993
+ 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, scenes of up to 30 seconds 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).`;
2972
2994
  return {
2973
2995
  kind: "text",
2974
2996
  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")
@@ -3051,9 +3073,11 @@ var init_drama = __esm({
3051
3073
  end_text: zod.z.string().max(60).optional(),
3052
3074
  brand_color: zod.z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
3053
3075
  logo_url: zod.z.string().url().optional(),
3054
- cast: zod.z.array(zod.z.object({ name: zod.z.string().min(1).max(40), image_url: zod.z.string().url() })).max(5).optional(),
3076
+ cast: zod.z.array(zod.z.object({ name: zod.z.string().min(1).max(40), image_url: zod.z.string().url(), outfit_image_url: zod.z.string().url().optional() })).max(5).optional(),
3077
+ length_sec: zod.z.number().int().min(24).max(180).multipleOf(6).optional(),
3055
3078
  scenes: zod.z.number().int().min(1).max(15).optional(),
3056
3079
  score: zod.z.boolean().optional(),
3080
+ engine: zod.z.enum(["seedance-2-5", "wan-3-0-video", "kling-4-0"]).optional(),
3057
3081
  drama_id: zod.z.string().max(60).optional(),
3058
3082
  beats: zod.z.array(zod.z.number().int().min(1).max(15)).min(1).max(15).optional(),
3059
3083
  notes: zod.z.array(zod.z.string().max(300)).max(15).optional()
@@ -3064,7 +3088,7 @@ var init_drama = __esm({
3064
3088
  action: {
3065
3089
  type: "string",
3066
3090
  enum: ["create", "produce", "status", "redo", "cut"],
3067
- 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."
3091
+ description: "create (default, free): writes the plan (30 seconds unless length_sec 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."
3068
3092
  },
3069
3093
  idea: { type: "string", description: "Create: the story in a line or two (who, the conflict, the twist)." },
3070
3094
  format: { type: "string", enum: ["9:16", "16:9"], description: "Create: vertical 9:16 (default) or widescreen 16:9." },
@@ -3072,7 +3096,7 @@ var init_drama = __esm({
3072
3096
  kind: {
3073
3097
  type: "string",
3074
3098
  enum: [...KINDS],
3075
- 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)."
3099
+ description: "Create: micro-drama (default: episode one with a hook and a cliffhanger), short-film (a complete story) or trailer (sells a film that does not exist, never shows the ending)."
3076
3100
  },
3077
3101
  look: { type: "string", enum: [...LOOKS2], description: "Create (optional): the visual style and genre. Default live-action (a micro-drama story type brings its own)." },
3078
3102
  story_type: {
@@ -3092,13 +3116,16 @@ var init_drama = __esm({
3092
3116
  type: "object",
3093
3117
  properties: {
3094
3118
  name: { type: "string", description: "The character's name as the story uses it." },
3095
- image_url: { type: "string", description: "A clear front-facing portrait on the PixelDojo CDN." }
3119
+ image_url: { type: "string", description: `A clear front-facing portrait on the PixelDojo CDN (a saved character's first "for video" picture when it has one).` },
3120
+ outfit_image_url: { type: "string", description: `Optional: the saved character's outfit picture (the second "for video" picture from pixeldojo_library). The scenes then keep their clothes closer.` }
3096
3121
  },
3097
3122
  required: ["name", "image_url"]
3098
3123
  },
3099
- 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."
3124
+ 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; pass its "for video" face and outfit pictures when it lists them). Others get a portrait made for them.'
3100
3125
  },
3101
- 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)." },
3126
+ length_sec: { type: "integer", minimum: 24, maximum: 180, multipleOf: 6, description: "Create (optional): the film's length in seconds, 24 to 180 in steps of 6. Default 30. If the user has not said how long, ask (30 seconds, 1, 2 or 3 minutes; the price grows with the length) or leave it out for 30 seconds. The writer splits the length into scenes of up to 30 s, one per place and cast." },
3127
+ scenes: { type: "integer", minimum: 1, maximum: 15, description: "Create (optional, older): the length in 12-second units (5 = 60 s). Prefer length_sec." },
3128
+ engine: { type: "string", enum: ["seedance-2-5", "wan-3-0-video", "kling-4-0"], description: "Create (optional): the video model every scene films on, from the cast pictures. seedance-2-5 (default), wan-3-0-video (about a third of the price), kling-4-0. Pass it only when the user names one. Adult work always runs on wan-3-0-video." },
3102
3129
  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)." },
3103
3130
  drama_id: { type: "string", description: "From create. Required for produce, status and redo." },
3104
3131
  beats: { type: "array", items: { type: "integer", minimum: 1, maximum: 15 }, description: "Redo: the beat numbers to film again." },
@@ -3115,7 +3142,7 @@ var init_drama = __esm({
3115
3142
  DRAMA_OPTS = { tool: "pixeldojo_drama", idField: "drama_id", unit: "beat" };
3116
3143
  dramaSkill = {
3117
3144
  name: "pixeldojo_drama",
3118
- 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).",
3145
+ description: "Make a micro-drama episode (30 seconds by default, up to 3 minutes with length_sec; 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). Scenes of up to 30 seconds (one per place and cast), each filmed in one pass with several cuts, on Seedance 2.5, WAN 3 or Kling 4, 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 190 credits for a regular 30 second film, about 380 for a minute; less on the wan-3-0-video engine and for mature work); produce films it; then call status until it is finished (10-15 minutes).",
3119
3146
  inputZod: InputSchema13,
3120
3147
  inputJsonSchema: InputJsonSchema13,
3121
3148
  allowedModels: ["seedance-2-5", "seedream-4"],
@@ -3130,7 +3157,9 @@ var init_drama = __esm({
3130
3157
  ...input.mature ? { mature: true } : {},
3131
3158
  ...input.look ? { look: input.look } : {},
3132
3159
  ...input.story_type ? { story_type: input.story_type } : {},
3160
+ ...input.length_sec ? { length_sec: input.length_sec } : {},
3133
3161
  ...input.scenes ? { scenes: input.scenes } : {},
3162
+ ...input.engine ? { engine: input.engine } : {},
3134
3163
  ...input.score === false ? { score: false } : {},
3135
3164
  ...Object.fromEntries(
3136
3165
  ["title_card", "name_tags", "end_screen", "end_text", "brand_color", "logo_url"].filter((k) => input[k] !== void 0).map((k) => [k, input[k]])
@@ -3159,7 +3188,7 @@ async function api(ctx, method, path, body) {
3159
3188
  Authorization: `Bearer ${ctx.apiKey}`,
3160
3189
  "Content-Type": "application/json",
3161
3190
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
3162
- "x-pixeldojo-skill": "music_video"
3191
+ "x-pixeldojo-skill": "pixeldojo_music_video"
3163
3192
  },
3164
3193
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
3165
3194
  });
@@ -3288,7 +3317,7 @@ async function kataApi(ctx, method, path, body) {
3288
3317
  Authorization: `Bearer ${ctx.apiKey}`,
3289
3318
  "Content-Type": "application/json",
3290
3319
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
3291
- "x-pixeldojo-skill": "kata"
3320
+ "x-pixeldojo-skill": "pixeldojo_kata"
3292
3321
  },
3293
3322
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
3294
3323
  });
@@ -3452,7 +3481,7 @@ async function run13(input, ctx) {
3452
3481
  aspect_ratio: input.aspect_ratio,
3453
3482
  duration: input.duration,
3454
3483
  reference_video_url: input.reference_video_url,
3455
- brand_kit: input.brand_kit
3484
+ brand_kit: input.brand_kit === false ? false : input.brand_kit_id ?? input.brand_kit
3456
3485
  };
3457
3486
  const client = ctx.client;
3458
3487
  const submitted = await client.runAd(body);
@@ -3497,7 +3526,8 @@ var init_ad = __esm({
3497
3526
  aspect_ratio: zod.z.enum(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]).optional(),
3498
3527
  duration: zod.z.number().int().min(4).max(15).optional(),
3499
3528
  reference_video_url: zod.z.string().url().optional(),
3500
- brand_kit: zod.z.boolean().optional()
3529
+ brand_kit: zod.z.boolean().optional(),
3530
+ brand_kit_id: zod.z.string().max(80).optional()
3501
3531
  }).refine(
3502
3532
  (v) => v.product_url || (v.product_image_urls?.length || v.product_image_paths?.length) && v.product_name,
3503
3533
  {
@@ -3591,7 +3621,11 @@ var init_ad = __esm({
3591
3621
  },
3592
3622
  brand_kit: {
3593
3623
  type: "boolean",
3594
- description: "The account's brand kit (palette, voice, selling points; see pixeldojo_brand_kit) rides on every ad. false skips it."
3624
+ description: "The account's default brand kit (palette, voice, selling points; see pixeldojo_brand_kit) rides on every ad. false skips it."
3625
+ },
3626
+ brand_kit_id: {
3627
+ type: "string",
3628
+ description: "Use this saved brand kit instead of the default (ids listed by pixeldojo_brand_kit)."
3595
3629
  }
3596
3630
  }
3597
3631
  // "product_url OR product_name+images" is enforced by the server and the
@@ -3932,7 +3966,8 @@ async function run18(input, ctx) {
3932
3966
  }
3933
3967
  const lines2 = characters.map(
3934
3968
  (c) => `- ${c.mention} (${c.kind})${c.description ? `: ${c.description.slice(0, 100)}` : ""}
3935
- images: ${c.image_urls.slice(0, 4).join(" ")}`
3969
+ images: ${c.image_urls.slice(0, 4).join(" ")}${c.video_pair && c.video_image_urls?.length === 2 ? `
3970
+ for video (face, outfit; pass both to pixeldojo_drama cast as image_url + outfit_image_url): ${c.video_image_urls.join(" ")}` : ""}`
3936
3971
  );
3937
3972
  return {
3938
3973
  kind: "text",
@@ -4155,7 +4190,8 @@ async function effectRun(input, ctx) {
4155
4190
  });
4156
4191
  const data = await res.json().catch(() => ({}));
4157
4192
  if (!res.ok || !data.jobId) {
4158
- return { kind: "text", text: `Effect not started (nothing charged): ${data.error ?? `HTTP ${res.status}`}` };
4193
+ const err = typeof data.error === "string" ? data.error : data.error?.message;
4194
+ return { kind: "text", text: `Effect not started (nothing charged): ${err ?? `HTTP ${res.status}`}` };
4159
4195
  }
4160
4196
  const result = await pollJobUntilDoneOrHandoff(data.jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
4161
4197
  now: ctx.pollOverrides?.now,
@@ -4287,7 +4323,15 @@ ${describeKit(r2.data.brand_kit)}` };
4287
4323
  }
4288
4324
  const r = await call2(ctx, "pixeldojo_brand_kit", path);
4289
4325
  if (!r.ok) return { kind: "text", text: `Could not read the brand kit: ${r.error}` };
4290
- return { kind: "text", text: describeKit(r.data.brand_kit ?? null) };
4326
+ const all = r.data.brand_kits ?? [];
4327
+ const others = all.filter((k) => !k.isDefault);
4328
+ return {
4329
+ kind: "text",
4330
+ text: [
4331
+ describeKit(r.data.brand_kit ?? null),
4332
+ ...others.length ? [`Other brand kits (pass brand_kit_id on a run to use one): ${others.map((k) => `${k.name || "(no name)"} (id ${k.id})`).join("; ")}`] : []
4333
+ ].join("\n")
4334
+ };
4291
4335
  }
4292
4336
  async function templatesRun(input, ctx) {
4293
4337
  const q = new URLSearchParams();
@@ -4333,7 +4377,7 @@ async function imageRun(input, ctx) {
4333
4377
  engine: input.engine,
4334
4378
  count: input.count,
4335
4379
  reference_ad_url: input.reference_ad_url,
4336
- brand_kit: input.brand_kit
4380
+ brand_kit: input.brand_kit === false ? false : input.brand_kit_id ?? input.brand_kit
4337
4381
  }
4338
4382
  });
4339
4383
  if (!r.ok) return { kind: "text", text: `Images not started: ${r.error}` };
@@ -4403,7 +4447,7 @@ async function motionRun(input, ctx) {
4403
4447
  video_url: input.video_url,
4404
4448
  brand_name: input.brand_name,
4405
4449
  colors: input.colors?.map((c) => c.trim().toLowerCase()),
4406
- brand_kit: input.brand_kit
4450
+ brand_kit: input.brand_kit === false ? false : input.brand_kit_id ?? input.brand_kit
4407
4451
  }
4408
4452
  });
4409
4453
  if (!r.ok) return { kind: "text", text: `Not started: ${r.error}` };
@@ -4500,7 +4544,8 @@ var init_marketing = __esm({
4500
4544
  motion_id: zod.z.string().max(80).optional(),
4501
4545
  brand_name: zod.z.string().max(60).optional(),
4502
4546
  colors: zod.z.array(zod.z.string()).max(5).optional(),
4503
- brand_kit: zod.z.boolean().optional()
4547
+ brand_kit: zod.z.boolean().optional(),
4548
+ brand_kit_id: zod.z.string().max(80).optional()
4504
4549
  });
4505
4550
  props = {
4506
4551
  action: {
@@ -4536,7 +4581,8 @@ var init_marketing = __esm({
4536
4581
  motion_id: { type: "string", description: "status: the id a motion or end_card call returned." },
4537
4582
  brand_name: { type: "string", description: "motion: the brand shown when there is no logo. Default: the brand kit." },
4538
4583
  colors: { type: "array", items: { type: "string" }, maxItems: 5, description: "motion: #RRGGBB, lead color first. Default: the brand kit." },
4539
- brand_kit: { type: "boolean", description: "false skips the brand kit for this call." }
4584
+ brand_kit: { type: "boolean", description: "false skips the brand kit for this call." },
4585
+ brand_kit_id: { type: "string", description: "Use this saved brand kit instead of the default (ids listed by pixeldojo_brand_kit)." }
4540
4586
  };
4541
4587
  marketingSkill = {
4542
4588
  name: "pixeldojo_marketing",
@@ -5064,7 +5110,7 @@ var init_skills = __esm({
5064
5110
  }
5065
5111
  };
5066
5112
  });
5067
- render = (title) => ({ title, readOnlyHint: false, destructiveHint: false, openWorldHint: false });
5113
+ render = (title) => ({ title, readOnlyHint: false, destructiveHint: false, openWorldHint: true });
5068
5114
  read = (title) => ({ title, readOnlyHint: true, destructiveHint: false, openWorldHint: false });
5069
5115
  TOOL_ANNOTATIONS = {
5070
5116
  pixeldojo_generate: render("Generate image or video"),
@@ -5079,22 +5125,22 @@ var init_skills = __esm({
5079
5125
  pixeldojo_effect: render("Run one-click effect"),
5080
5126
  pixeldojo_video: render("Change, animate or extend video"),
5081
5127
  pixeldojo_ad: render("Make video ad"),
5082
- pixeldojo_brand_kit: render("Brand kit from website"),
5128
+ pixeldojo_brand_kit: { ...render("Brand kit from website"), destructiveHint: true },
5083
5129
  pixeldojo_marketing: render("Image ads, motion graphics and templates"),
5084
5130
  pixeldojo_campaign: render("Make product launch campaign"),
5085
- pixeldojo_film: render("Make short film"),
5131
+ pixeldojo_film: { ...render("Make short film"), destructiveHint: true },
5086
5132
  pixeldojo_episode: render("Make faceless video"),
5087
5133
  pixeldojo_drama: render("Make micro-drama, short film or trailer"),
5088
5134
  pixeldojo_music_video: render("Make music video for a song"),
5089
5135
  pixeldojo_kata: render("Recreate an edit with your cast"),
5090
5136
  pixeldojo_run_workflow: render("Run saved workflow"),
5091
5137
  pixeldojo_upload: render("Upload local file"),
5092
- pixeldojo_save_workflow: { ...render("Save workflow"), destructiveHint: true },
5138
+ pixeldojo_save_workflow: { ...render("Save workflow"), destructiveHint: true, openWorldHint: false },
5093
5139
  pixeldojo_status: read("Check job status"),
5094
5140
  pixeldojo_campaign_status: read("Check campaign status"),
5095
5141
  pixeldojo_library: read("Search media library"),
5096
- pixeldojo_save: render("Save to My Media"),
5097
- pixeldojo_save_character: render("Save character or element"),
5142
+ pixeldojo_save: { ...render("Save to My Media"), openWorldHint: false },
5143
+ pixeldojo_save_character: { ...render("Save character or element"), openWorldHint: false },
5098
5144
  pixeldojo_build_character: render("Build AI character from traits"),
5099
5145
  pixeldojo_perform: render("Make character dance or do a trend"),
5100
5146
  pixeldojo_list_workflows: read("List saved workflows"),
@@ -5153,7 +5199,7 @@ function makeRedactedLogger(target = console) {
5153
5199
  }
5154
5200
 
5155
5201
  // src/server.ts
5156
- var PACKAGE_VERSION = "0.31.0" ;
5202
+ var PACKAGE_VERSION = "0.34.1" ;
5157
5203
  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.";
5158
5204
  function buildServer(options) {
5159
5205
  if (!options.apiKey) {
@@ -5271,7 +5317,10 @@ function errorToActionableMessage(err) {
5271
5317
  return "Authentication failed. Check PIXELDOJO_API_KEY. Get a key at https://pixeldojo.ai/api-platform/api-keys.";
5272
5318
  }
5273
5319
  if (e.status === 402 || e.code === "insufficient_credits") {
5274
- return "Insufficient credits. Top up at https://pixeldojo.ai/api-platform/buy-credits.";
5320
+ return (
5321
+ // ponytail: no checkout link, ChatGPT's plugin review bans linking to a purchase page.
5322
+ "Not enough credits for this. Add credits in your Pixel Dojo account, then try again."
5323
+ );
5275
5324
  }
5276
5325
  if (e.status === 429 || e.code === "rate_limit") {
5277
5326
  return "Rate limit hit. Try again in a few seconds.";