@pixeldojo/mcp 0.12.0 → 0.13.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/dist/cli.js CHANGED
@@ -111,7 +111,8 @@ var init_types = __esm({
111
111
  "src/skills/types.ts"() {
112
112
  }
113
113
  });
114
- async function uploadLocalFile(path, client) {
114
+ async function uploadLocalFile(path, ctx) {
115
+ if (ctx.localFiles !== true) throw new SkillUploadError(HOSTED_LOCAL_PATH_MESSAGE);
115
116
  const ext = extname(path).toLowerCase();
116
117
  const mime = EXT_TO_MIME[ext];
117
118
  if (!mime) {
@@ -150,13 +151,13 @@ async function uploadLocalFile(path, client) {
150
151
  const buffer = await readFile(path);
151
152
  const blob = new Blob([new Uint8Array(buffer)], { type: mime });
152
153
  const filename = basename(path);
153
- const response = await client.uploadImage(blob, filename);
154
+ const response = await ctx.client.uploadImage(blob, filename);
154
155
  return response.url;
155
156
  }
156
- async function uploadLocalFiles(paths, client) {
157
- return Promise.all(paths.map((p) => uploadLocalFile(p, client)));
157
+ async function uploadLocalFiles(paths, ctx) {
158
+ return Promise.all(paths.map((p) => uploadLocalFile(p, ctx)));
158
159
  }
159
- var MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, EXT_TO_MIME, SkillUploadError;
160
+ var MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, EXT_TO_MIME, SkillUploadError, HOSTED_LOCAL_PATH_MESSAGE;
160
161
  var init_upload_helpers = __esm({
161
162
  "src/skills/upload-helpers.ts"() {
162
163
  MAX_IMAGE_BYTES = 50 * 1024 * 1024;
@@ -179,6 +180,7 @@ var init_upload_helpers = __esm({
179
180
  }
180
181
  userMessage;
181
182
  };
183
+ HOSTED_LOCAL_PATH_MESSAGE = "Local file paths are not available on the hosted PixelDojo MCP server (it cannot see your disk). Pass a public http(s) URL instead (image_url / image_urls / video_url / audio_urls / product_image_urls), or install the local server (npx @pixeldojo/mcp init) to use files on your machine.";
182
184
  }
183
185
  });
184
186
 
@@ -450,10 +452,10 @@ async function run(input, ctx) {
450
452
  let resolved = input;
451
453
  let referenceUrls = [];
452
454
  try {
453
- const singleUrl = input.image_url ?? (input.image_path ? await uploadLocalFile(input.image_path, ctx.client) : void 0);
455
+ const singleUrl = input.image_url ?? (input.image_path ? await uploadLocalFile(input.image_path, ctx) : void 0);
454
456
  const manyUrls = [...input.image_urls ?? []];
455
457
  for (const p of input.image_paths ?? []) {
456
- manyUrls.push(await uploadLocalFile(p, ctx.client));
458
+ manyUrls.push(await uploadLocalFile(p, ctx));
457
459
  }
458
460
  referenceUrls = [...singleUrl ? [singleUrl] : [], ...manyUrls].filter(
459
461
  (u, i, all) => all.indexOf(u) === i
@@ -538,19 +540,19 @@ var init_generate = __esm({
538
540
  InputSchema = z.object({
539
541
  prompt: z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
540
542
  model: z.string().optional().describe(
541
- 'Override the model. Any apiId from /api/v1/models (e.g. "flux", "google-nano-banana", "veo-3.1-fast", "kling-video-v3-standard-text"). Omit to use the default.'
543
+ `Model apiId, e.g. "flux", "google-nano-banana", "veo-3.1-fast", "kling-video-v3-standard-text". Full catalog: https://pixeldojo.ai/api/v1/models; one model's fields: https://pixeldojo.ai/llm/{apiId}.txt. Omit for google-nano-banana, which makes still images only; name a video model to get a video.`
542
544
  ),
543
545
  preset: z.string().optional().describe(
544
- 'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean", "magazine-cover-text"). Browse the catalog at https://pixeldojo.ai/presets. Explicit fields override preset values.'
546
+ "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."
545
547
  ),
546
- aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
547
- duration: z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
548
+ aspect_ratio: z.string().optional().describe('e.g. "1:1", "16:9", "9:16". Snapped to the nearest ratio the model supports, and dropped if the model has no aspect_ratio field.'),
549
+ duration: z.number().int().positive().optional().describe("Video length in seconds. Snapped or clamped to what the model allows, and dropped if the model has no duration field. Required for grok-video-extend (the extension length)."),
548
550
  resolution: z.string().optional().describe(
549
551
  'Output resolution when the model supports it (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits. Omit to use the model default.'
550
552
  ),
551
- image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
553
+ image_url: z.string().url().optional().describe("Reference still for image-to-image / image-to-video. A clip URL (.mp4/.mov/.webm) or an audio track URL (.mp3/.wav/.m4a/...) here is detected by extension and routed as that kind of media instead."),
552
554
  video_url: z.string().url().optional().describe(
553
- "Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames. A clip routes to a model that can actually read one."
555
+ "Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames. With no model, a clip runs on seedance-2-reference; a named model with no clip input is swapped for it and the result says so."
554
556
  ),
555
557
  params: ParamsInput,
556
558
  image_path: z.string().min(1).optional().describe(
@@ -577,45 +579,56 @@ var init_generate = __esm({
577
579
  prompt: { type: "string", description: "Prompt describing what to generate." },
578
580
  model: {
579
581
  type: "string",
580
- description: 'Override the model. Any apiId from /api/v1/models (e.g. "flux", "google-nano-banana", "veo-3.1-fast", "kling-video-v3-standard-text"). Omit to use the default.'
582
+ description: `Model apiId, e.g. "flux", "google-nano-banana", "veo-3.1-fast", "kling-video-v3-standard-text". Full catalog: https://pixeldojo.ai/api/v1/models; one model's fields: https://pixeldojo.ai/llm/{apiId}.txt. Omit for google-nano-banana, which makes still images only; name a video model to get a video.`
581
583
  },
582
584
  preset: {
583
585
  type: "string",
584
- description: 'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean"). Explicit fields override preset values.'
586
+ description: "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."
587
+ },
588
+ aspect_ratio: {
589
+ type: "string",
590
+ description: 'e.g. "1:1", "16:9", "9:16". Snapped to the nearest ratio the model supports, and dropped if the model has no aspect_ratio field.'
591
+ },
592
+ duration: {
593
+ type: "integer",
594
+ description: "Video length in seconds. Snapped or clamped to what the model allows, and dropped if the model has no duration field. Required for grok-video-extend (the extension length)."
585
595
  },
586
- aspect_ratio: { type: "string" },
587
- duration: { type: "integer" },
588
596
  resolution: {
589
597
  type: "string",
590
- description: 'Output resolution when supported (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits.'
598
+ description: 'Output resolution when the model supports it (e.g. "480p", "720p", "1080p", "4k"). Snapped to the nearest tier the model offers, and dropped if the model has no resolution field. Lower tiers cost fewer credits.'
599
+ },
600
+ image_url: {
601
+ type: "string",
602
+ format: "uri",
603
+ description: "Reference still for image-to-image / image-to-video. A clip URL (.mp4/.mov/.webm) or an audio track URL (.mp3/.wav/.m4a/...) here is detected by extension and routed as that kind of media instead."
591
604
  },
592
- image_url: { type: "string", format: "uri" },
593
605
  video_url: {
594
606
  type: "string",
595
607
  format: "uri",
596
- description: "Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames."
608
+ description: "Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames. With no model, a clip runs on seedance-2-reference; a named model with no clip input is swapped for it and the result says so."
597
609
  },
598
610
  params: PARAMS_JSON_SCHEMA,
599
611
  image_urls: {
600
612
  type: "array",
601
613
  items: { type: "string", format: "uri" },
602
- description: "Multiple reference image URLs for models that accept a reference set. Single-reference models use the first."
614
+ description: "Multiple reference image URLs for models that accept a reference set (character + wardrobe + location, for example). Single-reference models use the first and ignore the rest."
603
615
  },
604
616
  image_paths: {
605
617
  type: "array",
606
618
  items: { type: "string" },
607
- description: "Multiple absolute local file paths, each uploaded to 24h temp storage and used as a reference."
619
+ description: "Multiple absolute local file paths (JPEG, PNG, WebP, GIF, MP4, WebM, MOV), each uploaded to 24h temp storage and used as a reference, same rules as image_urls."
608
620
  },
609
621
  image_path: {
610
622
  type: "string",
611
- description: "Absolute local file path. The skill uploads it to PixelDojo (24h temp storage) and uses the resulting URL as the reference image. Prefer this over image_url when the user has a local file."
623
+ description: "Absolute local file path to a reference image or clip (JPEG, PNG, WebP, GIF, MP4, WebM, MOV; no audio). The skill uploads it to 24h temp storage and uses the URL as image_url. Ignored when image_url is also set."
612
624
  }
613
625
  },
614
626
  required: ["prompt"]
615
627
  };
616
628
  generateSkill = {
617
629
  name: "pixeldojo_generate",
618
- description: "Generate any image or video from a prompt. Auto-routes to a sensible default model; pass `model` to pick a specific apiId from the catalog. For reference flows: pass `image_url` for a still, `video_url` for a source clip, or `image_path` (absolute local path) and the skill uploads it to 24h temp storage automatically. A clip or an audio reference routes to an engine that can read one. Model-specific settings go in `params` alongside an explicit `model`.",
630
+ description: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a text prompt runs on google-nano-banana, which makes still images only; for a video, name a video model such as veo-3.1-fast. 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.",
631
+ hostedDescription: "Generate an image or video from a prompt with any model in the PixelDojo catalog. With no `model`, a text prompt runs on google-nano-banana, which makes still images only; for a video, name a video model such as veo-3.1-fast. 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.",
619
632
  inputZod: InputSchema,
620
633
  inputJsonSchema: InputJsonSchema,
621
634
  allowedModels: null,
@@ -691,13 +704,13 @@ async function run2(input, ctx) {
691
704
  let resolved = input;
692
705
  try {
693
706
  if (input.image_paths && input.image_paths.length > 0) {
694
- const urls = await uploadLocalFiles(input.image_paths, ctx.client);
707
+ const urls = await uploadLocalFiles(input.image_paths, ctx);
695
708
  resolved = {
696
709
  ...input,
697
710
  image_urls: [...input.image_urls ?? [], ...urls]
698
711
  };
699
712
  } else if (input.image_path && !input.image_url) {
700
- const url = await uploadLocalFile(input.image_path, ctx.client);
713
+ const url = await uploadLocalFile(input.image_path, ctx);
701
714
  resolved = { ...input, image_url: url };
702
715
  }
703
716
  } catch (err) {
@@ -779,32 +792,35 @@ var init_edit = __esm({
779
792
  image_url: {
780
793
  type: "string",
781
794
  format: "uri",
782
- description: "Single reference image. Pass image_urls for multi-image edits."
795
+ description: "Single image to edit. Ignored when image_urls is set; pass image_urls for multi-image edits."
783
796
  },
784
797
  image_urls: {
785
798
  type: "array",
786
799
  items: { type: "string", format: "uri" },
787
800
  minItems: 1,
788
801
  maxItems: 9,
789
- description: "Multiple reference images (1-9)."
802
+ description: "Multiple reference images (1-9), most important first. Models that take fewer keep the first ones and the result says how many were dropped."
790
803
  },
791
804
  image_path: {
792
805
  type: "string",
793
- description: "Absolute local file path. Skill uploads it (24h temp storage) and edits the resulting URL. Prefer over image_url when the user has a local file."
806
+ description: "Absolute local image path (JPEG, PNG, WebP, GIF). Skill uploads it (24h temp storage) and edits the resulting URL. Ignored when image_url or image_paths is set."
794
807
  },
795
808
  image_paths: {
796
809
  type: "array",
797
810
  items: { type: "string" },
798
811
  minItems: 1,
799
812
  maxItems: 9,
800
- description: "Multiple absolute local file paths (1-9). Each uploaded before dispatch."
813
+ description: "Multiple absolute local image paths (1-9). Each is uploaded to 24h temp storage and appended after any image_urls."
801
814
  },
802
815
  model: {
803
816
  type: "string",
804
817
  enum: [...ALLOWED_MODELS],
805
818
  description: "Override edit model. Default: wan-2.7-image-edit."
806
819
  },
807
- aspect_ratio: { type: "string" },
820
+ aspect_ratio: {
821
+ type: "string",
822
+ description: 'Output aspect ratio, e.g. "1:1", "16:9", "match_input_image". Snapped to the nearest ratio the model supports, and dropped if the model has no aspect_ratio field.'
823
+ },
808
824
  params: PARAMS_JSON_SCHEMA
809
825
  },
810
826
  required: ["prompt"]
@@ -812,6 +828,7 @@ var init_edit = __esm({
812
828
  editSkill = {
813
829
  name: "pixeldojo_edit",
814
830
  description: "Edit an existing image with a text instruction. Defaults to wan-2.7-image-edit; pass `model` to pick another edit-capable apiId. Supports up to 9 reference images for multi-image composition. For reference images: pass image_url(s) for hosted URLs or image_path(s) for absolute local file paths (skill uploads to 24h temp storage automatically).",
831
+ hostedDescription: "Edit an existing image with a text instruction. Defaults to wan-2.7-image-edit; pass `model` to pick another edit-capable apiId. Supports up to 9 reference images for multi-image composition. Pass reference images as public http(s) URLs via image_url or image_urls.",
815
832
  inputZod: InputSchema2,
816
833
  inputJsonSchema: InputJsonSchema2,
817
834
  allowedModels: [...ALLOWED_MODELS],
@@ -922,7 +939,7 @@ var init_character = __esm({
922
939
  InputJsonSchema3 = {
923
940
  type: "object",
924
941
  properties: {
925
- prompt: { type: "string" },
942
+ prompt: { type: "string", description: "The shot to render: scene, pose, wardrobe, framing. The references supply the identity." },
926
943
  model: {
927
944
  type: "string",
928
945
  enum: ALLOWED,
@@ -931,16 +948,19 @@ var init_character = __esm({
931
948
  reference_image_url: {
932
949
  type: "string",
933
950
  format: "uri",
934
- description: "Single reference. Prefer reference_image_urls."
951
+ description: "Single still of the character. Ignored when reference_image_urls is set; prefer that, since more angles lock the face better."
935
952
  },
936
953
  reference_image_urls: {
937
954
  type: "array",
938
955
  items: { type: "string", format: "uri" },
939
956
  minItems: 1,
940
957
  maxItems: MAX_REFERENCES,
941
- description: "All references for this shot, most important first. Multiple angles of the same person lock the face far better than one photo."
958
+ description: "All reference stills for this shot (1-9), most important first. Multiple angles of the same person lock the face far better than one photo. Models that take fewer keep the first ones and the result says how many were dropped. Required for wan-2.7-image-edit; clips and audio are refused."
959
+ },
960
+ aspect_ratio: {
961
+ type: "string",
962
+ 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).'
942
963
  },
943
- aspect_ratio: { type: "string" },
944
964
  params: PARAMS_JSON_SCHEMA
945
965
  },
946
966
  required: ["prompt"]
@@ -1037,24 +1057,30 @@ var init_storyboard = __esm({
1037
1057
  properties: {
1038
1058
  brief: {
1039
1059
  type: "string",
1040
- description: "Storyboard brief: scene, shots, mood, action."
1060
+ description: "Scene brief: setting, action, camera, mood. Sent to the video model as its prompt."
1041
1061
  },
1042
1062
  model: {
1043
1063
  type: "string",
1044
1064
  enum: ALLOWED2,
1045
- description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`
1065
+ description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}, or ${VIDEO_REFERENCE_MODEL} when image_url or video_url is given.`
1066
+ },
1067
+ aspect_ratio: {
1068
+ type: "string",
1069
+ description: 'e.g. "16:9", "9:16", "1:1". Snapped to the nearest ratio the model supports, and dropped if the model has no aspect_ratio field.'
1070
+ },
1071
+ duration: {
1072
+ type: "integer",
1073
+ description: "Clip length in seconds. Snapped or clamped to what the model allows."
1046
1074
  },
1047
- aspect_ratio: { type: "string" },
1048
- duration: { type: "integer" },
1049
1075
  image_url: {
1050
1076
  type: "string",
1051
1077
  format: "uri",
1052
- description: "Reference still to build the shot from."
1078
+ description: "Reference still to build the shot from (character, product, location). A clip or audio URL here is detected by extension and routed as that kind of media."
1053
1079
  },
1054
1080
  video_url: {
1055
1081
  type: "string",
1056
1082
  format: "uri",
1057
- description: "Source clip to work from. Use this for videos; image_url is for stills."
1083
+ description: "Source clip to work from. Use this for videos; image_url is for stills. A named model with no clip input is swapped for seedance-2-reference and the result says so."
1058
1084
  },
1059
1085
  params: PARAMS_JSON_SCHEMA
1060
1086
  },
@@ -1062,7 +1088,7 @@ var init_storyboard = __esm({
1062
1088
  };
1063
1089
  storyboardSkill = {
1064
1090
  name: "pixeldojo_storyboard",
1065
- description: "Generate a video storyboard preview from a single brief. Pass image_url for a reference still or video_url for a source clip. v1: single-shot preview; multi-shot orchestration coming.",
1091
+ description: "Generate one short video clip (a single shot, not a multi-panel storyboard) from a scene brief. A text-only brief runs on veo-3.1-fast unless `model` names another allowed video model. With image_url (reference still) or video_url (source clip) and no `model`, it runs on seedance-2-reference, with the same reference routing as pixeldojo_generate. For a multi-shot film with dialogue and a soundtrack, use pixeldojo_film.",
1066
1092
  inputZod: InputSchema4,
1067
1093
  inputJsonSchema: InputJsonSchema4,
1068
1094
  allowedModels: [...ALLOWED2],
@@ -1152,27 +1178,49 @@ var init_upscale = __esm({
1152
1178
  scale_factor: z.number().min(1).max(16).optional().describe(
1153
1179
  "Image models only. Magnific snaps to the nearest of 2/4/8/16 (default 2); creative-upscale accepts 1-10 (default 2); face-enhance accepts 1-8 (default 4)."
1154
1180
  ),
1155
- creativity: z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0."),
1181
+ creativity: z.number().int().min(-10).max(10).optional().describe("Magnific only: integer -10 (preserve) \u2192 10 (creative). Defaults to 0."),
1156
1182
  video_duration: z.number().positive().optional().describe("video-upscaler only, REQUIRED there: source clip length in seconds (its credit meter)."),
1157
1183
  target_resolution: z.enum(VIDEO_RESOLUTIONS).optional().describe("video-upscaler only: target output resolution. Defaults to 1080p.")
1158
1184
  });
1159
1185
  InputJsonSchema5 = {
1160
1186
  type: "object",
1161
1187
  properties: {
1162
- image_url: { type: "string", format: "uri" },
1188
+ image_url: {
1189
+ type: "string",
1190
+ format: "uri",
1191
+ description: "Source image to upscale (image models). A clip URL here (.mp4/.mov/.webm) is treated as video_url."
1192
+ },
1163
1193
  video_url: {
1164
1194
  type: "string",
1165
1195
  format: "uri",
1166
- description: "Source video clip \u2014 routes to video-upscaler."
1196
+ description: "Source video clip (MP4, max 500 MB). Routes to video-upscaler when no model is named; needs video_duration."
1197
+ },
1198
+ model: {
1199
+ type: "string",
1200
+ enum: ALLOWED3,
1201
+ description: `Defaults to ${DEFAULT_MODEL5}, or ${VIDEO_MODEL} when the source is a clip. face-enhance is for portraits.`
1202
+ },
1203
+ scale_factor: {
1204
+ type: "number",
1205
+ minimum: 1,
1206
+ maximum: 16,
1207
+ description: "Image models only. magnific-upscaler snaps to the nearest of 2/4/8/16 (default 2); creative-upscale clamps to 1-10 (default 2); face-enhance clamps to 1-8 (default 4)."
1208
+ },
1209
+ creativity: {
1210
+ type: "integer",
1211
+ minimum: -10,
1212
+ maximum: 10,
1213
+ description: "magnific-upscaler only: sent unchanged as its creativity setting, an integer from -10 to 10 (default 0; lower preserves the source, higher invents more detail)."
1167
1214
  },
1168
- model: { type: "string", enum: ALLOWED3 },
1169
- scale_factor: { type: "number", minimum: 1, maximum: 16 },
1170
- creativity: { type: "number", minimum: 0, maximum: 1 },
1171
1215
  video_duration: {
1172
1216
  type: "number",
1173
- description: "video-upscaler only, required there: source clip length in seconds."
1217
+ description: "video-upscaler only, required there: source clip length in seconds. It sets the cost (1 credit per second)."
1174
1218
  },
1175
- target_resolution: { type: "string", enum: VIDEO_RESOLUTIONS }
1219
+ target_resolution: {
1220
+ type: "string",
1221
+ enum: VIDEO_RESOLUTIONS,
1222
+ description: "video-upscaler only: output resolution. Defaults to 1080p."
1223
+ }
1176
1224
  }
1177
1225
  };
1178
1226
  upscaleSkill = {
@@ -1261,7 +1309,7 @@ var init_status = __esm({
1261
1309
  properties: {
1262
1310
  jobId: {
1263
1311
  type: "string",
1264
- description: "Job id returned from a prior pixeldojo_* skill handoff."
1312
+ description: "The jobId a generation tool returned instead of an asset (a job_... id)."
1265
1313
  }
1266
1314
  },
1267
1315
  required: ["jobId"]
@@ -1270,7 +1318,7 @@ var init_status = __esm({
1270
1318
  ABANDON_HINT_MS = 12 * 60 * 1e3;
1271
1319
  statusSkill = {
1272
1320
  name: "pixeldojo_status",
1273
- description: "Check the status of a long-running job after a 30s sync handoff. Returns assets when ready, failure reason if failed, or current state if still running.",
1321
+ description: 'Check once, without waiting, on a jobId that a generation tool returned instead of an asset after its 30s wait. Free. Returns asset URLs when complete, the failure reason if it failed, or the current state and job age if still running. Campaign ids go to pixeldojo_campaign_status; films report progress through pixeldojo_film with action "status".',
1274
1322
  inputZod: InputSchema6,
1275
1323
  inputJsonSchema: InputJsonSchema6,
1276
1324
  // status is special — it doesn't dispatch to a model
@@ -1315,7 +1363,7 @@ var init_from_url = __esm({
1315
1363
  };
1316
1364
  fromUrlSkill = {
1317
1365
  name: "pixeldojo_from_url",
1318
- description: "Extract product info (name, description, images) from any product page URL. Use this as the first step before pixeldojo_campaign / :ad / :lookbook so the agent has structured product data instead of a free-text description.",
1366
+ description: "Extract a product profile from a product page URL: name, description, image URLs (the first three are listed), and the extraction method used. Works on Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata. Free. pixeldojo_campaign and pixeldojo_ad accept a product URL and run this extraction themselves; call this directly to check the product data first, or to reuse its images in other tools.",
1319
1367
  inputZod: InputSchema7,
1320
1368
  inputJsonSchema: InputJsonSchema7,
1321
1369
  // No model dispatch — this skill calls a server-side scrape, not a generation.
@@ -1520,20 +1568,31 @@ var init_film = __esm({
1520
1568
  film_id: z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status."),
1521
1569
  action: z.enum(["plan", "produce", "status"]).default("plan").describe("'plan' creates + storyboards (free). 'produce' starts charged generation. 'status' advances production and reports progress; poll it every ~30s until it returns the finished film."),
1522
1570
  soundtrack: z.enum(["scene-synced", "flat", "none"]).default("scene-synced").describe("scene-synced composes one musical section per scene, pinned to the scene lengths (default). flat is a single cohesive bed. none skips music."),
1523
- music_job_id: z.string().max(80).optional().describe("Internal: pass back the music job id from a previous status reply so the composing score is attached instead of re-charged.")
1571
+ music_job_id: z.string().max(80).optional().describe("The music_job_id from the previous status reply while the score is composing, so that score is attached. A status call without it, once all clips are ready and no score is attached, starts and charges for a new score.")
1524
1572
  });
1525
1573
  InputJsonSchema8 = {
1526
1574
  type: "object",
1527
1575
  properties: {
1528
- idea: { type: "string", description: "One-to-three sentence film idea. The studio writes the screenplay (free)." },
1529
- script: { type: "string", description: "A full screenplay to plan instead of an idea." },
1530
- look: { type: "string", enum: [...LOOKS], description: "Visual style. Default live-action." },
1531
- aspect: { type: "string", enum: ["16:9", "9:16"], description: "Default 16:9." },
1532
- length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Target length (~6/12/24/60 shots). Default 60s." },
1533
- film_id: { type: "string", description: "Existing film id for produce/status." },
1534
- action: { type: "string", enum: ["plan", "produce", "status"], description: "plan (free) | produce (charged) | status (poll every ~30s)." },
1535
- soundtrack: { type: "string", enum: ["scene-synced", "flat", "none"], description: "Default scene-synced." },
1536
- music_job_id: { type: "string", description: "Pass back from a previous status reply while the score is composing." }
1576
+ idea: { type: "string", description: "Plan step: one-to-three sentence film idea. The studio writes the screenplay (free). Provide idea or script." },
1577
+ script: { type: "string", description: "Plan step: a full screenplay (sluglines, action lines, dialogue) to plan instead of an idea." },
1578
+ look: { type: "string", enum: [...LOOKS], description: "Plan step: visual style carried through frames, clips and soundtrack. Default live-action." },
1579
+ aspect: { type: "string", enum: ["16:9", "9:16"], description: "Plan step: frame aspect ratio. Default 16:9." },
1580
+ length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Plan step: target length, which sets the shot count (~6/12/24/60 shots). Default 60s." },
1581
+ film_id: { type: "string", description: 'The film_id returned by the plan step. Required for produce and status; with it, action "plan" is treated as "status".' },
1582
+ action: {
1583
+ type: "string",
1584
+ enum: ["plan", "produce", "status"],
1585
+ description: "plan (default, free): writes the script and plans shots, returns film_id and a cost estimate. produce (charged): starts rendering frames. status: advances production one step and reports progress; call it every ~30s until it returns the finished film URL."
1586
+ },
1587
+ soundtrack: {
1588
+ type: "string",
1589
+ enum: ["scene-synced", "flat", "none"],
1590
+ description: "Read on status calls once all clips are ready. scene-synced (default): one musical section per scene. flat: one cohesive bed. none: export without music."
1591
+ },
1592
+ music_job_id: {
1593
+ type: "string",
1594
+ description: "The music_job_id from the previous status reply while the score is composing. A status call without it, once all clips are ready and no score is attached, starts and charges for a new score."
1595
+ }
1537
1596
  }
1538
1597
  };
1539
1598
  shots = (f) => f.scenes.flatMap((s) => s.shots);
@@ -1570,11 +1629,11 @@ var init_film = __esm({
1570
1629
  async function run9(input, ctx) {
1571
1630
  const productImages = [
1572
1631
  ...input.product_image_urls ?? [],
1573
- ...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx.client)))
1632
+ ...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx)))
1574
1633
  ];
1575
1634
  const characterImages = [
1576
1635
  ...input.character_image_urls ?? [],
1577
- ...await Promise.all((input.character_image_paths ?? []).map((p) => uploadLocalFile(p, ctx.client)))
1636
+ ...await Promise.all((input.character_image_paths ?? []).map((p) => uploadLocalFile(p, ctx)))
1578
1637
  ];
1579
1638
  const body = {
1580
1639
  style: input.style,
@@ -1652,16 +1711,24 @@ var init_ad = __esm({
1652
1711
  product_url: {
1653
1712
  type: "string",
1654
1713
  format: "uri",
1655
- description: "Product page URL. Name, description, and images are extracted automatically."
1714
+ description: "Product page URL. Name, description, and images are extracted automatically. Pass this, or product_name plus product images; when set, the product_* fields are ignored."
1715
+ },
1716
+ product_name: {
1717
+ type: "string",
1718
+ description: "Product name. Required with product_image_urls / product_image_paths when there is no product_url."
1719
+ },
1720
+ product_description: { type: "string", description: "Optional product description (no product_url case)." },
1721
+ product_image_urls: {
1722
+ type: "array",
1723
+ items: { type: "string", format: "uri" },
1724
+ maxItems: 5,
1725
+ description: "Up to 5 hosted product photos (no product_url case)."
1656
1726
  },
1657
- product_name: { type: "string" },
1658
- product_description: { type: "string" },
1659
- product_image_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 5 },
1660
1727
  product_image_paths: {
1661
1728
  type: "array",
1662
1729
  items: { type: "string" },
1663
1730
  maxItems: 5,
1664
- description: "Absolute local file paths; uploaded to 24h temp storage automatically."
1731
+ description: "Up to 5 absolute local product image paths; uploaded to 24h temp storage automatically."
1665
1732
  },
1666
1733
  character_image_urls: {
1667
1734
  type: "array",
@@ -1669,12 +1736,21 @@ var init_ad = __esm({
1669
1736
  maxItems: 4,
1670
1737
  description: "Presenter reference images for a consistent on-camera character."
1671
1738
  },
1672
- character_image_paths: { type: "array", items: { type: "string" }, maxItems: 4 },
1739
+ character_image_paths: {
1740
+ type: "array",
1741
+ items: { type: "string" },
1742
+ maxItems: 4,
1743
+ description: "Absolute local presenter image paths; uploaded to 24h temp storage automatically."
1744
+ },
1673
1745
  location_description: {
1674
1746
  type: "string",
1675
1747
  description: 'Where the ad takes place ("a sunlit Parisian cafe"). Text is enough.'
1676
1748
  },
1677
- location_image_url: { type: "string", format: "uri" },
1749
+ location_image_url: {
1750
+ type: "string",
1751
+ format: "uri",
1752
+ description: "Optional reference photo of the location."
1753
+ },
1678
1754
  hook: {
1679
1755
  type: "string",
1680
1756
  description: "The opening visual beat (first 1-2 seconds). Omit to use the style default."
@@ -1683,21 +1759,34 @@ var init_ad = __esm({
1683
1759
  type: "string",
1684
1760
  description: "The exact spoken line, voiced word for word. Omit and the presenter improvises."
1685
1761
  },
1686
- quality: { type: "string", enum: ["mini", "fast", "high"] },
1762
+ quality: {
1763
+ type: "string",
1764
+ enum: ["mini", "fast", "high"],
1765
+ description: "Render tier. Omitted or unsupported by the ad engine, the engine's default tier is used."
1766
+ },
1687
1767
  resolution: {
1688
1768
  type: "string",
1689
1769
  enum: ["480p", "720p", "1080p", "4k"],
1690
- description: '1080p/4k require quality "high".'
1770
+ description: '1080p/4k require quality "high"; without it, or when the engine lacks the value, the engine default resolution is used.'
1771
+ },
1772
+ aspect_ratio: {
1773
+ type: "string",
1774
+ enum: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"],
1775
+ description: "Defaults to the style's own aspect ratio; a value the engine does not support falls back to it."
1691
1776
  },
1692
- aspect_ratio: { type: "string", enum: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"] },
1693
- duration: { type: "integer", minimum: 4, maximum: 15 }
1777
+ duration: {
1778
+ type: "integer",
1779
+ minimum: 4,
1780
+ maximum: 15,
1781
+ description: "Ad length in seconds. Defaults to the style's length; clamped to what the engine allows."
1782
+ }
1694
1783
  }
1695
1784
  // "product_url OR product_name+images" is enforced by the server and the
1696
1785
  // Zod refine; no root-level oneOf (Grok rejects non-object root unions).
1697
1786
  };
1698
1787
  adSkill = {
1699
1788
  name: "pixeldojo_ad",
1700
- description: "Marketing Studio: turn any product into a video ad. Pass a product URL (auto-extracted) or product images, pick an ad style (UGC testimonial, unboxing, try-on, showcase, TV spot, and more via GET /api/v1/marketing/presets), optionally add presenter reference images, a location, a hook, and the exact spoken script. Renders with voiceover; returns the video or a statusUrl handoff to poll via pixeldojo_status. Ideal for automated generate-and-post workflows.",
1789
+ description: "Marketing Studio: turn a product into a rendered video ad with voiceover. Requires `product_url` (name, description and images are extracted), or `product_name` plus product_image_urls / product_image_paths. Pick a `style` slug (default ugc-testimonial; full catalog at GET https://pixeldojo.ai/api/v1/marketing/presets), and optionally add presenter reference images, a location, a hook, and the exact spoken script. Returns the video URL, or a jobId to check with pixeldojo_status.",
1701
1790
  inputZod: InputSchema9,
1702
1791
  inputJsonSchema: InputJsonSchema9,
1703
1792
  // Composition skill — the server routes to the ad engine internally.
@@ -1759,27 +1848,47 @@ var init_campaign = __esm({
1759
1848
  productUrl: {
1760
1849
  type: "string",
1761
1850
  format: "uri",
1762
- description: "Product page URL. We extract name + description + images automatically."
1851
+ description: "Product page URL; name, description and images are extracted automatically. Pass this or `product` (one is required); when both are set, productUrl wins."
1763
1852
  },
1764
1853
  product: {
1765
1854
  type: "object",
1855
+ description: "Manual product profile, used when productUrl is omitted.",
1766
1856
  properties: {
1767
- name: { type: "string" },
1768
- description: { type: "string" },
1769
- images: { type: "array", items: { type: "string", format: "uri" } }
1857
+ name: { type: "string", description: "Product name (required)." },
1858
+ description: { type: "string", description: "Optional product description." },
1859
+ images: {
1860
+ type: "array",
1861
+ items: { type: "string", format: "uri" },
1862
+ maxItems: 5,
1863
+ description: "Up to 5 hosted product photo URLs."
1864
+ }
1770
1865
  },
1771
1866
  required: ["name"]
1772
1867
  },
1773
1868
  tone: {
1774
1869
  type: "string",
1775
- description: "Creative direction threaded through every prompt."
1870
+ description: 'Optional creative direction ("premium minimalist", "playful Y2K"), threaded through every prompt.'
1776
1871
  },
1777
- variants: { type: "integer", minimum: 1, maximum: 8 },
1778
- includeVideo: { type: "boolean" },
1779
- videoAspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1"] },
1780
- videoMode: { type: "string", enum: ["lifestyle", "cgi", "ugc", "skip"] }
1781
- },
1782
- oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
1872
+ variants: {
1873
+ type: "integer",
1874
+ minimum: 1,
1875
+ maximum: 8,
1876
+ description: "Lifestyle shots in addition to the hero (default 4)."
1877
+ },
1878
+ includeVideo: { type: "boolean", description: "Also render a launch video (default true)." },
1879
+ videoAspectRatio: {
1880
+ type: "string",
1881
+ enum: ["9:16", "16:9", "1:1"],
1882
+ description: "Launch video aspect ratio (default 9:16)."
1883
+ },
1884
+ videoMode: {
1885
+ type: "string",
1886
+ enum: ["lifestyle", "cgi", "ugc", "skip"],
1887
+ description: "Launch video styling: lifestyle (default), cgi, ugc, or skip for no video."
1888
+ }
1889
+ }
1890
+ // productUrl-or-product is enforced by the Zod refine and the server; no
1891
+ // root-level oneOf (some clients reject root unions — see ad.ts).
1783
1892
  };
1784
1893
  campaignSkill = {
1785
1894
  name: "pixeldojo_campaign",
@@ -1854,7 +1963,7 @@ var init_campaign_status = __esm({
1854
1963
  async function run12(input, ctx) {
1855
1964
  let url;
1856
1965
  try {
1857
- url = await uploadLocalFile(input.path, ctx.client);
1966
+ url = await uploadLocalFile(input.path, ctx);
1858
1967
  } catch (err) {
1859
1968
  if (err instanceof SkillUploadError) {
1860
1969
  return { kind: "text", text: `pixeldojo_upload: ${err.userMessage}` };
@@ -1864,7 +1973,7 @@ async function run12(input, ctx) {
1864
1973
  return {
1865
1974
  kind: "text",
1866
1975
  text: `Uploaded ${basename(input.path)} \u2192 ${url}
1867
- (temp storage; auto-deletes in ~24h \u2014 pass this URL as image_url to :generate or :edit)`
1976
+ (temp storage; auto-deletes in ~24h. Pass this URL as image_url, or video_url for a clip, to any pixeldojo tool that takes one.)`
1868
1977
  };
1869
1978
  }
1870
1979
  var InputSchema12, InputJsonSchema12, uploadSkill;
@@ -1888,7 +1997,7 @@ var init_upload = __esm({
1888
1997
  };
1889
1998
  uploadSkill = {
1890
1999
  name: "pixeldojo_upload",
1891
- description: "Upload a local image or video file and get back a public URL you can pass to :generate or :edit as image_url. Use when the user has a reference image on disk (drag-dropped into Claude Code, screenshot, etc.) and you need a URL. Storage auto-expires after 24 hours.",
2000
+ description: "Upload a local image or video (JPEG, PNG, WebP, GIF up to 50MB; MP4, WebM, MOV up to 200MB) and get back a public URL that expires after 24 hours. Free. Audio files are not accepted. Pass the URL as image_url (stills) or video_url (clips) to tools that take hosted URLs only, such as pixeldojo_character, pixeldojo_storyboard and pixeldojo_upscale. pixeldojo_generate, pixeldojo_edit and pixeldojo_ad take local paths directly and need no separate upload.",
1892
2001
  inputZod: InputSchema12,
1893
2002
  inputJsonSchema: InputJsonSchema12,
1894
2003
  allowedModels: [],
@@ -1906,18 +2015,7 @@ async function run13(input, ctx) {
1906
2015
  };
1907
2016
  }
1908
2017
  const apiId = requested;
1909
- let audioUrls = input.audio_urls ?? [];
1910
- if (input.audio_paths?.length) {
1911
- try {
1912
- const uploaded = await uploadLocalFiles(input.audio_paths, ctx.client);
1913
- audioUrls = [...audioUrls, ...uploaded].slice(0, 3);
1914
- } catch (err) {
1915
- if (err instanceof SkillUploadError) {
1916
- return { kind: "text", text: `pixeldojo_audio (upload): ${err.userMessage}` };
1917
- }
1918
- throw err;
1919
- }
1920
- }
2018
+ const audioUrls = input.audio_urls ?? [];
1921
2019
  const body = { prompt: input.prompt };
1922
2020
  if (apiId === "seed-audio") {
1923
2021
  if (input.voice) body.voice = input.voice;
@@ -1950,37 +2048,57 @@ var ALLOWED4, DEFAULT_MODEL6, InputSchema13, InputJsonSchema13, audioSkill;
1950
2048
  var init_audio = __esm({
1951
2049
  "src/skills/audio.ts"() {
1952
2050
  init_poll();
1953
- init_upload_helpers();
1954
2051
  init_types();
1955
2052
  ALLOWED4 = ["seed-audio", "text-to-speech"];
1956
2053
  DEFAULT_MODEL6 = "seed-audio";
1957
2054
  InputSchema13 = z.object({
1958
2055
  prompt: z.string().min(1).describe(
1959
- "A script for speech, or a scene description for cinematic audio (dialogue + effects + music). Reference uploaded clips as @Audio1..@Audio3."
2056
+ "seed-audio: a script to speak, or a scene description (dialogue, sound effects, music), up to 2048 characters; refer to audio_urls clips in order as @Audio1..@Audio3. text-to-speech: the exact text to speak, up to 10,000 characters."
1960
2057
  ),
1961
- model: z.string().optional().describe(`One of: ${ALLOWED4.join(", ")}. Defaults to ${DEFAULT_MODEL6}.`),
1962
- voice: z.string().optional().describe("Optional voice preset name or cloned-voice id (seed-audio) / voice_id (text-to-speech)."),
1963
- audio_urls: z.array(z.string().url()).max(3).optional().describe("seed-audio only: up to 3 reference audio URLs (30s / 10MB max each)."),
1964
- audio_paths: z.array(z.string()).max(3).optional().describe("seed-audio only: absolute local paths to reference clips; uploaded automatically (24h temp storage)."),
1965
- output_format: z.string().optional().describe("seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3, wav, flac, pcm."),
1966
- speed: z.number().min(0.5).max(2).optional().describe("Speech speed multiplier (0.5\u20132).")
2058
+ model: z.string().optional().describe(`${DEFAULT_MODEL6} (default): speech, multi-voice dialogue, effects and music in one clip, and voice cloning from audio_urls. text-to-speech: plain narration with preset voices.`),
2059
+ voice: z.string().optional().describe("text-to-speech only: voice preset (e.g. Wise_Woman, Calm_Woman, Friendly_Person; default Wise_Woman). seed-audio ignores it: describe the voice in the prompt, or clone one with audio_urls."),
2060
+ audio_urls: z.array(z.string().url()).max(3).optional().describe("seed-audio only: up to 3 hosted reference clips (wav, mp3, pcm or ogg_opus, 30s / 10MB max each) for voice cloning, referenced in the prompt as @Audio1..@Audio3. Local audio files cannot be uploaded; pass URLs. Ignored by text-to-speech."),
2061
+ output_format: z.string().optional().describe("Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."),
2062
+ speed: z.number().min(0.5).max(2).optional().describe("Speech speed multiplier, 0.5-2 (default 1). Both models.")
1967
2063
  });
1968
2064
  InputJsonSchema13 = {
1969
2065
  type: "object",
1970
2066
  properties: {
1971
- prompt: { type: "string" },
1972
- model: { type: "string", enum: ALLOWED4 },
1973
- voice: { type: "string" },
1974
- audio_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 3 },
1975
- audio_paths: { type: "array", items: { type: "string" }, maxItems: 3 },
1976
- output_format: { type: "string" },
1977
- speed: { type: "number", minimum: 0.5, maximum: 2 }
2067
+ prompt: {
2068
+ type: "string",
2069
+ description: "seed-audio: a script to speak, or a scene description (dialogue, sound effects, music), up to 2048 characters; refer to audio_urls clips in order as @Audio1..@Audio3. text-to-speech: the exact text to speak, up to 10,000 characters."
2070
+ },
2071
+ model: {
2072
+ type: "string",
2073
+ enum: ALLOWED4,
2074
+ description: `${DEFAULT_MODEL6} (default): speech, multi-voice dialogue, effects and music in one clip, and voice cloning from audio_urls. text-to-speech: plain narration with preset voices.`
2075
+ },
2076
+ voice: {
2077
+ type: "string",
2078
+ description: "text-to-speech only: voice preset (e.g. Wise_Woman, Calm_Woman, Friendly_Person; default Wise_Woman). seed-audio ignores it: describe the voice in the prompt, or clone one with audio_urls."
2079
+ },
2080
+ audio_urls: {
2081
+ type: "array",
2082
+ items: { type: "string", format: "uri" },
2083
+ maxItems: 3,
2084
+ description: "seed-audio only: up to 3 hosted reference clips (wav, mp3, pcm or ogg_opus, 30s / 10MB max each) for voice cloning, referenced in the prompt as @Audio1..@Audio3. Local audio files cannot be uploaded; pass URLs. Ignored by text-to-speech."
2085
+ },
2086
+ output_format: {
2087
+ type: "string",
2088
+ description: "Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."
2089
+ },
2090
+ speed: {
2091
+ type: "number",
2092
+ minimum: 0.5,
2093
+ maximum: 2,
2094
+ description: "Speech speed multiplier, 0.5-2 (default 1). Both models."
2095
+ }
1978
2096
  },
1979
2097
  required: ["prompt"]
1980
2098
  };
1981
2099
  audioSkill = {
1982
2100
  name: "pixeldojo_audio",
1983
- description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain TTS available).",
2101
+ description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain text-to-speech available). Voice cloning takes hosted reference clip URLs in audio_urls; local audio files are not accepted. Returns an audio URL, or a jobId for pixeldojo_status.",
1984
2102
  inputZod: InputSchema13,
1985
2103
  inputJsonSchema: InputJsonSchema13,
1986
2104
  allowedModels: [...ALLOWED4],
@@ -2017,7 +2135,7 @@ async function run14(input, ctx) {
2017
2135
  text: `${res.items.length} library item(s):
2018
2136
  ${lines.join("\n")}
2019
2137
 
2020
- Pass any URL back into :generate, :edit, :character, or :upscale as a reference input.`
2138
+ Pass any URL back into pixeldojo_generate, pixeldojo_edit, pixeldojo_character, or pixeldojo_upscale as a reference input.`
2021
2139
  };
2022
2140
  }
2023
2141
  var InputSchema14, InputJsonSchema14, librarySkill;
@@ -2032,10 +2150,17 @@ var init_library = __esm({
2032
2150
  InputJsonSchema14 = {
2033
2151
  type: "object",
2034
2152
  properties: {
2035
- q: { type: "string" },
2036
- model: { type: "string" },
2037
- modality: { type: "string", enum: ["image", "video", "audio"] },
2038
- limit: { type: "integer", minimum: 1, maximum: 50 }
2153
+ q: {
2154
+ type: "string",
2155
+ description: "Substring match against the prompt that produced the asset. Omit to list the newest items."
2156
+ },
2157
+ model: { type: "string", description: "Only assets from this model apiId." },
2158
+ modality: {
2159
+ type: "string",
2160
+ enum: ["image", "video", "audio"],
2161
+ description: "Only this output type."
2162
+ },
2163
+ limit: { type: "integer", minimum: 1, maximum: 50, description: "Max results (default 10)." }
2039
2164
  },
2040
2165
  required: []
2041
2166
  };
@@ -2177,7 +2302,7 @@ async function runWorkflow(input, ctx) {
2177
2302
  ${outputs.join("\n")}`
2178
2303
  };
2179
2304
  }
2180
- var WORKFLOW_STEP_EXTRA_BUDGET_MS, StepSchema, SaveInputSchema, RunInputSchema, ListInputSchema, stepJson, saveWorkflowSkill, runWorkflowSkill, listWorkflowsSkill;
2305
+ var WORKFLOW_STEP_EXTRA_BUDGET_MS, StepSchema, SaveInputSchema, RunInputSchema, ListInputSchema, STEP_SKILLS, stepJson, saveWorkflowSkill, runWorkflowSkill, listWorkflowsSkill;
2181
2306
  var init_workflows = __esm({
2182
2307
  "src/skills/workflows.ts"() {
2183
2308
  init_poll();
@@ -2201,11 +2326,28 @@ var init_workflows = __esm({
2201
2326
  inputs: z.record(z.string(), z.string()).optional().describe("Values for {{input.<key>}} placeholders in the workflow steps.")
2202
2327
  });
2203
2328
  ListInputSchema = z.object({});
2329
+ STEP_SKILLS = [
2330
+ "pixeldojo_generate",
2331
+ "pixeldojo_edit",
2332
+ "pixeldojo_character",
2333
+ "pixeldojo_storyboard",
2334
+ "pixeldojo_upscale",
2335
+ "pixeldojo_audio",
2336
+ "pixeldojo_upload",
2337
+ "pixeldojo_from_url"
2338
+ ];
2204
2339
  stepJson = {
2205
2340
  type: "object",
2206
2341
  properties: {
2207
- skill: { type: "string" },
2208
- args: { type: "object" }
2342
+ skill: {
2343
+ type: "string",
2344
+ enum: STEP_SKILLS,
2345
+ description: "Tool this step runs. Steps run in order when the workflow runs, and each is charged at that tool's own rate."
2346
+ },
2347
+ args: {
2348
+ type: "object",
2349
+ description: "That tool's own arguments, exactly as you would pass them calling it directly. Any string value, at any depth, may contain {{input.<key>}} (filled from pixeldojo_run_workflow inputs) or {{prev}} (the first asset URL the previous step produced)."
2350
+ }
2209
2351
  },
2210
2352
  required: ["skill"]
2211
2353
  };
@@ -2216,9 +2358,19 @@ var init_workflows = __esm({
2216
2358
  inputJsonSchema: {
2217
2359
  type: "object",
2218
2360
  properties: {
2219
- name: { type: "string", pattern: "^[a-z0-9][a-z0-9-]{1,40}$" },
2220
- description: { type: "string" },
2221
- steps: { type: "array", items: stepJson, minItems: 1, maxItems: 8 }
2361
+ name: {
2362
+ type: "string",
2363
+ pattern: "^[a-z0-9][a-z0-9-]{1,40}$",
2364
+ description: 'Kebab-case handle, 2-41 chars (e.g. "product-launch-pack"). Saving to an existing name overwrites it.'
2365
+ },
2366
+ description: { type: "string", description: "Optional note on what the workflow does (max 500 chars)." },
2367
+ steps: {
2368
+ type: "array",
2369
+ items: stepJson,
2370
+ minItems: 1,
2371
+ maxItems: 8,
2372
+ description: "The chain, 1-8 steps, run in order. Saving charges nothing; running charges each step."
2373
+ }
2222
2374
  },
2223
2375
  required: ["name", "steps"]
2224
2376
  },
@@ -2233,8 +2385,12 @@ var init_workflows = __esm({
2233
2385
  inputJsonSchema: {
2234
2386
  type: "object",
2235
2387
  properties: {
2236
- name: { type: "string" },
2237
- inputs: { type: "object", additionalProperties: { type: "string" } }
2388
+ name: { type: "string", description: "Saved workflow name (see pixeldojo_list_workflows)." },
2389
+ inputs: {
2390
+ type: "object",
2391
+ additionalProperties: { type: "string" },
2392
+ description: `String values for the workflow's {{input.<key>}} placeholders, e.g. {"product": "red sneaker"}. A step whose placeholder has no value stops the run before that step.`
2393
+ }
2238
2394
  },
2239
2395
  required: ["name"]
2240
2396
  },
@@ -2258,6 +2414,7 @@ var init_workflows = __esm({
2258
2414
  var skills_exports = {};
2259
2415
  __export(skills_exports, {
2260
2416
  ALL_SKILLS: () => ALL_SKILLS,
2417
+ HOSTED_SKILLS: () => HOSTED_SKILLS,
2261
2418
  adSkill: () => adSkill,
2262
2419
  audioSkill: () => audioSkill,
2263
2420
  campaignSkill: () => campaignSkill,
@@ -2265,6 +2422,7 @@ __export(skills_exports, {
2265
2422
  characterSkill: () => characterSkill,
2266
2423
  editSkill: () => editSkill,
2267
2424
  filmSkill: () => filmSkill,
2425
+ findHostedSkill: () => findHostedSkill,
2268
2426
  findSkill: () => findSkill,
2269
2427
  fromUrlSkill: () => fromUrlSkill,
2270
2428
  generateSkill: () => generateSkill,
@@ -2277,11 +2435,15 @@ __export(skills_exports, {
2277
2435
  uploadSkill: () => uploadSkill,
2278
2436
  upscaleSkill: () => upscaleSkill
2279
2437
  });
2438
+ function findHostedSkill(name) {
2439
+ const wanted = name.replace(/^pixeldojo:/, "pixeldojo_");
2440
+ return HOSTED_SKILLS.find((s) => s.name === wanted);
2441
+ }
2280
2442
  function findSkill(name) {
2281
2443
  const wanted = name.replace(/^pixeldojo:/, "pixeldojo_");
2282
2444
  return ALL_SKILLS.find((s) => s.name === wanted);
2283
2445
  }
2284
- var ALL_SKILLS;
2446
+ var ALL_SKILLS, isLocalPathField, HOSTED_SKILLS;
2285
2447
  var init_skills = __esm({
2286
2448
  "src/skills/index.ts"() {
2287
2449
  init_generate();
@@ -2318,6 +2480,20 @@ var init_skills = __esm({
2318
2480
  campaignStatusSkill,
2319
2481
  filmSkill
2320
2482
  ];
2483
+ isLocalPathField = (key) => /_paths?$/.test(key);
2484
+ HOSTED_SKILLS = ALL_SKILLS.filter(
2485
+ (s) => s.name !== uploadSkill.name
2486
+ ).map((s) => {
2487
+ const props = s.inputJsonSchema.properties ?? {};
2488
+ return {
2489
+ ...s,
2490
+ description: s.hostedDescription ?? s.description,
2491
+ inputJsonSchema: {
2492
+ ...s.inputJsonSchema,
2493
+ properties: Object.fromEntries(Object.entries(props).filter(([k]) => !isLocalPathField(k)))
2494
+ }
2495
+ };
2496
+ });
2321
2497
  }
2322
2498
  });
2323
2499
  function makeAttributedClient(options) {
@@ -2371,7 +2547,8 @@ function makeRedactedLogger(target = console) {
2371
2547
  }
2372
2548
 
2373
2549
  // src/server.ts
2374
- var PACKAGE_VERSION = "0.12.0" ;
2550
+ var PACKAGE_VERSION = "0.13.1" ;
2551
+ var SERVER_INSTRUCTIONS = "PixelDojo generates images, video, audio and short films across 100+ models, billed in credits from the connected PixelDojo 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, so download anything the user wants to keep. Finished films are the exception and stay in the account library.";
2375
2552
  function buildServer(options) {
2376
2553
  if (!options.apiKey) {
2377
2554
  throw new Error(
@@ -2389,7 +2566,8 @@ function buildServer(options) {
2389
2566
  {
2390
2567
  capabilities: {
2391
2568
  tools: {}
2392
- }
2569
+ },
2570
+ instructions: SERVER_INSTRUCTIONS
2393
2571
  }
2394
2572
  );
2395
2573
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -2441,6 +2619,8 @@ function buildServer(options) {
2441
2619
  apiKey: options.apiKey,
2442
2620
  baseUrl,
2443
2621
  mcpVersion: version,
2622
+ localFiles: true,
2623
+ // stdio: the caller's own machine
2444
2624
  logger,
2445
2625
  buildStatusUrl: (jobId) => `${baseUrl}/api/v1/jobs/${encodeURIComponent(jobId)}`
2446
2626
  };