@pixeldojo/mcp 0.31.0 → 0.36.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/index.d.cts CHANGED
@@ -267,9 +267,10 @@ declare const HOSTED_SKILLS: ReadonlyArray<SkillModule<unknown>>;
267
267
  * - readOnlyHint: false for anything that renders, spends credits or saves.
268
268
  * - destructiveHint: renders are additive (new media, nothing of the user's
269
269
  * is changed or deleted; failed jobs refund), so false. save_workflow
270
- * overwrites a same-named workflow, so true.
271
- * - openWorldHint: everything stays inside the user's own account, except
272
- * from_url, which fetches an arbitrary public page.
270
+ * overwrites a same-named workflow, brand_kit overwrites the account's kit
271
+ * and film edits/re-exports an existing film, so those are true.
272
+ * - openWorldHint: true for renders (they call outside AI providers; ChatGPT's
273
+ * plugin scan flagged false), false for account reads; from_url fetches any page.
273
274
  */
274
275
  type Hints = {
275
276
  title: string;
package/dist/index.d.ts CHANGED
@@ -267,9 +267,10 @@ declare const HOSTED_SKILLS: ReadonlyArray<SkillModule<unknown>>;
267
267
  * - readOnlyHint: false for anything that renders, spends credits or saves.
268
268
  * - destructiveHint: renders are additive (new media, nothing of the user's
269
269
  * is changed or deleted; failed jobs refund), so false. save_workflow
270
- * overwrites a same-named workflow, so true.
271
- * - openWorldHint: everything stays inside the user's own account, except
272
- * from_url, which fetches an arbitrary public page.
270
+ * overwrites a same-named workflow, brand_kit overwrites the account's kit
271
+ * and film edits/re-exports an existing film, so those are true.
272
+ * - openWorldHint: true for renders (they call outside AI providers; ChatGPT's
273
+ * plugin scan flagged false), false for account reads; from_url fetches any page.
273
274
  */
274
275
  type Hints = {
275
276
  title: string;
package/dist/index.js CHANGED
@@ -246,6 +246,14 @@ async function snapAspectRatio(apiId, requested, client, opts = {}) {
246
246
  if (!field.enum || field.enum.length === 0) return requested;
247
247
  return nearestAspectFromEnum(requested, field.enum);
248
248
  }
249
+ async function snapImageSizePreset(apiId, requested, client) {
250
+ if (!requested) return void 0;
251
+ const props2 = await getFieldConstraints(apiId, client);
252
+ const values = props2?.image_size?.enum;
253
+ if (!values || props2?.aspect_ratio) return void 0;
254
+ const key = requested.trim().replace(":", "_");
255
+ return values.find((v) => String(v).endsWith(`_${key}`));
256
+ }
249
257
  async function resolveMediaSlot(apiId, kind, client) {
250
258
  const info = await getModelInfo(apiId, client);
251
259
  if (info === null) return void 0;
@@ -504,20 +512,24 @@ async function run(input, ctx) {
504
512
  }
505
513
  }
506
514
  let refField;
515
+ let imageSizePreset;
507
516
  let audioSlot;
508
517
  if (!VIDEO_INPUT_MODELS.has(apiId)) {
509
- const [aspect_ratio, duration, resolution, ref, audio] = await Promise.all([
518
+ const [aspect_ratio, imageSize, duration, resolution, ref, audio] = await Promise.all([
510
519
  snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client),
520
+ snapImageSizePreset(apiId, resolved.aspect_ratio, ctx.client),
511
521
  snapDuration(apiId, resolved.duration, ctx.client),
512
522
  snapResolution(apiId, resolved.resolution, ctx.client),
513
523
  src.url ? resolveMediaSlot(apiId, src.isClip ? "video" : "image", ctx.client) : Promise.resolve(void 0),
514
524
  src.audioUrl ? resolveMediaSlot(apiId, "audio", ctx.client) : Promise.resolve(void 0)
515
525
  ]);
516
526
  resolved = { ...resolved, aspect_ratio, duration, resolution };
527
+ imageSizePreset = imageSize;
517
528
  refField = ref === void 0 && src.url ? fallbackSlot(apiId, src.isClip ? "video" : "image") : ref;
518
529
  audioSlot = audio === void 0 && src.audioUrl ? fallbackSlot(apiId, "audio") : audio;
519
530
  }
520
531
  const body = buildPayload(apiId, resolved, src, refField, audioSlot);
532
+ if (imageSizePreset) body.image_size = imageSizePreset;
521
533
  const paramsNote = applyParams(body, input.params, input.model, apiId);
522
534
  const note = [routeNote, paramsNote].filter(Boolean).join(" ");
523
535
  const submitted = await ctx.client.run(apiId, body);
@@ -549,7 +561,7 @@ var init_generate = __esm({
549
561
  InputSchema = z.object({
550
562
  prompt: z.string().min(1).max(8e3).describe('Prompt describing what to generate. Write @Name to use a character or element the user saved in Character Studio ("@Mara on a beach"): its photos are attached automatically on models that take reference images. pixeldojo_library source=characters lists them.'),
551
563
  model: z.string().optional().describe(
552
- `Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5" for images (the default), "seedance-2-5" for video (prompt only or a starting image), "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.`
564
+ `Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5-5" for images (the default), "seedance-2-5-flash" for video (prompt only or a starting image, 480p or 720p), "seedance-2-5" for 1080p video, "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-5, which makes still images only; name a video model to get a video.`
553
565
  ),
554
566
  preset: z.string().optional().describe(
555
567
  "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."
@@ -574,7 +586,7 @@ var init_generate = __esm({
574
586
  "Multiple absolute local file paths. Each is uploaded to PixelDojo (24h temp storage) and used as a reference, same rules as image_urls."
575
587
  )
576
588
  });
577
- DEFAULT_MODEL = "seedream-5";
589
+ DEFAULT_MODEL = "seedream-5-5";
578
590
  VIDEO_REFERENCE_MODEL = "seedance-2-reference";
579
591
  VIDEO_REFERENCE_SLOTS = {
580
592
  image: { name: "reference_images", isArray: true },
@@ -588,7 +600,7 @@ var init_generate = __esm({
588
600
  prompt: { type: "string", description: 'Prompt describing what to generate. Write @Name to use a character or element the user saved in Character Studio ("@Mara on a beach"): its photos are attached automatically on models that take reference images. pixeldojo_library source=characters lists them.' },
589
601
  model: {
590
602
  type: "string",
591
- description: `Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5" for images (the default), "seedance-2-5" for video (prompt only or a starting image), "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.`
603
+ description: `Model apiId. Recommended (the models the PixelDojo studios use): "seedream-5-5" for images (the default), "seedance-2-5-flash" for video (prompt only or a starting image, 480p or 720p), "seedance-2-5" for 1080p video, "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-5, which makes still images only; name a video model to get a video.`
592
604
  },
593
605
  preset: {
594
606
  type: "string",
@@ -636,8 +648,8 @@ var init_generate = __esm({
636
648
  };
637
649
  generateSkill = {
638
650
  name: "pixeldojo_generate",
639
- description: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image; kling-4-0 for a multi-shot scene up to 30 s with dialogue; minimax-h3-turbo for the lowest price when you have a photo or reference images (it refuses a prompt alone). If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills, `video_url` for a source clip, `image_path`/`image_paths` for absolute local files (uploaded to 24h temp storage first). A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
640
- hostedDescription: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a prompt runs on seedream-5, which makes still images only. For a video, name seedance-2-5, which works from a prompt alone or a starting image; kling-4-0 for a multi-shot scene up to 30 s with dialogue; minimax-h3-turbo for the lowest price when you have a photo or reference images (it refuses a prompt alone). If a provider refuses the content, retry once on wan-3-0-video (video) or seedream-5 (images), the least-restricted models. For a multi-shot film or a music video synced to a song use pixeldojo_film; to change, move or extend an existing clip use pixeldojo_video. These are the models the PixelDojo studios use; name another only when the user asks for it. References: `image_url`/`image_urls` for stills and `video_url` for a source clip, all public http(s) URLs. A clip or an audio track URL with no `model` runs on seedance-2-reference; a named model that cannot read a clip is swapped for it and the result says so, while a named model with no audio input drops the track. Model-specific settings go in `params` alongside an explicit `model`. Returns asset URLs, or a jobId for pixeldojo_status when the job outlasts the 30s wait.",
651
+ 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-5, which makes still images only. For a video, name seedance-2-5-flash, which works from a prompt alone or a starting image at 480p or 720p; seedance-2-5 for 1080p; 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.",
652
+ 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-5, which makes still images only. For a video, name seedance-2-5-flash, which works from a prompt alone or a starting image at 480p or 720p; seedance-2-5 for 1080p; 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.",
641
653
  inputZod: InputSchema,
642
654
  inputJsonSchema: InputJsonSchema,
643
655
  allowedModels: null,
@@ -917,25 +929,31 @@ var init_character = __esm({
917
929
  init_schema_snap();
918
930
  init_params();
919
931
  ALLOWED = [
932
+ "seedream-5-5",
920
933
  "seedream-5",
921
934
  "seedream-4",
922
935
  "seedream-5-flash",
923
936
  "google-nano-banana",
924
937
  "wan-2.7-image-edit",
925
938
  "qwen-3-pro",
926
- "qwen-image-2.0-pro"
939
+ "qwen-image-2.0-pro",
940
+ "p-h3-image",
941
+ "minimax-h3-image"
927
942
  ];
928
- DEFAULT_MODEL3 = "seedream-5";
943
+ DEFAULT_MODEL3 = "seedream-5-5";
929
944
  MAX_REFERENCES = 9;
930
945
  REQUIRES_REFERENCE = /* @__PURE__ */ new Set(["wan-2.7-image-edit"]);
931
946
  FALLBACK_REF_KEY = {
932
947
  "seedream-5": "image_urls",
948
+ "seedream-5-5": "image_urls",
933
949
  "seedream-4": "image_urls",
934
950
  "seedream-5-flash": "image_urls",
935
951
  "google-nano-banana": "image_input",
936
952
  "wan-2.7-image-edit": "image_urls",
937
953
  "qwen-3-pro": "image",
938
- "qwen-image-2.0-pro": "reference_images"
954
+ "qwen-image-2.0-pro": "reference_images",
955
+ "p-h3-image": "reference_images",
956
+ "minimax-h3-image": "reference_images"
939
957
  };
940
958
  InputSchema3 = z.object({
941
959
  prompt: z.string().min(1).max(8e3),
@@ -972,7 +990,7 @@ var init_character = __esm({
972
990
  },
973
991
  aspect_ratio: {
974
992
  type: "string",
975
- description: 'e.g. "1:1", "16:9", "9:16". Snapped to the nearest ratio the model supports, and dropped for models with no aspect_ratio field (including the default seedream-5, which sizes by image_size: set that in params with an explicit model).'
993
+ description: 'e.g. "1:1", "16:9", "9:16". Snapped to the nearest ratio the model supports, and dropped for models with no aspect_ratio field (including the default seedream-5-5, which sizes by image_size: set that in params with an explicit model).'
976
994
  },
977
995
  params: PARAMS_JSON_SCHEMA
978
996
  },
@@ -980,7 +998,7 @@ var init_character = __esm({
980
998
  };
981
999
  characterSkill = {
982
1000
  name: "pixeldojo_character",
983
- description: "Generate or edit consistent characters across shots. Pass reference_image_urls (up to 9) to lock identity \u2014 send every photo of the person you have, since more angles hold the face far better than one. Defaults to seedream-5.",
1001
+ description: "Generate or edit consistent characters across shots. Pass reference_image_urls (up to 9) to lock identity \u2014 send every photo of the person you have, since more angles hold the face far better than one. Defaults to seedream-5-5.",
984
1002
  inputZod: InputSchema3,
985
1003
  inputJsonSchema: InputJsonSchema3,
986
1004
  allowedModels: [...ALLOWED],
@@ -1679,7 +1697,7 @@ async function filmApi(ctx, method, path, body) {
1679
1697
  Authorization: `Bearer ${ctx.apiKey}`,
1680
1698
  "Content-Type": "application/json",
1681
1699
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
1682
- "x-pixeldojo-skill": "film"
1700
+ "x-pixeldojo-skill": "pixeldojo_film"
1683
1701
  },
1684
1702
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
1685
1703
  });
@@ -2008,7 +2026,7 @@ It is saved in the Library folder "Film: ${film.title}".`,
2008
2026
  }
2009
2027
  const startExport = async () => {
2010
2028
  const r = await filmApi(ctx, "POST", `/api/films/${filmId}/export`, {});
2011
- return r.status === 200 || r.status === 409 ? null : r.data.error ?? `HTTP ${r.status}`;
2029
+ return r.status === 200 || r.status === 409 && r.data.code === "export-running" ? null : r.data.error ?? `HTTP ${r.status}`;
2012
2030
  };
2013
2031
  if (film.exportError && all.length > 0 && all.every((sh) => sh.status === "clip-ready")) {
2014
2032
  const again = await startExport();
@@ -2438,8 +2456,6 @@ function buildVideoPayload(verb, apiId, i) {
2438
2456
  ...vd !== void 0 ? { source_duration_seconds: vd } : {}
2439
2457
  }
2440
2458
  };
2441
- case "flux-video-edit":
2442
- return { body: { prompt, video: i.videoUrl, video_duration: Math.ceil(vd) }, note: droppedImages };
2443
2459
  case "xai-video-edit":
2444
2460
  return { body: { prompt, video_url: i.videoUrl, video_duration: vd }, note: droppedImages };
2445
2461
  case "happyhorse-1.0-video-edit":
@@ -2470,6 +2486,17 @@ function buildVideoPayload(verb, apiId, i) {
2470
2486
  }
2471
2487
  }
2472
2488
  if (verb === "move") {
2489
+ if (apiId === "p-video-2-pro-replace") {
2490
+ return {
2491
+ body: {
2492
+ prompt: "",
2493
+ video: i.videoUrl,
2494
+ images: i.images.slice(0, 2),
2495
+ video_duration: vd,
2496
+ ...prompt ? { instruction_prompt: prompt } : {}
2497
+ }
2498
+ };
2499
+ }
2473
2500
  if (apiId === "wan-video-character-swap") {
2474
2501
  return { body: { prompt: "", image_url: i.images[0], video_url: i.videoUrl, video_duration: vd } };
2475
2502
  }
@@ -2591,8 +2618,6 @@ var init_video = __esm({
2591
2618
  VIDEO_STUDIO_ENGINES = {
2592
2619
  change: [
2593
2620
  "seedance-2-5-video-edit",
2594
- "flux-video-edit",
2595
- "kling-video-edit",
2596
2621
  "kling-4-0",
2597
2622
  "minimax-h3-turbo",
2598
2623
  "wan-video-edit",
@@ -2600,24 +2625,24 @@ var init_video = __esm({
2600
2625
  "happyhorse-1.0-video-edit",
2601
2626
  "xai-video-edit"
2602
2627
  ],
2603
- move: ["kling-motion-control", "wan-video-character-swap"],
2628
+ move: ["p-video-2-pro-replace", "kling-motion-control", "wan-video-character-swap"],
2604
2629
  extend: ["seedance-2-5-video-edit", "wan-2.7-i2v", "grok-video-extend"]
2605
2630
  };
2606
2631
  VERBS = ["change", "move", "extend"];
2607
2632
  BACKWARD_ENGINES = /* @__PURE__ */ new Set(["seedance-2-5-video-edit"]);
2608
2633
  clamp = (n, min, max) => Math.min(max, Math.max(min, Math.round(n)));
2609
2634
  NEEDS_VIDEO_DURATION = /* @__PURE__ */ new Set([
2610
- "flux-video-edit",
2611
- "kling-video-edit",
2612
2635
  "wan-video-edit",
2613
2636
  "seedance-video-edit",
2614
2637
  "happyhorse-1.0-video-edit",
2615
2638
  "xai-video-edit",
2616
2639
  "kling-motion-control",
2617
2640
  "wan-video-character-swap",
2618
- "kling-4-0"
2641
+ "kling-4-0",
2642
+ // Its schema defaults the length, but the price is per second: a missing length would under-charge.
2643
+ "p-video-2-pro-replace"
2619
2644
  ]);
2620
- NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["flux-video-edit", "xai-video-edit", "minimax-h3-turbo"]);
2645
+ NO_IMAGE_ENGINES = /* @__PURE__ */ new Set(["xai-video-edit", "minimax-h3-turbo"]);
2621
2646
  InputSchema11 = z.object({
2622
2647
  verb: z.enum(VERBS),
2623
2648
  prompt: z.string().max(8e3).optional(),
@@ -2643,7 +2668,7 @@ var init_video = __esm({
2643
2668
  },
2644
2669
  prompt: {
2645
2670
  type: "string",
2646
- 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)."
2671
+ 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)."
2647
2672
  },
2648
2673
  video_url: {
2649
2674
  type: "string",
@@ -2709,7 +2734,7 @@ async function episodeApi(ctx, method, path, body) {
2709
2734
  Authorization: `Bearer ${ctx.apiKey}`,
2710
2735
  "Content-Type": "application/json",
2711
2736
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
2712
- "x-pixeldojo-skill": "episode"
2737
+ "x-pixeldojo-skill": "pixeldojo_episode"
2713
2738
  },
2714
2739
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2715
2740
  });
@@ -2950,7 +2975,7 @@ async function dramaApi(ctx, method, path, body) {
2950
2975
  Authorization: `Bearer ${ctx.apiKey}`,
2951
2976
  "Content-Type": "application/json",
2952
2977
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
2953
- "x-pixeldojo-skill": "drama"
2978
+ "x-pixeldojo-skill": "pixeldojo_drama"
2954
2979
  },
2955
2980
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
2956
2981
  });
@@ -2964,9 +2989,10 @@ function describeDrama(v, o = DRAMA_OPTS) {
2964
2989
  const next2 = (action) => `${o.tool} with {"${o.idField}": "${id}", "action": "${action}"}`;
2965
2990
  const music = o.unit === "section";
2966
2991
  const what = music ? "music video" : "episode";
2992
+ 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.` };
2967
2993
  if (v.status === "failed") return { kind: "text", text: `The ${what} stopped: ${v.error ?? "unknown error"}. Each render that failed was refunded.${v.beats.some((b) => b.url) ? ` Re-film ${o.unit}s with action "redo".` : ""}` };
2968
2994
  if (v.status === "quote") {
2969
- const cost = music ? `Filming costs about ${v.estimate_credits} credits (cast portraits and ${v.sections?.length ?? "several"} sections lip-synced to the song).` : `Filming costs about ${v.estimate_credits} credits (cast portraits, 12-second scenes with their dialogue and sound${v.score === false ? ", no music" : ", and one score over the finished edit"}); every scene is checked, and a scene that drops a line or shows a clear fault waits for the user's OK before it is filmed again, at the scene's own price (a render that fails is tried again free).`;
2995
+ 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 ${v.dialogue === false ? "their own sound and no dialogue" : "their dialogue and sound"}${v.score === false ? ", no music" : ", and one score over the finished edit"}); every scene is checked, and a scene that drops a line or shows a clear fault waits for the user's OK before it is filmed again, at the scene's own price (a render that fails is tried again free).`;
2970
2996
  return {
2971
2997
  kind: "text",
2972
2998
  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")
@@ -3049,9 +3075,12 @@ var init_drama = __esm({
3049
3075
  end_text: z.string().max(60).optional(),
3050
3076
  brand_color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
3051
3077
  logo_url: z.string().url().optional(),
3052
- cast: z.array(z.object({ name: z.string().min(1).max(40), image_url: z.string().url() })).max(5).optional(),
3078
+ cast: z.array(z.object({ name: z.string().min(1).max(40), image_url: z.string().url(), outfit_image_url: z.string().url().optional() })).max(5).optional(),
3079
+ length_sec: z.number().int().min(24).max(180).multipleOf(6).optional(),
3053
3080
  scenes: z.number().int().min(1).max(15).optional(),
3054
3081
  score: z.boolean().optional(),
3082
+ dialogue: z.boolean().optional(),
3083
+ engine: z.enum(["seedance-2-5", "seedance-2-5-flash", "wan-3-0-video", "kling-4-0"]).optional(),
3055
3084
  drama_id: z.string().max(60).optional(),
3056
3085
  beats: z.array(z.number().int().min(1).max(15)).min(1).max(15).optional(),
3057
3086
  notes: z.array(z.string().max(300)).max(15).optional()
@@ -3062,7 +3091,7 @@ var init_drama = __esm({
3062
3091
  action: {
3063
3092
  type: "string",
3064
3093
  enum: ["create", "produce", "status", "redo", "cut"],
3065
- description: "create (default, free): writes the episode plan (5 scenes unless scenes says, cast, lines) and returns drama_id and the price. Show the plan and price to the user and wait for their OK. produce (charged): films it. status (free): progress; call every ~60s until it returns the episode. redo (charged): re-film chosen beats of a finished episode or one waiting at the review, then it cuts again. cut (free): when the review flagged beats and the user wants it as it is, cut it now."
3094
+ 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."
3066
3095
  },
3067
3096
  idea: { type: "string", description: "Create: the story in a line or two (who, the conflict, the twist)." },
3068
3097
  format: { type: "string", enum: ["9:16", "16:9"], description: "Create: vertical 9:16 (default) or widescreen 16:9." },
@@ -3070,7 +3099,7 @@ var init_drama = __esm({
3070
3099
  kind: {
3071
3100
  type: "string",
3072
3101
  enum: [...KINDS],
3073
- description: "Create: micro-drama (default: episode one with a hook and a cliffhanger), short-film (a complete 60 s story) or trailer (sells a film that does not exist, never shows the ending)."
3102
+ 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)."
3074
3103
  },
3075
3104
  look: { type: "string", enum: [...LOOKS2], description: "Create (optional): the visual style and genre. Default live-action (a micro-drama story type brings its own)." },
3076
3105
  story_type: {
@@ -3090,14 +3119,18 @@ var init_drama = __esm({
3090
3119
  type: "object",
3091
3120
  properties: {
3092
3121
  name: { type: "string", description: "The character's name as the story uses it." },
3093
- image_url: { type: "string", description: "A clear front-facing portrait on the PixelDojo CDN." }
3122
+ 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).` },
3123
+ 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.` }
3094
3124
  },
3095
3125
  required: ["name", "image_url"]
3096
3126
  },
3097
- description: "Create (optional): up to 5 characters to star (people, animals or creatures), each with a portrait on the PixelDojo CDN (a saved character from pixeldojo_library). Others get a portrait made for them."
3127
+ 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.'
3098
3128
  },
3099
- scenes: { type: "integer", minimum: 1, maximum: 15, description: "Create (optional): how many 12-second scenes, default 5. Pass it when the user names a count or a length (about 12 s a scene)." },
3129
+ 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." },
3130
+ scenes: { type: "integer", minimum: 1, maximum: 15, description: "Create (optional, older): the length in 12-second units (5 = 60 s). Prefer length_sec." },
3131
+ engine: { type: "string", enum: ["seedance-2-5", "seedance-2-5-flash", "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), seedance-2-5-flash (the same model family, faster and about a third cheaper, 720p), 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." },
3100
3132
  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)." },
3133
+ dialogue: { type: "boolean", description: "Create (optional): pass false for a film with no dialogue, where nobody speaks and the story is told in pictures, sound and the score. Use it when the user asks for no dialogue, a silent or wordless film, or visuals with music only; otherwise leave it out (characters speak)." },
3101
3134
  drama_id: { type: "string", description: "From create. Required for produce, status and redo." },
3102
3135
  beats: { type: "array", items: { type: "integer", minimum: 1, maximum: 15 }, description: "Redo: the beat numbers to film again." },
3103
3136
  notes: { type: "array", items: { type: "string" }, description: "Redo (optional): what to fix in each of those beats, in the same order." }
@@ -3113,10 +3146,10 @@ var init_drama = __esm({
3113
3146
  DRAMA_OPTS = { tool: "pixeldojo_drama", idField: "drama_id", unit: "beat" };
3114
3147
  dramaSkill = {
3115
3148
  name: "pixeldojo_drama",
3116
- description: "Make a 60-second micro-drama episode (hook, twist, cliffhanger; optionally a classic genre such as billionaire, werewolf or mafia), short film (a complete story) or movie trailer from an idea, vertical 9:16 or 16:9, in any of Film Studio's looks (live-action, anime, 3D animation, noir, horror, sci-fi, western, romance, comedy and more). Five 12-second scenes, each filmed in one pass with several cuts, the cast's faces kept from portraits and every line spoken and lip-synced, cut together with their own sound and one score over the finished edit, with free motion graphics (title card, name tags, end screen; each can be turned off), and saved to the Library. Not for music videos (pixeldojo_music_video) create is free and returns the plan and the exact price (about 380 credits for a regular episode, about 160 for mature work); produce films it; then call status until it is finished (10-15 minutes).",
3149
+ 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 (or no dialogue at all with dialogue: false), 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).",
3117
3150
  inputZod: InputSchema13,
3118
3151
  inputJsonSchema: InputJsonSchema13,
3119
- allowedModels: ["seedance-2-5", "seedream-4"],
3152
+ allowedModels: ["seedance-2-5", "seedance-2-5-flash", "seedream-4"],
3120
3153
  defaultModel: "seedance-2-5",
3121
3154
  async run(input, ctx) {
3122
3155
  if (input.action === "create") {
@@ -3128,8 +3161,11 @@ var init_drama = __esm({
3128
3161
  ...input.mature ? { mature: true } : {},
3129
3162
  ...input.look ? { look: input.look } : {},
3130
3163
  ...input.story_type ? { story_type: input.story_type } : {},
3164
+ ...input.length_sec ? { length_sec: input.length_sec } : {},
3131
3165
  ...input.scenes ? { scenes: input.scenes } : {},
3166
+ ...input.engine ? { engine: input.engine } : {},
3132
3167
  ...input.score === false ? { score: false } : {},
3168
+ ...input.dialogue === false ? { dialogue: false } : {},
3133
3169
  ...Object.fromEntries(
3134
3170
  ["title_card", "name_tags", "end_screen", "end_text", "brand_color", "logo_url"].filter((k) => input[k] !== void 0).map((k) => [k, input[k]])
3135
3171
  ),
@@ -3157,7 +3193,7 @@ async function api(ctx, method, path, body) {
3157
3193
  Authorization: `Bearer ${ctx.apiKey}`,
3158
3194
  "Content-Type": "application/json",
3159
3195
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
3160
- "x-pixeldojo-skill": "music_video"
3196
+ "x-pixeldojo-skill": "pixeldojo_music_video"
3161
3197
  },
3162
3198
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
3163
3199
  });
@@ -3286,7 +3322,7 @@ async function kataApi(ctx, method, path, body) {
3286
3322
  Authorization: `Bearer ${ctx.apiKey}`,
3287
3323
  "Content-Type": "application/json",
3288
3324
  "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
3289
- "x-pixeldojo-skill": "kata"
3325
+ "x-pixeldojo-skill": "pixeldojo_kata"
3290
3326
  },
3291
3327
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
3292
3328
  });
@@ -3338,6 +3374,7 @@ var init_kata = __esm({
3338
3374
  photos: z.array(z.object({ url: z.string().url(), note: z.string().max(80).optional() })).max(4).optional(),
3339
3375
  instructions: z.string().max(1500).optional(),
3340
3376
  look: z.string().max(80).optional(),
3377
+ engine: z.enum(["seedance-2-5", "seedance-2-5-flash"]).optional(),
3341
3378
  kata_id: z.string().max(60).optional()
3342
3379
  });
3343
3380
  InputJsonSchema15 = {
@@ -3370,6 +3407,7 @@ var init_kata = __esm({
3370
3407
  },
3371
3408
  instructions: { type: "string", description: "Create (optional): what to change, e.g. the setting, the words on screen, where the product goes." },
3372
3409
  look: { type: "string", description: 'Create (optional): style transfer, one look for every new shot, e.g. "hand-drawn anime".' },
3410
+ engine: { type: "string", enum: ["seedance-2-5", "seedance-2-5-flash"], description: "Create (optional): the video model the new clips film on. seedance-2-5 (default) or seedance-2-5-flash (faster, about a third cheaper). Pass it only when the user asks." },
3373
3411
  kata_id: { type: "string", description: "From create. Required for produce and status." }
3374
3412
  }
3375
3413
  };
@@ -3391,7 +3429,7 @@ var init_kata = __esm({
3391
3429
  description: `Kata: recreate a reference edit (a viral short, an ad, a montage, a trailer, 4 to 60 s) with the user's own characters, products and photos. It keeps the same pacing and cut points to the frame, the transitions, the camera moves, the caption style and the music feel (an original score on the same tempo, never the reference's audio); dance moves come across by motion transfer, and a look can restyle every shot. Start from a reference_url or a Kata library preset (action "presets"). create is free and returns the plan and the price; produce makes it; then call status until it is finished. Saved to the Library.`,
3392
3430
  inputZod: InputSchema15,
3393
3431
  inputJsonSchema: InputJsonSchema15,
3394
- allowedModels: ["seedance-2-5", "google-nano-banana", "kling-motion-control"],
3432
+ allowedModels: ["seedance-2-5", "seedance-2-5-flash", "google-nano-banana", "kling-motion-control"],
3395
3433
  defaultModel: "seedance-2-5",
3396
3434
  async run(input, ctx) {
3397
3435
  if (input.action === "presets") {
@@ -3410,7 +3448,8 @@ var init_kata = __esm({
3410
3448
  ...input.cast?.length ? { cast: input.cast } : {},
3411
3449
  ...input.photos?.length ? { photos: input.photos } : {},
3412
3450
  ...input.instructions ? { instructions: input.instructions } : {},
3413
- ...input.look ? { look: input.look } : {}
3451
+ ...input.look ? { look: input.look } : {},
3452
+ ...input.engine ? { engine: input.engine } : {}
3414
3453
  });
3415
3454
  if (!created.data.id) return { kind: "text", text: `Could not start the Kata: ${errorText3(created.data, created.status)}` };
3416
3455
  const planned = await waitForPlan3(ctx, created.data.id);
@@ -3450,7 +3489,7 @@ async function run13(input, ctx) {
3450
3489
  aspect_ratio: input.aspect_ratio,
3451
3490
  duration: input.duration,
3452
3491
  reference_video_url: input.reference_video_url,
3453
- brand_kit: input.brand_kit
3492
+ brand_kit: input.brand_kit === false ? false : input.brand_kit_id ?? input.brand_kit
3454
3493
  };
3455
3494
  const client = ctx.client;
3456
3495
  const submitted = await client.runAd(body);
@@ -3495,7 +3534,8 @@ var init_ad = __esm({
3495
3534
  aspect_ratio: z.enum(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]).optional(),
3496
3535
  duration: z.number().int().min(4).max(15).optional(),
3497
3536
  reference_video_url: z.string().url().optional(),
3498
- brand_kit: z.boolean().optional()
3537
+ brand_kit: z.boolean().optional(),
3538
+ brand_kit_id: z.string().max(80).optional()
3499
3539
  }).refine(
3500
3540
  (v) => v.product_url || (v.product_image_urls?.length || v.product_image_paths?.length) && v.product_name,
3501
3541
  {
@@ -3589,7 +3629,11 @@ var init_ad = __esm({
3589
3629
  },
3590
3630
  brand_kit: {
3591
3631
  type: "boolean",
3592
- description: "The account's brand kit (palette, voice, selling points; see pixeldojo_brand_kit) rides on every ad. false skips it."
3632
+ description: "The account's default brand kit (palette, voice, selling points; see pixeldojo_brand_kit) rides on every ad. false skips it."
3633
+ },
3634
+ brand_kit_id: {
3635
+ type: "string",
3636
+ description: "Use this saved brand kit instead of the default (ids listed by pixeldojo_brand_kit)."
3593
3637
  }
3594
3638
  }
3595
3639
  // "product_url OR product_name+images" is enforced by the server and the
@@ -3930,7 +3974,8 @@ async function run18(input, ctx) {
3930
3974
  }
3931
3975
  const lines2 = characters.map(
3932
3976
  (c) => `- ${c.mention} (${c.kind})${c.description ? `: ${c.description.slice(0, 100)}` : ""}
3933
- images: ${c.image_urls.slice(0, 4).join(" ")}`
3977
+ images: ${c.image_urls.slice(0, 4).join(" ")}${c.video_pair && c.video_image_urls?.length === 2 ? `
3978
+ for video (face, outfit; pass both to pixeldojo_drama cast as image_url + outfit_image_url): ${c.video_image_urls.join(" ")}` : ""}`
3934
3979
  );
3935
3980
  return {
3936
3981
  kind: "text",
@@ -4153,7 +4198,8 @@ async function effectRun(input, ctx) {
4153
4198
  });
4154
4199
  const data = await res.json().catch(() => ({}));
4155
4200
  if (!res.ok || !data.jobId) {
4156
- return { kind: "text", text: `Effect not started (nothing charged): ${data.error ?? `HTTP ${res.status}`}` };
4201
+ const err = typeof data.error === "string" ? data.error : data.error?.message;
4202
+ return { kind: "text", text: `Effect not started (nothing charged): ${err ?? `HTTP ${res.status}`}` };
4157
4203
  }
4158
4204
  const result = await pollJobUntilDoneOrHandoff(data.jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
4159
4205
  now: ctx.pollOverrides?.now,
@@ -4285,7 +4331,15 @@ ${describeKit(r2.data.brand_kit)}` };
4285
4331
  }
4286
4332
  const r = await call2(ctx, "pixeldojo_brand_kit", path);
4287
4333
  if (!r.ok) return { kind: "text", text: `Could not read the brand kit: ${r.error}` };
4288
- return { kind: "text", text: describeKit(r.data.brand_kit ?? null) };
4334
+ const all = r.data.brand_kits ?? [];
4335
+ const others = all.filter((k) => !k.isDefault);
4336
+ return {
4337
+ kind: "text",
4338
+ text: [
4339
+ describeKit(r.data.brand_kit ?? null),
4340
+ ...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("; ")}`] : []
4341
+ ].join("\n")
4342
+ };
4289
4343
  }
4290
4344
  async function templatesRun(input, ctx) {
4291
4345
  const q = new URLSearchParams();
@@ -4331,7 +4385,7 @@ async function imageRun(input, ctx) {
4331
4385
  engine: input.engine,
4332
4386
  count: input.count,
4333
4387
  reference_ad_url: input.reference_ad_url,
4334
- brand_kit: input.brand_kit
4388
+ brand_kit: input.brand_kit === false ? false : input.brand_kit_id ?? input.brand_kit
4335
4389
  }
4336
4390
  });
4337
4391
  if (!r.ok) return { kind: "text", text: `Images not started: ${r.error}` };
@@ -4401,7 +4455,7 @@ async function motionRun(input, ctx) {
4401
4455
  video_url: input.video_url,
4402
4456
  brand_name: input.brand_name,
4403
4457
  colors: input.colors?.map((c) => c.trim().toLowerCase()),
4404
- brand_kit: input.brand_kit
4458
+ brand_kit: input.brand_kit === false ? false : input.brand_kit_id ?? input.brand_kit
4405
4459
  }
4406
4460
  });
4407
4461
  if (!r.ok) return { kind: "text", text: `Not started: ${r.error}` };
@@ -4498,7 +4552,8 @@ var init_marketing = __esm({
4498
4552
  motion_id: z.string().max(80).optional(),
4499
4553
  brand_name: z.string().max(60).optional(),
4500
4554
  colors: z.array(z.string()).max(5).optional(),
4501
- brand_kit: z.boolean().optional()
4555
+ brand_kit: z.boolean().optional(),
4556
+ brand_kit_id: z.string().max(80).optional()
4502
4557
  });
4503
4558
  props = {
4504
4559
  action: {
@@ -4534,7 +4589,8 @@ var init_marketing = __esm({
4534
4589
  motion_id: { type: "string", description: "status: the id a motion or end_card call returned." },
4535
4590
  brand_name: { type: "string", description: "motion: the brand shown when there is no logo. Default: the brand kit." },
4536
4591
  colors: { type: "array", items: { type: "string" }, maxItems: 5, description: "motion: #RRGGBB, lead color first. Default: the brand kit." },
4537
- brand_kit: { type: "boolean", description: "false skips the brand kit for this call." }
4592
+ brand_kit: { type: "boolean", description: "false skips the brand kit for this call." },
4593
+ brand_kit_id: { type: "string", description: "Use this saved brand kit instead of the default (ids listed by pixeldojo_brand_kit)." }
4538
4594
  };
4539
4595
  marketingSkill = {
4540
4596
  name: "pixeldojo_marketing",
@@ -5062,7 +5118,7 @@ var init_skills = __esm({
5062
5118
  }
5063
5119
  };
5064
5120
  });
5065
- render = (title) => ({ title, readOnlyHint: false, destructiveHint: false, openWorldHint: false });
5121
+ render = (title) => ({ title, readOnlyHint: false, destructiveHint: false, openWorldHint: true });
5066
5122
  read = (title) => ({ title, readOnlyHint: true, destructiveHint: false, openWorldHint: false });
5067
5123
  TOOL_ANNOTATIONS = {
5068
5124
  pixeldojo_generate: render("Generate image or video"),
@@ -5077,22 +5133,22 @@ var init_skills = __esm({
5077
5133
  pixeldojo_effect: render("Run one-click effect"),
5078
5134
  pixeldojo_video: render("Change, animate or extend video"),
5079
5135
  pixeldojo_ad: render("Make video ad"),
5080
- pixeldojo_brand_kit: render("Brand kit from website"),
5136
+ pixeldojo_brand_kit: { ...render("Brand kit from website"), destructiveHint: true },
5081
5137
  pixeldojo_marketing: render("Image ads, motion graphics and templates"),
5082
5138
  pixeldojo_campaign: render("Make product launch campaign"),
5083
- pixeldojo_film: render("Make short film"),
5139
+ pixeldojo_film: { ...render("Make short film"), destructiveHint: true },
5084
5140
  pixeldojo_episode: render("Make faceless video"),
5085
5141
  pixeldojo_drama: render("Make micro-drama, short film or trailer"),
5086
5142
  pixeldojo_music_video: render("Make music video for a song"),
5087
5143
  pixeldojo_kata: render("Recreate an edit with your cast"),
5088
5144
  pixeldojo_run_workflow: render("Run saved workflow"),
5089
5145
  pixeldojo_upload: render("Upload local file"),
5090
- pixeldojo_save_workflow: { ...render("Save workflow"), destructiveHint: true },
5146
+ pixeldojo_save_workflow: { ...render("Save workflow"), destructiveHint: true, openWorldHint: false },
5091
5147
  pixeldojo_status: read("Check job status"),
5092
5148
  pixeldojo_campaign_status: read("Check campaign status"),
5093
5149
  pixeldojo_library: read("Search media library"),
5094
- pixeldojo_save: render("Save to My Media"),
5095
- pixeldojo_save_character: render("Save character or element"),
5150
+ pixeldojo_save: { ...render("Save to My Media"), openWorldHint: false },
5151
+ pixeldojo_save_character: { ...render("Save character or element"), openWorldHint: false },
5096
5152
  pixeldojo_build_character: render("Build AI character from traits"),
5097
5153
  pixeldojo_perform: render("Make character dance or do a trend"),
5098
5154
  pixeldojo_list_workflows: read("List saved workflows"),
@@ -5151,7 +5207,7 @@ function makeRedactedLogger(target = console) {
5151
5207
  }
5152
5208
 
5153
5209
  // src/server.ts
5154
- var PACKAGE_VERSION = "0.31.0" ;
5210
+ var PACKAGE_VERSION = "0.36.0" ;
5155
5211
  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.";
5156
5212
  function buildServer(options) {
5157
5213
  if (!options.apiKey) {
@@ -5269,7 +5325,10 @@ function errorToActionableMessage(err) {
5269
5325
  return "Authentication failed. Check PIXELDOJO_API_KEY. Get a key at https://pixeldojo.ai/api-platform/api-keys.";
5270
5326
  }
5271
5327
  if (e.status === 402 || e.code === "insufficient_credits") {
5272
- return "Insufficient credits. Top up at https://pixeldojo.ai/api-platform/buy-credits.";
5328
+ return (
5329
+ // ponytail: no checkout link, ChatGPT's plugin review bans linking to a purchase page.
5330
+ "Not enough credits for this. Add credits in your Pixel Dojo account, then try again."
5331
+ );
5273
5332
  }
5274
5333
  if (e.status === 429 || e.code === "rate_limit") {
5275
5334
  return "Rate limit hit. Try again in a few seconds.";