@pixeldojo/mcp 0.24.0 → 0.26.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.cjs CHANGED
@@ -1326,6 +1326,7 @@ async function run7(input, ctx) {
1326
1326
  ...callout ? {
1327
1327
  callout_title: callout.title,
1328
1328
  callout_subtitle: callout.subtitle,
1329
+ callout_style: callout.style,
1329
1330
  callout_start: callout.start_sec,
1330
1331
  callout_duration: callout.duration_sec
1331
1332
  } : {},
@@ -1389,6 +1390,7 @@ var init_overlay = __esm({
1389
1390
  callout: zod.z.object({
1390
1391
  title: zod.z.string().max(32),
1391
1392
  subtitle: zod.z.string().max(40).optional(),
1393
+ style: zod.z.enum(["label", "classic"]).optional(),
1392
1394
  start_sec: zod.z.number().min(0).optional(),
1393
1395
  duration_sec: zod.z.number().min(1).max(10).optional()
1394
1396
  }).optional(),
@@ -1445,11 +1447,12 @@ var init_overlay = __esm({
1445
1447
  },
1446
1448
  callout: {
1447
1449
  type: "object",
1448
- description: 'Lower-third label that slides in from the left: a benefit ("Ships in 24h") or a name and title.',
1450
+ description: 'Lower third: a benefit ("Ships in 24h") or a name and title. style label (default) is a brand-colored box sliding in; classic is the broadcast strap, an accent bar beside a bold name and a lighter title (bottom band on 16:9).',
1449
1451
  required: ["title"],
1450
1452
  properties: {
1451
1453
  title: { type: "string", maxLength: 32, description: "The label." },
1452
- subtitle: { type: "string", maxLength: 40, description: "Optional second line." },
1454
+ subtitle: { type: "string", maxLength: 40, description: "Optional second line (the title, for classic)." },
1455
+ style: { type: "string", enum: ["label", "classic"], description: "Default label." },
1453
1456
  start_sec: { type: "number", description: "Default 3." },
1454
1457
  duration_sec: { type: "number", minimum: 1, maximum: 10, description: "Default 4." }
1455
1458
  }
@@ -1836,14 +1839,17 @@ async function plan(input, ctx) {
1836
1839
  look: input.look ?? (music ? MUSIC_VIDEO_LOOKS[0] : "live-action"),
1837
1840
  lengthTarget: input.length,
1838
1841
  ...!music && input.pacing === "trailer" ? { pacing: "trailer" } : {},
1842
+ ...input.quality === "final" ? { clipQuality: "final" } : {},
1839
1843
  assetIds: [],
1840
1844
  ...music ? {
1841
1845
  // No script step names a music video, so it would stay "Untitled film".
1842
1846
  ...input.idea?.trim() ? { title: input.idea.trim().slice(0, 80) } : {},
1843
1847
  musicVideoMode: true,
1844
1848
  musicUrl: input.song_url,
1845
- ...input.song_duration_sec ? { musicDurationSec: input.song_duration_sec } : {},
1846
- clipEngine: MUSIC_CLIP_ENGINE_ID
1849
+ ...input.song_duration_sec ? { musicDurationSec: input.song_duration_sec } : {}
1850
+ // Engine left on Auto, like the web composer's music mode, so the
1851
+ // planner routes a mature song to H3 and the rest to P-Video 2 Pro
1852
+ // Reference (tests/contracts/mcp-film-music-parity.test.ts).
1847
1853
  } : {}
1848
1854
  });
1849
1855
  if (!created.data.film) return { kind: "text", text: `Could not create the film: ${created.data.error ?? created.status}` };
@@ -2245,14 +2251,13 @@ async function run10(input, ctx) {
2245
2251
  if (action === "redo") return redoShots(input, ctx);
2246
2252
  return advance(input.film_id, input.soundtrack, input.music_job_id, ctx);
2247
2253
  }
2248
- var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, MUSIC_CLIP_ENGINE_ID, InputSchema10, InputJsonSchema10, shots, benignDispatchRefusal, count, frameDone, RETRY_CLEARS, REFUSED, isRefused, PLANNER_POLL_MS, PLANNER_MAX_POLLS, isMusicVideo, needsSlice, stillPlanningText, LOOK_MUSIC, ARC, filmSkill;
2254
+ var STORY_LOOKS, MUSIC_VIDEO_LOOKS, LOOKS, InputSchema10, InputJsonSchema10, shots, benignDispatchRefusal, count, frameDone, RETRY_CLEARS, REFUSED, isRefused, PLANNER_POLL_MS, PLANNER_MAX_POLLS, isMusicVideo, needsSlice, stillPlanningText, LOOK_MUSIC, ARC, filmSkill;
2249
2255
  var init_film = __esm({
2250
2256
  "src/skills/film.ts"() {
2251
2257
  init_upload_helpers();
2252
2258
  STORY_LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
2253
2259
  MUSIC_VIDEO_LOOKS = ["rap-video", "neon-club", "stage-performance", "retro-camcorder", "y2k-pop", "moody-rnb", "rock-grit", "dream-pop", "k-pop"];
2254
2260
  LOOKS = [...STORY_LOOKS, ...MUSIC_VIDEO_LOOKS];
2255
- MUSIC_CLIP_ENGINE_ID = "minimax-h3-reference";
2256
2261
  InputSchema10 = zod.z.object({
2257
2262
  idea: zod.z.string().max(2e3).optional().describe("One-to-three sentence film idea. The studio writes the screenplay (free). Provide idea OR script for planning."),
2258
2263
  script: zod.z.string().max(2e4).optional().describe("A full screenplay to plan instead of an idea (sluglines, action lines, dialogue)."),
@@ -2263,6 +2268,7 @@ var init_film = __esm({
2263
2268
  lyrics: zod.z.string().max(1e4).optional().describe("Music video mode: the song lyrics, so shots land on the right lines. Optional."),
2264
2269
  aspect: zod.z.enum(["16:9", "9:16"]).default("16:9"),
2265
2270
  pacing: zod.z.enum(["story", "trailer"]).default("story").describe("'story' (default) plays every shot's action whole. 'trailer' cuts it like a trailer or promo: most shots keep only their first 1.5-3 seconds."),
2271
+ quality: zod.z.enum(["standard", "final"]).default("standard").describe("'standard' (default) renders 720p clips and a 720p film. 'final' renders 1080p clips where the engine supports it and exports the film at 1080p; it costs more per second."),
2266
2272
  length: zod.z.enum(["30s", "60s", "2-3min", "5min"]).default("60s").describe("Target length of the finished film."),
2267
2273
  film_id: zod.z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status/cast/redo."),
2268
2274
  action: zod.z.enum(["plan", "produce", "status", "cast", "redo"]).default("plan").describe("'plan' creates + storyboards (free). 'cast' casts, replaces or removes characters on an existing film. 'produce' starts charged generation. 'status' advances production and reports progress; poll it every ~30s until it returns the finished film. 'redo' renders a new take of the listed shots (charged), then status re-saves and re-exports."),
@@ -2302,6 +2308,11 @@ var init_film = __esm({
2302
2308
  lyrics: { type: "string", description: "Plan step, music video mode (optional): the song lyrics, so shots land on the right lines." },
2303
2309
  aspect: { type: "string", enum: ["16:9", "9:16"], description: "Plan step: frame aspect ratio. Default 16:9." },
2304
2310
  length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Plan step: target length. Default 60s. Ignored for a music video, whose length is the song." },
2311
+ quality: {
2312
+ type: "string",
2313
+ enum: ["standard", "final"],
2314
+ description: "Plan step: 'standard' (default) is 720p. 'final' renders 1080p clips where the engine supports it and exports at 1080p, at a higher price per second."
2315
+ },
2305
2316
  pacing: {
2306
2317
  type: "string",
2307
2318
  enum: ["story", "trailer"],
@@ -2850,7 +2861,7 @@ var init_episode = __esm({
2850
2861
  "src/skills/episode.ts"() {
2851
2862
  STYLES2 = ["editorial-motion", "paper-diorama", "hand-drawn", "storybook", "poster-vector", "soft-3d"];
2852
2863
  CATEGORIES = ["education", "history", "kids", "storytelling", "custom"];
2853
- ENGINES = ["omni-draft", "omni-final", "still-motion"];
2864
+ ENGINES = ["omni-draft", "omni-final", "omni-1080", "still-motion"];
2854
2865
  MIN_SECONDS = 60;
2855
2866
  MAX_SECONDS = 300;
2856
2867
  MAX_IN_FLIGHT = 4;
@@ -2885,7 +2896,7 @@ var init_episode = __esm({
2885
2896
  engine: {
2886
2897
  type: "string",
2887
2898
  enum: [...ENGINES],
2888
- description: "Create step (optional): render every scene with one engine. omni-draft: fast, cheap motion preview. omni-final: full quality motion with sound. still-motion: one illustration per scene, gently panned, cheapest. Default: the style picks."
2899
+ description: "Create step (optional): render every scene with one engine. omni-draft: fast, cheap motion preview. omni-final: full quality motion with sound (720p). omni-1080: the same at 1080p, and the episode exports at 1080p. still-motion: one illustration per scene, gently panned, cheapest. Default: the style picks."
2889
2900
  },
2890
2901
  music: { type: "boolean", description: "Create step: add a music bed under the narration. Default true." },
2891
2902
  title: { type: "string", description: "Create step (optional): episode title. Defaults to the topic." }
@@ -3578,6 +3589,148 @@ var init_save_character = __esm({
3578
3589
  };
3579
3590
  }
3580
3591
  });
3592
+ function priceOf(e) {
3593
+ if (!e.creditsByEngine) return `${e.credits ?? "?"} credits`;
3594
+ return Object.entries(e.creditsByEngine).map(([engine, c]) => `${engine}${engine === e.defaultEngine ? " (default)" : ""} ${c ?? "?"} credits`).join(" / ");
3595
+ }
3596
+ async function listRun(input, ctx) {
3597
+ const q = new URLSearchParams();
3598
+ for (const [k, v] of Object.entries(input)) if (v !== void 0 && v !== "") q.set(k, String(v));
3599
+ if (!input.limit) q.set("limit", "20");
3600
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/effects?${q}`, { headers: headers(ctx, "pixeldojo_effects") });
3601
+ const data = await res.json().catch(() => ({}));
3602
+ if (!res.ok) return { kind: "text", text: `Could not list effects: ${data.error ?? `HTTP ${res.status}`}` };
3603
+ const effects = data.effects ?? [];
3604
+ const cats = Object.entries(data.categories ?? {}).map(([fam, list]) => `${fam}: ${list.join(", ")}`);
3605
+ if (!effects.length) {
3606
+ return { kind: "text", text: ["No effects match. Categories:", ...cats].join("\n") };
3607
+ }
3608
+ const lines = effects.map((e) => {
3609
+ const ins = e.inputs.map(
3610
+ (i) => i.kind === "image" ? `photo "${i.label}"` : `text "${i.label}"${i.options ? ` [${i.options.join(" | ")}]` : ""}${i.optional ? " (optional)" : ""}`
3611
+ );
3612
+ return `- ${e.slug} (${e.family}, ${e.output}, ${priceOf(e)}): ${e.title}. ${e.tagline} Inputs: ${ins.join(", ")}.`;
3613
+ });
3614
+ const shown = data.total && data.total > effects.length ? `, showing ${effects.length}` : "";
3615
+ return {
3616
+ kind: "text",
3617
+ text: [
3618
+ `${data.total ?? effects.length} effects${shown}. Run one with pixeldojo_effect (slug, image_urls in the order listed, texts for text inputs).`,
3619
+ ...lines,
3620
+ ...input.search || input.family || input.category ? [] : ["", "Categories:", ...cats]
3621
+ ].join("\n")
3622
+ };
3623
+ }
3624
+ async function effectRun(input, ctx) {
3625
+ const images = input.image_paths?.length ? await uploadLocalFiles(input.image_paths, ctx) : input.image_urls ?? [];
3626
+ const res = await (ctx.fetch ?? fetch)(`${ctx.baseUrl}/api/v1/effects/${encodeURIComponent(input.slug)}/run`, {
3627
+ method: "POST",
3628
+ headers: headers(ctx, "pixeldojo_effect"),
3629
+ body: JSON.stringify({
3630
+ images,
3631
+ ...input.texts ? { texts: input.texts } : {},
3632
+ ...input.family ? { family: input.family } : {},
3633
+ ...input.engine ? { engine: input.engine } : {}
3634
+ })
3635
+ });
3636
+ const data = await res.json().catch(() => ({}));
3637
+ if (!res.ok || !data.jobId) {
3638
+ return { kind: "text", text: `Effect not started (nothing charged): ${data.error ?? `HTTP ${res.status}`}` };
3639
+ }
3640
+ const result = await pollJobUntilDoneOrHandoff(data.jobId, (id) => ctx.client.getJob(id), ctx.buildStatusUrl, {
3641
+ now: ctx.pollOverrides?.now,
3642
+ sleep: ctx.pollOverrides?.sleep
3643
+ });
3644
+ return pollResultToSkillResult(
3645
+ result,
3646
+ (urls) => urls.length === 0 ? `Job ${data.jobId} completed but returned no assets.` : `Done (${data.creditCost ?? "?"} credits). Asset: ${urls[0]}`
3647
+ );
3648
+ }
3649
+ var FAMILIES, ENGINES2, headers, ListSchema, effectsListSkill, RunSchema, runProperties, effectRunSkill;
3650
+ var init_effects = __esm({
3651
+ "src/skills/effects.ts"() {
3652
+ init_poll();
3653
+ init_upload_helpers();
3654
+ init_types();
3655
+ FAMILIES = ["video-effect", "photo-effect", "tool"];
3656
+ ENGINES2 = ["klein", "qwen"];
3657
+ headers = (ctx, skill) => ({
3658
+ Authorization: `Bearer ${ctx.apiKey}`,
3659
+ "Content-Type": "application/json",
3660
+ "x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
3661
+ "x-pixeldojo-skill": skill
3662
+ });
3663
+ ListSchema = zod.z.object({
3664
+ search: zod.z.string().max(200).optional(),
3665
+ family: zod.z.enum(FAMILIES).optional(),
3666
+ category: zod.z.string().max(60).optional(),
3667
+ limit: zod.z.number().int().min(1).max(100).optional()
3668
+ });
3669
+ effectsListSkill = {
3670
+ name: "pixeldojo_effects",
3671
+ description: "List PixelDojo's one-click effects: 283 video effects (a photo or two in, a 5 s clip out: AI Hug, Earth Zoom In, AI Dance), 30 photo effects (a photo in, a styled image out: action figure, Ghibli style, sticker, aging filter) and one-job tools (remove a watermark, object or background, headshots, photo restoration, relight, logo, thumbnail and poster makers). Search by words, family or category; each result gives the slug, inputs and credit price. Free. Run one with pixeldojo_effect.",
3672
+ inputZod: ListSchema,
3673
+ inputJsonSchema: {
3674
+ type: "object",
3675
+ properties: {
3676
+ search: { type: "string", description: 'Words like "hug", "action figure", "ghibli", "remove background".' },
3677
+ family: {
3678
+ type: "string",
3679
+ enum: FAMILIES,
3680
+ description: "video-effect = photo to clip, photo-effect = photo to styled image, tool = one-job edit."
3681
+ },
3682
+ category: { type: "string", description: 'A category name from a previous listing, e.g. "Viral", "Toys & 3D".' },
3683
+ limit: { type: "integer", minimum: 1, maximum: 100, description: "Default 20." }
3684
+ }
3685
+ },
3686
+ allowedModels: [],
3687
+ defaultModel: "",
3688
+ run: listRun
3689
+ };
3690
+ RunSchema = zod.z.object({
3691
+ slug: zod.z.string().min(1).max(80),
3692
+ family: zod.z.enum(FAMILIES).optional(),
3693
+ image_urls: zod.z.array(zod.z.string().url()).max(4).optional(),
3694
+ image_paths: zod.z.array(zod.z.string()).max(4).optional(),
3695
+ texts: zod.z.array(zod.z.string().max(200)).max(4).optional(),
3696
+ engine: zod.z.enum(ENGINES2).optional()
3697
+ });
3698
+ runProperties = {
3699
+ slug: { type: "string", description: 'The effect slug from pixeldojo_effects, e.g. "ai-hug", "action-figure", "watermark-remover".' },
3700
+ family: {
3701
+ type: "string",
3702
+ enum: FAMILIES,
3703
+ description: "Needed only for action-figure, comic-book and red-carpet, which are both a video and a photo effect."
3704
+ },
3705
+ image_urls: {
3706
+ type: "array",
3707
+ items: { type: "string", format: "uri" },
3708
+ description: "The photos as https URLs, in the order the listing names them (most take one; duo effects take two)."
3709
+ },
3710
+ image_paths: {
3711
+ type: "array",
3712
+ items: { type: "string" },
3713
+ description: "Absolute local photo paths instead of image_urls; uploaded first (24h temp storage)."
3714
+ },
3715
+ texts: {
3716
+ type: "array",
3717
+ items: { type: "string" },
3718
+ description: "Text inputs in listing order, for entries that list them (object to remove, new background, brand name)."
3719
+ },
3720
+ engine: { type: "string", enum: ENGINES2, description: "Photo effects only: leave out for the default engine." }
3721
+ };
3722
+ effectRunSkill = {
3723
+ name: "pixeldojo_effect",
3724
+ description: "Run one of PixelDojo's one-click effects on your photos: the prompt is built in, so pass only the slug from pixeldojo_effects and the photos (image_urls, or image_paths for local files), plus texts for tools that ask for them. Video effects return a 5 s clip (usually handed off; poll with pixeldojo_status), photo effects and tools an image. Costs the credits the listing shows.",
3725
+ hostedDescription: "Run one of PixelDojo's one-click effects on your photos: the prompt is built in, so pass only the slug from pixeldojo_effects and the photos as https image_urls, plus texts for tools that ask for them. Video effects return a 5 s clip (usually handed off; poll with pixeldojo_status), photo effects and tools an image. Costs the credits the listing shows.",
3726
+ inputZod: RunSchema,
3727
+ inputJsonSchema: { type: "object", required: ["slug"], properties: runProperties },
3728
+ allowedModels: [],
3729
+ defaultModel: "",
3730
+ run: effectRun
3731
+ };
3732
+ }
3733
+ });
3581
3734
  function substitutePlaceholders(args, inputs, prevUrl) {
3582
3735
  const missing = [];
3583
3736
  const fill = (value) => {
@@ -3826,6 +3979,8 @@ __export(skills_exports, {
3826
3979
  captionSkill: () => captionSkill,
3827
3980
  characterSkill: () => characterSkill,
3828
3981
  editSkill: () => editSkill,
3982
+ effectRunSkill: () => effectRunSkill,
3983
+ effectsListSkill: () => effectsListSkill,
3829
3984
  episodeSkill: () => episodeSkill,
3830
3985
  filmSkill: () => filmSkill,
3831
3986
  findHostedSkill: () => findHostedSkill,
@@ -3887,6 +4042,7 @@ var init_skills = __esm({
3887
4042
  init_library();
3888
4043
  init_save();
3889
4044
  init_save_character();
4045
+ init_effects();
3890
4046
  init_workflows();
3891
4047
  exports.ALL_SKILLS = [
3892
4048
  generateSkill,
@@ -3897,6 +4053,8 @@ var init_skills = __esm({
3897
4053
  captionSkill,
3898
4054
  overlaySkill,
3899
4055
  audioSkill,
4056
+ effectsListSkill,
4057
+ effectRunSkill,
3900
4058
  librarySkill,
3901
4059
  saveSkill,
3902
4060
  saveCharacterSkill,
@@ -3938,6 +4096,8 @@ var init_skills = __esm({
3938
4096
  pixeldojo_caption: render("Caption video"),
3939
4097
  pixeldojo_overlay: render("Add offer or end card"),
3940
4098
  pixeldojo_audio: render("Generate music, speech or sound"),
4099
+ pixeldojo_effects: read("List one-click effects"),
4100
+ pixeldojo_effect: render("Run one-click effect"),
3941
4101
  pixeldojo_video: render("Change, animate or extend video"),
3942
4102
  pixeldojo_ad: render("Make video ad"),
3943
4103
  pixeldojo_campaign: render("Make product launch campaign"),
@@ -3960,10 +4120,10 @@ function makeAttributedClient(options) {
3960
4120
  const { mcpVersion, skillName, baseFetch, ...sdkOptions } = options;
3961
4121
  const real = baseFetch ?? globalThis.fetch.bind(globalThis);
3962
4122
  const wrappedFetch = (input, init) => {
3963
- const headers = new Headers(init?.headers);
3964
- headers.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
3965
- if (skillName) headers.set("x-pixeldojo-skill", skillName);
3966
- return real(input, { ...init, headers });
4123
+ const headers2 = new Headers(init?.headers);
4124
+ headers2.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
4125
+ if (skillName) headers2.set("x-pixeldojo-skill", skillName);
4126
+ return real(input, { ...init, headers: headers2 });
3967
4127
  };
3968
4128
  return new sdk.PixelDojoClient({
3969
4129
  ...sdkOptions,
@@ -4007,7 +4167,7 @@ function makeRedactedLogger(target = console) {
4007
4167
  }
4008
4168
 
4009
4169
  // src/server.ts
4010
- var PACKAGE_VERSION = "0.24.0" ;
4170
+ var PACKAGE_VERSION = "0.26.0" ;
4011
4171
  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.";
4012
4172
  function buildServer(options) {
4013
4173
  if (!options.apiKey) {