@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/index.cjs CHANGED
@@ -113,7 +113,8 @@ var init_types = __esm({
113
113
  "src/skills/types.ts"() {
114
114
  }
115
115
  });
116
- async function uploadLocalFile(path$1, client) {
116
+ async function uploadLocalFile(path$1, ctx) {
117
+ if (ctx.localFiles !== true) throw new SkillUploadError(HOSTED_LOCAL_PATH_MESSAGE);
117
118
  const ext = path.extname(path$1).toLowerCase();
118
119
  const mime = EXT_TO_MIME[ext];
119
120
  if (!mime) {
@@ -152,13 +153,13 @@ async function uploadLocalFile(path$1, client) {
152
153
  const buffer = await promises.readFile(path$1);
153
154
  const blob = new Blob([new Uint8Array(buffer)], { type: mime });
154
155
  const filename = path.basename(path$1);
155
- const response = await client.uploadImage(blob, filename);
156
+ const response = await ctx.client.uploadImage(blob, filename);
156
157
  return response.url;
157
158
  }
158
- async function uploadLocalFiles(paths, client) {
159
- return Promise.all(paths.map((p) => uploadLocalFile(p, client)));
159
+ async function uploadLocalFiles(paths, ctx) {
160
+ return Promise.all(paths.map((p) => uploadLocalFile(p, ctx)));
160
161
  }
161
- var MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, EXT_TO_MIME, SkillUploadError;
162
+ var MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, EXT_TO_MIME, SkillUploadError, HOSTED_LOCAL_PATH_MESSAGE;
162
163
  var init_upload_helpers = __esm({
163
164
  "src/skills/upload-helpers.ts"() {
164
165
  MAX_IMAGE_BYTES = 50 * 1024 * 1024;
@@ -181,6 +182,7 @@ var init_upload_helpers = __esm({
181
182
  }
182
183
  userMessage;
183
184
  };
185
+ 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.";
184
186
  }
185
187
  });
186
188
 
@@ -452,10 +454,10 @@ async function run(input, ctx) {
452
454
  let resolved = input;
453
455
  let referenceUrls = [];
454
456
  try {
455
- const singleUrl = input.image_url ?? (input.image_path ? await uploadLocalFile(input.image_path, ctx.client) : void 0);
457
+ const singleUrl = input.image_url ?? (input.image_path ? await uploadLocalFile(input.image_path, ctx) : void 0);
456
458
  const manyUrls = [...input.image_urls ?? []];
457
459
  for (const p of input.image_paths ?? []) {
458
- manyUrls.push(await uploadLocalFile(p, ctx.client));
460
+ manyUrls.push(await uploadLocalFile(p, ctx));
459
461
  }
460
462
  referenceUrls = [...singleUrl ? [singleUrl] : [], ...manyUrls].filter(
461
463
  (u, i, all) => all.indexOf(u) === i
@@ -540,19 +542,19 @@ var init_generate = __esm({
540
542
  InputSchema = zod.z.object({
541
543
  prompt: zod.z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
542
544
  model: zod.z.string().optional().describe(
543
- '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.'
545
+ `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.`
544
546
  ),
545
547
  preset: zod.z.string().optional().describe(
546
- '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.'
548
+ "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."
547
549
  ),
548
- aspect_ratio: zod.z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
549
- duration: zod.z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
550
+ aspect_ratio: zod.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.'),
551
+ duration: zod.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)."),
550
552
  resolution: zod.z.string().optional().describe(
551
553
  'Output resolution when the model supports it (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits. Omit to use the model default.'
552
554
  ),
553
- image_url: zod.z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
555
+ image_url: zod.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."),
554
556
  video_url: zod.z.string().url().optional().describe(
555
- "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."
557
+ "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."
556
558
  ),
557
559
  params: ParamsInput,
558
560
  image_path: zod.z.string().min(1).optional().describe(
@@ -579,45 +581,56 @@ var init_generate = __esm({
579
581
  prompt: { type: "string", description: "Prompt describing what to generate." },
580
582
  model: {
581
583
  type: "string",
582
- 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.'
584
+ 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.`
583
585
  },
584
586
  preset: {
585
587
  type: "string",
586
- description: 'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean"). Explicit fields override preset values.'
588
+ 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."
589
+ },
590
+ aspect_ratio: {
591
+ type: "string",
592
+ 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.'
593
+ },
594
+ duration: {
595
+ type: "integer",
596
+ 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)."
587
597
  },
588
- aspect_ratio: { type: "string" },
589
- duration: { type: "integer" },
590
598
  resolution: {
591
599
  type: "string",
592
- description: 'Output resolution when supported (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits.'
600
+ 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.'
601
+ },
602
+ image_url: {
603
+ type: "string",
604
+ format: "uri",
605
+ 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."
593
606
  },
594
- image_url: { type: "string", format: "uri" },
595
607
  video_url: {
596
608
  type: "string",
597
609
  format: "uri",
598
- description: "Source video clip to animate / restyle / extend. Use this for videos; image_url is for still frames."
610
+ 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."
599
611
  },
600
612
  params: PARAMS_JSON_SCHEMA,
601
613
  image_urls: {
602
614
  type: "array",
603
615
  items: { type: "string", format: "uri" },
604
- description: "Multiple reference image URLs for models that accept a reference set. Single-reference models use the first."
616
+ 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."
605
617
  },
606
618
  image_paths: {
607
619
  type: "array",
608
620
  items: { type: "string" },
609
- description: "Multiple absolute local file paths, each uploaded to 24h temp storage and used as a reference."
621
+ 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."
610
622
  },
611
623
  image_path: {
612
624
  type: "string",
613
- 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."
625
+ 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."
614
626
  }
615
627
  },
616
628
  required: ["prompt"]
617
629
  };
618
630
  generateSkill = {
619
631
  name: "pixeldojo_generate",
620
- 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`.",
632
+ 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.",
633
+ 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.",
621
634
  inputZod: InputSchema,
622
635
  inputJsonSchema: InputJsonSchema,
623
636
  allowedModels: null,
@@ -693,13 +706,13 @@ async function run2(input, ctx) {
693
706
  let resolved = input;
694
707
  try {
695
708
  if (input.image_paths && input.image_paths.length > 0) {
696
- const urls = await uploadLocalFiles(input.image_paths, ctx.client);
709
+ const urls = await uploadLocalFiles(input.image_paths, ctx);
697
710
  resolved = {
698
711
  ...input,
699
712
  image_urls: [...input.image_urls ?? [], ...urls]
700
713
  };
701
714
  } else if (input.image_path && !input.image_url) {
702
- const url = await uploadLocalFile(input.image_path, ctx.client);
715
+ const url = await uploadLocalFile(input.image_path, ctx);
703
716
  resolved = { ...input, image_url: url };
704
717
  }
705
718
  } catch (err) {
@@ -781,32 +794,35 @@ var init_edit = __esm({
781
794
  image_url: {
782
795
  type: "string",
783
796
  format: "uri",
784
- description: "Single reference image. Pass image_urls for multi-image edits."
797
+ description: "Single image to edit. Ignored when image_urls is set; pass image_urls for multi-image edits."
785
798
  },
786
799
  image_urls: {
787
800
  type: "array",
788
801
  items: { type: "string", format: "uri" },
789
802
  minItems: 1,
790
803
  maxItems: 9,
791
- description: "Multiple reference images (1-9)."
804
+ 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."
792
805
  },
793
806
  image_path: {
794
807
  type: "string",
795
- 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."
808
+ 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."
796
809
  },
797
810
  image_paths: {
798
811
  type: "array",
799
812
  items: { type: "string" },
800
813
  minItems: 1,
801
814
  maxItems: 9,
802
- description: "Multiple absolute local file paths (1-9). Each uploaded before dispatch."
815
+ description: "Multiple absolute local image paths (1-9). Each is uploaded to 24h temp storage and appended after any image_urls."
803
816
  },
804
817
  model: {
805
818
  type: "string",
806
819
  enum: [...ALLOWED_MODELS],
807
820
  description: "Override edit model. Default: wan-2.7-image-edit."
808
821
  },
809
- aspect_ratio: { type: "string" },
822
+ aspect_ratio: {
823
+ type: "string",
824
+ 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.'
825
+ },
810
826
  params: PARAMS_JSON_SCHEMA
811
827
  },
812
828
  required: ["prompt"]
@@ -814,6 +830,7 @@ var init_edit = __esm({
814
830
  editSkill = {
815
831
  name: "pixeldojo_edit",
816
832
  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).",
833
+ 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.",
817
834
  inputZod: InputSchema2,
818
835
  inputJsonSchema: InputJsonSchema2,
819
836
  allowedModels: [...ALLOWED_MODELS],
@@ -924,7 +941,7 @@ var init_character = __esm({
924
941
  InputJsonSchema3 = {
925
942
  type: "object",
926
943
  properties: {
927
- prompt: { type: "string" },
944
+ prompt: { type: "string", description: "The shot to render: scene, pose, wardrobe, framing. The references supply the identity." },
928
945
  model: {
929
946
  type: "string",
930
947
  enum: ALLOWED,
@@ -933,16 +950,19 @@ var init_character = __esm({
933
950
  reference_image_url: {
934
951
  type: "string",
935
952
  format: "uri",
936
- description: "Single reference. Prefer reference_image_urls."
953
+ description: "Single still of the character. Ignored when reference_image_urls is set; prefer that, since more angles lock the face better."
937
954
  },
938
955
  reference_image_urls: {
939
956
  type: "array",
940
957
  items: { type: "string", format: "uri" },
941
958
  minItems: 1,
942
959
  maxItems: MAX_REFERENCES,
943
- description: "All references for this shot, most important first. Multiple angles of the same person lock the face far better than one photo."
960
+ 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."
961
+ },
962
+ aspect_ratio: {
963
+ type: "string",
964
+ 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).'
944
965
  },
945
- aspect_ratio: { type: "string" },
946
966
  params: PARAMS_JSON_SCHEMA
947
967
  },
948
968
  required: ["prompt"]
@@ -1039,24 +1059,30 @@ var init_storyboard = __esm({
1039
1059
  properties: {
1040
1060
  brief: {
1041
1061
  type: "string",
1042
- description: "Storyboard brief: scene, shots, mood, action."
1062
+ description: "Scene brief: setting, action, camera, mood. Sent to the video model as its prompt."
1043
1063
  },
1044
1064
  model: {
1045
1065
  type: "string",
1046
1066
  enum: ALLOWED2,
1047
- description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`
1067
+ description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}, or ${VIDEO_REFERENCE_MODEL} when image_url or video_url is given.`
1068
+ },
1069
+ aspect_ratio: {
1070
+ type: "string",
1071
+ 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.'
1072
+ },
1073
+ duration: {
1074
+ type: "integer",
1075
+ description: "Clip length in seconds. Snapped or clamped to what the model allows."
1048
1076
  },
1049
- aspect_ratio: { type: "string" },
1050
- duration: { type: "integer" },
1051
1077
  image_url: {
1052
1078
  type: "string",
1053
1079
  format: "uri",
1054
- description: "Reference still to build the shot from."
1080
+ 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."
1055
1081
  },
1056
1082
  video_url: {
1057
1083
  type: "string",
1058
1084
  format: "uri",
1059
- description: "Source clip to work from. Use this for videos; image_url is for stills."
1085
+ 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."
1060
1086
  },
1061
1087
  params: PARAMS_JSON_SCHEMA
1062
1088
  },
@@ -1064,7 +1090,7 @@ var init_storyboard = __esm({
1064
1090
  };
1065
1091
  storyboardSkill = {
1066
1092
  name: "pixeldojo_storyboard",
1067
- 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.",
1093
+ 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.",
1068
1094
  inputZod: InputSchema4,
1069
1095
  inputJsonSchema: InputJsonSchema4,
1070
1096
  allowedModels: [...ALLOWED2],
@@ -1154,27 +1180,49 @@ var init_upscale = __esm({
1154
1180
  scale_factor: zod.z.number().min(1).max(16).optional().describe(
1155
1181
  "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)."
1156
1182
  ),
1157
- creativity: zod.z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0."),
1183
+ creativity: zod.z.number().int().min(-10).max(10).optional().describe("Magnific only: integer -10 (preserve) \u2192 10 (creative). Defaults to 0."),
1158
1184
  video_duration: zod.z.number().positive().optional().describe("video-upscaler only, REQUIRED there: source clip length in seconds (its credit meter)."),
1159
1185
  target_resolution: zod.z.enum(VIDEO_RESOLUTIONS).optional().describe("video-upscaler only: target output resolution. Defaults to 1080p.")
1160
1186
  });
1161
1187
  InputJsonSchema5 = {
1162
1188
  type: "object",
1163
1189
  properties: {
1164
- image_url: { type: "string", format: "uri" },
1190
+ image_url: {
1191
+ type: "string",
1192
+ format: "uri",
1193
+ description: "Source image to upscale (image models). A clip URL here (.mp4/.mov/.webm) is treated as video_url."
1194
+ },
1165
1195
  video_url: {
1166
1196
  type: "string",
1167
1197
  format: "uri",
1168
- description: "Source video clip \u2014 routes to video-upscaler."
1198
+ description: "Source video clip (MP4, max 500 MB). Routes to video-upscaler when no model is named; needs video_duration."
1199
+ },
1200
+ model: {
1201
+ type: "string",
1202
+ enum: ALLOWED3,
1203
+ description: `Defaults to ${DEFAULT_MODEL5}, or ${VIDEO_MODEL} when the source is a clip. face-enhance is for portraits.`
1204
+ },
1205
+ scale_factor: {
1206
+ type: "number",
1207
+ minimum: 1,
1208
+ maximum: 16,
1209
+ 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)."
1210
+ },
1211
+ creativity: {
1212
+ type: "integer",
1213
+ minimum: -10,
1214
+ maximum: 10,
1215
+ 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)."
1169
1216
  },
1170
- model: { type: "string", enum: ALLOWED3 },
1171
- scale_factor: { type: "number", minimum: 1, maximum: 16 },
1172
- creativity: { type: "number", minimum: 0, maximum: 1 },
1173
1217
  video_duration: {
1174
1218
  type: "number",
1175
- description: "video-upscaler only, required there: source clip length in seconds."
1219
+ description: "video-upscaler only, required there: source clip length in seconds. It sets the cost (1 credit per second)."
1176
1220
  },
1177
- target_resolution: { type: "string", enum: VIDEO_RESOLUTIONS }
1221
+ target_resolution: {
1222
+ type: "string",
1223
+ enum: VIDEO_RESOLUTIONS,
1224
+ description: "video-upscaler only: output resolution. Defaults to 1080p."
1225
+ }
1178
1226
  }
1179
1227
  };
1180
1228
  upscaleSkill = {
@@ -1263,7 +1311,7 @@ var init_status = __esm({
1263
1311
  properties: {
1264
1312
  jobId: {
1265
1313
  type: "string",
1266
- description: "Job id returned from a prior pixeldojo_* skill handoff."
1314
+ description: "The jobId a generation tool returned instead of an asset (a job_... id)."
1267
1315
  }
1268
1316
  },
1269
1317
  required: ["jobId"]
@@ -1272,7 +1320,7 @@ var init_status = __esm({
1272
1320
  ABANDON_HINT_MS = 12 * 60 * 1e3;
1273
1321
  statusSkill = {
1274
1322
  name: "pixeldojo_status",
1275
- 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.",
1323
+ 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".',
1276
1324
  inputZod: InputSchema6,
1277
1325
  inputJsonSchema: InputJsonSchema6,
1278
1326
  // status is special — it doesn't dispatch to a model
@@ -1317,7 +1365,7 @@ var init_from_url = __esm({
1317
1365
  };
1318
1366
  fromUrlSkill = {
1319
1367
  name: "pixeldojo_from_url",
1320
- 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.",
1368
+ 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.",
1321
1369
  inputZod: InputSchema7,
1322
1370
  inputJsonSchema: InputJsonSchema7,
1323
1371
  // No model dispatch — this skill calls a server-side scrape, not a generation.
@@ -1522,20 +1570,31 @@ var init_film = __esm({
1522
1570
  film_id: zod.z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status."),
1523
1571
  action: zod.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."),
1524
1572
  soundtrack: zod.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."),
1525
- music_job_id: zod.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.")
1573
+ music_job_id: zod.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.")
1526
1574
  });
1527
1575
  InputJsonSchema8 = {
1528
1576
  type: "object",
1529
1577
  properties: {
1530
- idea: { type: "string", description: "One-to-three sentence film idea. The studio writes the screenplay (free)." },
1531
- script: { type: "string", description: "A full screenplay to plan instead of an idea." },
1532
- look: { type: "string", enum: [...LOOKS], description: "Visual style. Default live-action." },
1533
- aspect: { type: "string", enum: ["16:9", "9:16"], description: "Default 16:9." },
1534
- length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Target length (~6/12/24/60 shots). Default 60s." },
1535
- film_id: { type: "string", description: "Existing film id for produce/status." },
1536
- action: { type: "string", enum: ["plan", "produce", "status"], description: "plan (free) | produce (charged) | status (poll every ~30s)." },
1537
- soundtrack: { type: "string", enum: ["scene-synced", "flat", "none"], description: "Default scene-synced." },
1538
- music_job_id: { type: "string", description: "Pass back from a previous status reply while the score is composing." }
1578
+ idea: { type: "string", description: "Plan step: one-to-three sentence film idea. The studio writes the screenplay (free). Provide idea or script." },
1579
+ script: { type: "string", description: "Plan step: a full screenplay (sluglines, action lines, dialogue) to plan instead of an idea." },
1580
+ look: { type: "string", enum: [...LOOKS], description: "Plan step: visual style carried through frames, clips and soundtrack. Default live-action." },
1581
+ aspect: { type: "string", enum: ["16:9", "9:16"], description: "Plan step: frame aspect ratio. Default 16:9." },
1582
+ 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." },
1583
+ 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".' },
1584
+ action: {
1585
+ type: "string",
1586
+ enum: ["plan", "produce", "status"],
1587
+ 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."
1588
+ },
1589
+ soundtrack: {
1590
+ type: "string",
1591
+ enum: ["scene-synced", "flat", "none"],
1592
+ 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."
1593
+ },
1594
+ music_job_id: {
1595
+ type: "string",
1596
+ 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."
1597
+ }
1539
1598
  }
1540
1599
  };
1541
1600
  shots = (f) => f.scenes.flatMap((s) => s.shots);
@@ -1572,11 +1631,11 @@ var init_film = __esm({
1572
1631
  async function run9(input, ctx) {
1573
1632
  const productImages = [
1574
1633
  ...input.product_image_urls ?? [],
1575
- ...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx.client)))
1634
+ ...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx)))
1576
1635
  ];
1577
1636
  const characterImages = [
1578
1637
  ...input.character_image_urls ?? [],
1579
- ...await Promise.all((input.character_image_paths ?? []).map((p) => uploadLocalFile(p, ctx.client)))
1638
+ ...await Promise.all((input.character_image_paths ?? []).map((p) => uploadLocalFile(p, ctx)))
1580
1639
  ];
1581
1640
  const body = {
1582
1641
  style: input.style,
@@ -1654,16 +1713,24 @@ var init_ad = __esm({
1654
1713
  product_url: {
1655
1714
  type: "string",
1656
1715
  format: "uri",
1657
- description: "Product page URL. Name, description, and images are extracted automatically."
1716
+ 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."
1717
+ },
1718
+ product_name: {
1719
+ type: "string",
1720
+ description: "Product name. Required with product_image_urls / product_image_paths when there is no product_url."
1721
+ },
1722
+ product_description: { type: "string", description: "Optional product description (no product_url case)." },
1723
+ product_image_urls: {
1724
+ type: "array",
1725
+ items: { type: "string", format: "uri" },
1726
+ maxItems: 5,
1727
+ description: "Up to 5 hosted product photos (no product_url case)."
1658
1728
  },
1659
- product_name: { type: "string" },
1660
- product_description: { type: "string" },
1661
- product_image_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 5 },
1662
1729
  product_image_paths: {
1663
1730
  type: "array",
1664
1731
  items: { type: "string" },
1665
1732
  maxItems: 5,
1666
- description: "Absolute local file paths; uploaded to 24h temp storage automatically."
1733
+ description: "Up to 5 absolute local product image paths; uploaded to 24h temp storage automatically."
1667
1734
  },
1668
1735
  character_image_urls: {
1669
1736
  type: "array",
@@ -1671,12 +1738,21 @@ var init_ad = __esm({
1671
1738
  maxItems: 4,
1672
1739
  description: "Presenter reference images for a consistent on-camera character."
1673
1740
  },
1674
- character_image_paths: { type: "array", items: { type: "string" }, maxItems: 4 },
1741
+ character_image_paths: {
1742
+ type: "array",
1743
+ items: { type: "string" },
1744
+ maxItems: 4,
1745
+ description: "Absolute local presenter image paths; uploaded to 24h temp storage automatically."
1746
+ },
1675
1747
  location_description: {
1676
1748
  type: "string",
1677
1749
  description: 'Where the ad takes place ("a sunlit Parisian cafe"). Text is enough.'
1678
1750
  },
1679
- location_image_url: { type: "string", format: "uri" },
1751
+ location_image_url: {
1752
+ type: "string",
1753
+ format: "uri",
1754
+ description: "Optional reference photo of the location."
1755
+ },
1680
1756
  hook: {
1681
1757
  type: "string",
1682
1758
  description: "The opening visual beat (first 1-2 seconds). Omit to use the style default."
@@ -1685,21 +1761,34 @@ var init_ad = __esm({
1685
1761
  type: "string",
1686
1762
  description: "The exact spoken line, voiced word for word. Omit and the presenter improvises."
1687
1763
  },
1688
- quality: { type: "string", enum: ["mini", "fast", "high"] },
1764
+ quality: {
1765
+ type: "string",
1766
+ enum: ["mini", "fast", "high"],
1767
+ description: "Render tier. Omitted or unsupported by the ad engine, the engine's default tier is used."
1768
+ },
1689
1769
  resolution: {
1690
1770
  type: "string",
1691
1771
  enum: ["480p", "720p", "1080p", "4k"],
1692
- description: '1080p/4k require quality "high".'
1772
+ description: '1080p/4k require quality "high"; without it, or when the engine lacks the value, the engine default resolution is used.'
1773
+ },
1774
+ aspect_ratio: {
1775
+ type: "string",
1776
+ enum: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"],
1777
+ description: "Defaults to the style's own aspect ratio; a value the engine does not support falls back to it."
1693
1778
  },
1694
- aspect_ratio: { type: "string", enum: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"] },
1695
- duration: { type: "integer", minimum: 4, maximum: 15 }
1779
+ duration: {
1780
+ type: "integer",
1781
+ minimum: 4,
1782
+ maximum: 15,
1783
+ description: "Ad length in seconds. Defaults to the style's length; clamped to what the engine allows."
1784
+ }
1696
1785
  }
1697
1786
  // "product_url OR product_name+images" is enforced by the server and the
1698
1787
  // Zod refine; no root-level oneOf (Grok rejects non-object root unions).
1699
1788
  };
1700
1789
  adSkill = {
1701
1790
  name: "pixeldojo_ad",
1702
- 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.",
1791
+ 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.",
1703
1792
  inputZod: InputSchema9,
1704
1793
  inputJsonSchema: InputJsonSchema9,
1705
1794
  // Composition skill — the server routes to the ad engine internally.
@@ -1761,27 +1850,47 @@ var init_campaign = __esm({
1761
1850
  productUrl: {
1762
1851
  type: "string",
1763
1852
  format: "uri",
1764
- description: "Product page URL. We extract name + description + images automatically."
1853
+ description: "Product page URL; name, description and images are extracted automatically. Pass this or `product` (one is required); when both are set, productUrl wins."
1765
1854
  },
1766
1855
  product: {
1767
1856
  type: "object",
1857
+ description: "Manual product profile, used when productUrl is omitted.",
1768
1858
  properties: {
1769
- name: { type: "string" },
1770
- description: { type: "string" },
1771
- images: { type: "array", items: { type: "string", format: "uri" } }
1859
+ name: { type: "string", description: "Product name (required)." },
1860
+ description: { type: "string", description: "Optional product description." },
1861
+ images: {
1862
+ type: "array",
1863
+ items: { type: "string", format: "uri" },
1864
+ maxItems: 5,
1865
+ description: "Up to 5 hosted product photo URLs."
1866
+ }
1772
1867
  },
1773
1868
  required: ["name"]
1774
1869
  },
1775
1870
  tone: {
1776
1871
  type: "string",
1777
- description: "Creative direction threaded through every prompt."
1872
+ description: 'Optional creative direction ("premium minimalist", "playful Y2K"), threaded through every prompt.'
1778
1873
  },
1779
- variants: { type: "integer", minimum: 1, maximum: 8 },
1780
- includeVideo: { type: "boolean" },
1781
- videoAspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1"] },
1782
- videoMode: { type: "string", enum: ["lifestyle", "cgi", "ugc", "skip"] }
1783
- },
1784
- oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
1874
+ variants: {
1875
+ type: "integer",
1876
+ minimum: 1,
1877
+ maximum: 8,
1878
+ description: "Lifestyle shots in addition to the hero (default 4)."
1879
+ },
1880
+ includeVideo: { type: "boolean", description: "Also render a launch video (default true)." },
1881
+ videoAspectRatio: {
1882
+ type: "string",
1883
+ enum: ["9:16", "16:9", "1:1"],
1884
+ description: "Launch video aspect ratio (default 9:16)."
1885
+ },
1886
+ videoMode: {
1887
+ type: "string",
1888
+ enum: ["lifestyle", "cgi", "ugc", "skip"],
1889
+ description: "Launch video styling: lifestyle (default), cgi, ugc, or skip for no video."
1890
+ }
1891
+ }
1892
+ // productUrl-or-product is enforced by the Zod refine and the server; no
1893
+ // root-level oneOf (some clients reject root unions — see ad.ts).
1785
1894
  };
1786
1895
  campaignSkill = {
1787
1896
  name: "pixeldojo_campaign",
@@ -1856,7 +1965,7 @@ var init_campaign_status = __esm({
1856
1965
  async function run12(input, ctx) {
1857
1966
  let url;
1858
1967
  try {
1859
- url = await uploadLocalFile(input.path, ctx.client);
1968
+ url = await uploadLocalFile(input.path, ctx);
1860
1969
  } catch (err) {
1861
1970
  if (err instanceof SkillUploadError) {
1862
1971
  return { kind: "text", text: `pixeldojo_upload: ${err.userMessage}` };
@@ -1866,7 +1975,7 @@ async function run12(input, ctx) {
1866
1975
  return {
1867
1976
  kind: "text",
1868
1977
  text: `Uploaded ${path.basename(input.path)} \u2192 ${url}
1869
- (temp storage; auto-deletes in ~24h \u2014 pass this URL as image_url to :generate or :edit)`
1978
+ (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.)`
1870
1979
  };
1871
1980
  }
1872
1981
  var InputSchema12, InputJsonSchema12, uploadSkill;
@@ -1890,7 +1999,7 @@ var init_upload = __esm({
1890
1999
  };
1891
2000
  uploadSkill = {
1892
2001
  name: "pixeldojo_upload",
1893
- 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.",
2002
+ 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.",
1894
2003
  inputZod: InputSchema12,
1895
2004
  inputJsonSchema: InputJsonSchema12,
1896
2005
  allowedModels: [],
@@ -1908,18 +2017,7 @@ async function run13(input, ctx) {
1908
2017
  };
1909
2018
  }
1910
2019
  const apiId = requested;
1911
- let audioUrls = input.audio_urls ?? [];
1912
- if (input.audio_paths?.length) {
1913
- try {
1914
- const uploaded = await uploadLocalFiles(input.audio_paths, ctx.client);
1915
- audioUrls = [...audioUrls, ...uploaded].slice(0, 3);
1916
- } catch (err) {
1917
- if (err instanceof SkillUploadError) {
1918
- return { kind: "text", text: `pixeldojo_audio (upload): ${err.userMessage}` };
1919
- }
1920
- throw err;
1921
- }
1922
- }
2020
+ const audioUrls = input.audio_urls ?? [];
1923
2021
  const body = { prompt: input.prompt };
1924
2022
  if (apiId === "seed-audio") {
1925
2023
  if (input.voice) body.voice = input.voice;
@@ -1952,37 +2050,57 @@ var ALLOWED4, DEFAULT_MODEL6, InputSchema13, InputJsonSchema13, audioSkill;
1952
2050
  var init_audio = __esm({
1953
2051
  "src/skills/audio.ts"() {
1954
2052
  init_poll();
1955
- init_upload_helpers();
1956
2053
  init_types();
1957
2054
  ALLOWED4 = ["seed-audio", "text-to-speech"];
1958
2055
  DEFAULT_MODEL6 = "seed-audio";
1959
2056
  InputSchema13 = zod.z.object({
1960
2057
  prompt: zod.z.string().min(1).describe(
1961
- "A script for speech, or a scene description for cinematic audio (dialogue + effects + music). Reference uploaded clips as @Audio1..@Audio3."
2058
+ "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."
1962
2059
  ),
1963
- model: zod.z.string().optional().describe(`One of: ${ALLOWED4.join(", ")}. Defaults to ${DEFAULT_MODEL6}.`),
1964
- voice: zod.z.string().optional().describe("Optional voice preset name or cloned-voice id (seed-audio) / voice_id (text-to-speech)."),
1965
- audio_urls: zod.z.array(zod.z.string().url()).max(3).optional().describe("seed-audio only: up to 3 reference audio URLs (30s / 10MB max each)."),
1966
- audio_paths: zod.z.array(zod.z.string()).max(3).optional().describe("seed-audio only: absolute local paths to reference clips; uploaded automatically (24h temp storage)."),
1967
- output_format: zod.z.string().optional().describe("seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3, wav, flac, pcm."),
1968
- speed: zod.z.number().min(0.5).max(2).optional().describe("Speech speed multiplier (0.5\u20132).")
2060
+ model: zod.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.`),
2061
+ voice: zod.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."),
2062
+ audio_urls: zod.z.array(zod.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."),
2063
+ output_format: zod.z.string().optional().describe("Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."),
2064
+ speed: zod.z.number().min(0.5).max(2).optional().describe("Speech speed multiplier, 0.5-2 (default 1). Both models.")
1969
2065
  });
1970
2066
  InputJsonSchema13 = {
1971
2067
  type: "object",
1972
2068
  properties: {
1973
- prompt: { type: "string" },
1974
- model: { type: "string", enum: ALLOWED4 },
1975
- voice: { type: "string" },
1976
- audio_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 3 },
1977
- audio_paths: { type: "array", items: { type: "string" }, maxItems: 3 },
1978
- output_format: { type: "string" },
1979
- speed: { type: "number", minimum: 0.5, maximum: 2 }
2069
+ prompt: {
2070
+ type: "string",
2071
+ 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."
2072
+ },
2073
+ model: {
2074
+ type: "string",
2075
+ enum: ALLOWED4,
2076
+ 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.`
2077
+ },
2078
+ voice: {
2079
+ type: "string",
2080
+ 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."
2081
+ },
2082
+ audio_urls: {
2083
+ type: "array",
2084
+ items: { type: "string", format: "uri" },
2085
+ maxItems: 3,
2086
+ 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."
2087
+ },
2088
+ output_format: {
2089
+ type: "string",
2090
+ description: "Output format. seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3 (default), wav, flac, pcm."
2091
+ },
2092
+ speed: {
2093
+ type: "number",
2094
+ minimum: 0.5,
2095
+ maximum: 2,
2096
+ description: "Speech speed multiplier, 0.5-2 (default 1). Both models."
2097
+ }
1980
2098
  },
1981
2099
  required: ["prompt"]
1982
2100
  };
1983
2101
  audioSkill = {
1984
2102
  name: "pixeldojo_audio",
1985
- description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain TTS available).",
2103
+ 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.",
1986
2104
  inputZod: InputSchema13,
1987
2105
  inputJsonSchema: InputJsonSchema13,
1988
2106
  allowedModels: [...ALLOWED4],
@@ -2019,7 +2137,7 @@ async function run14(input, ctx) {
2019
2137
  text: `${res.items.length} library item(s):
2020
2138
  ${lines.join("\n")}
2021
2139
 
2022
- Pass any URL back into :generate, :edit, :character, or :upscale as a reference input.`
2140
+ Pass any URL back into pixeldojo_generate, pixeldojo_edit, pixeldojo_character, or pixeldojo_upscale as a reference input.`
2023
2141
  };
2024
2142
  }
2025
2143
  var InputSchema14, InputJsonSchema14, librarySkill;
@@ -2034,10 +2152,17 @@ var init_library = __esm({
2034
2152
  InputJsonSchema14 = {
2035
2153
  type: "object",
2036
2154
  properties: {
2037
- q: { type: "string" },
2038
- model: { type: "string" },
2039
- modality: { type: "string", enum: ["image", "video", "audio"] },
2040
- limit: { type: "integer", minimum: 1, maximum: 50 }
2155
+ q: {
2156
+ type: "string",
2157
+ description: "Substring match against the prompt that produced the asset. Omit to list the newest items."
2158
+ },
2159
+ model: { type: "string", description: "Only assets from this model apiId." },
2160
+ modality: {
2161
+ type: "string",
2162
+ enum: ["image", "video", "audio"],
2163
+ description: "Only this output type."
2164
+ },
2165
+ limit: { type: "integer", minimum: 1, maximum: 50, description: "Max results (default 10)." }
2041
2166
  },
2042
2167
  required: []
2043
2168
  };
@@ -2179,7 +2304,7 @@ async function runWorkflow(input, ctx) {
2179
2304
  ${outputs.join("\n")}`
2180
2305
  };
2181
2306
  }
2182
- var WORKFLOW_STEP_EXTRA_BUDGET_MS, StepSchema, SaveInputSchema, RunInputSchema, ListInputSchema, stepJson, saveWorkflowSkill, runWorkflowSkill, listWorkflowsSkill;
2307
+ var WORKFLOW_STEP_EXTRA_BUDGET_MS, StepSchema, SaveInputSchema, RunInputSchema, ListInputSchema, STEP_SKILLS, stepJson, saveWorkflowSkill, runWorkflowSkill, listWorkflowsSkill;
2183
2308
  var init_workflows = __esm({
2184
2309
  "src/skills/workflows.ts"() {
2185
2310
  init_poll();
@@ -2203,11 +2328,28 @@ var init_workflows = __esm({
2203
2328
  inputs: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Values for {{input.<key>}} placeholders in the workflow steps.")
2204
2329
  });
2205
2330
  ListInputSchema = zod.z.object({});
2331
+ STEP_SKILLS = [
2332
+ "pixeldojo_generate",
2333
+ "pixeldojo_edit",
2334
+ "pixeldojo_character",
2335
+ "pixeldojo_storyboard",
2336
+ "pixeldojo_upscale",
2337
+ "pixeldojo_audio",
2338
+ "pixeldojo_upload",
2339
+ "pixeldojo_from_url"
2340
+ ];
2206
2341
  stepJson = {
2207
2342
  type: "object",
2208
2343
  properties: {
2209
- skill: { type: "string" },
2210
- args: { type: "object" }
2344
+ skill: {
2345
+ type: "string",
2346
+ enum: STEP_SKILLS,
2347
+ description: "Tool this step runs. Steps run in order when the workflow runs, and each is charged at that tool's own rate."
2348
+ },
2349
+ args: {
2350
+ type: "object",
2351
+ 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)."
2352
+ }
2211
2353
  },
2212
2354
  required: ["skill"]
2213
2355
  };
@@ -2218,9 +2360,19 @@ var init_workflows = __esm({
2218
2360
  inputJsonSchema: {
2219
2361
  type: "object",
2220
2362
  properties: {
2221
- name: { type: "string", pattern: "^[a-z0-9][a-z0-9-]{1,40}$" },
2222
- description: { type: "string" },
2223
- steps: { type: "array", items: stepJson, minItems: 1, maxItems: 8 }
2363
+ name: {
2364
+ type: "string",
2365
+ pattern: "^[a-z0-9][a-z0-9-]{1,40}$",
2366
+ description: 'Kebab-case handle, 2-41 chars (e.g. "product-launch-pack"). Saving to an existing name overwrites it.'
2367
+ },
2368
+ description: { type: "string", description: "Optional note on what the workflow does (max 500 chars)." },
2369
+ steps: {
2370
+ type: "array",
2371
+ items: stepJson,
2372
+ minItems: 1,
2373
+ maxItems: 8,
2374
+ description: "The chain, 1-8 steps, run in order. Saving charges nothing; running charges each step."
2375
+ }
2224
2376
  },
2225
2377
  required: ["name", "steps"]
2226
2378
  },
@@ -2235,8 +2387,12 @@ var init_workflows = __esm({
2235
2387
  inputJsonSchema: {
2236
2388
  type: "object",
2237
2389
  properties: {
2238
- name: { type: "string" },
2239
- inputs: { type: "object", additionalProperties: { type: "string" } }
2390
+ name: { type: "string", description: "Saved workflow name (see pixeldojo_list_workflows)." },
2391
+ inputs: {
2392
+ type: "object",
2393
+ additionalProperties: { type: "string" },
2394
+ 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.`
2395
+ }
2240
2396
  },
2241
2397
  required: ["name"]
2242
2398
  },
@@ -2260,6 +2416,7 @@ var init_workflows = __esm({
2260
2416
  var skills_exports = {};
2261
2417
  __export(skills_exports, {
2262
2418
  ALL_SKILLS: () => exports.ALL_SKILLS,
2419
+ HOSTED_SKILLS: () => exports.HOSTED_SKILLS,
2263
2420
  adSkill: () => adSkill,
2264
2421
  audioSkill: () => audioSkill,
2265
2422
  campaignSkill: () => campaignSkill,
@@ -2267,6 +2424,7 @@ __export(skills_exports, {
2267
2424
  characterSkill: () => characterSkill,
2268
2425
  editSkill: () => editSkill,
2269
2426
  filmSkill: () => filmSkill,
2427
+ findHostedSkill: () => findHostedSkill,
2270
2428
  findSkill: () => findSkill,
2271
2429
  fromUrlSkill: () => fromUrlSkill,
2272
2430
  generateSkill: () => generateSkill,
@@ -2279,11 +2437,15 @@ __export(skills_exports, {
2279
2437
  uploadSkill: () => uploadSkill,
2280
2438
  upscaleSkill: () => upscaleSkill
2281
2439
  });
2440
+ function findHostedSkill(name) {
2441
+ const wanted = name.replace(/^pixeldojo:/, "pixeldojo_");
2442
+ return exports.HOSTED_SKILLS.find((s) => s.name === wanted);
2443
+ }
2282
2444
  function findSkill(name) {
2283
2445
  const wanted = name.replace(/^pixeldojo:/, "pixeldojo_");
2284
2446
  return exports.ALL_SKILLS.find((s) => s.name === wanted);
2285
2447
  }
2286
- exports.ALL_SKILLS = void 0;
2448
+ exports.ALL_SKILLS = void 0; var isLocalPathField; exports.HOSTED_SKILLS = void 0;
2287
2449
  var init_skills = __esm({
2288
2450
  "src/skills/index.ts"() {
2289
2451
  init_generate();
@@ -2320,6 +2482,20 @@ var init_skills = __esm({
2320
2482
  campaignStatusSkill,
2321
2483
  filmSkill
2322
2484
  ];
2485
+ isLocalPathField = (key) => /_paths?$/.test(key);
2486
+ exports.HOSTED_SKILLS = exports.ALL_SKILLS.filter(
2487
+ (s) => s.name !== uploadSkill.name
2488
+ ).map((s) => {
2489
+ const props = s.inputJsonSchema.properties ?? {};
2490
+ return {
2491
+ ...s,
2492
+ description: s.hostedDescription ?? s.description,
2493
+ inputJsonSchema: {
2494
+ ...s.inputJsonSchema,
2495
+ properties: Object.fromEntries(Object.entries(props).filter(([k]) => !isLocalPathField(k)))
2496
+ }
2497
+ };
2498
+ });
2323
2499
  }
2324
2500
  });
2325
2501
  function makeAttributedClient(options) {
@@ -2373,7 +2549,8 @@ function makeRedactedLogger(target = console) {
2373
2549
  }
2374
2550
 
2375
2551
  // src/server.ts
2376
- var PACKAGE_VERSION = "0.12.0" ;
2552
+ var PACKAGE_VERSION = "0.13.1" ;
2553
+ 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.";
2377
2554
  function buildServer(options) {
2378
2555
  if (!options.apiKey) {
2379
2556
  throw new Error(
@@ -2391,7 +2568,8 @@ function buildServer(options) {
2391
2568
  {
2392
2569
  capabilities: {
2393
2570
  tools: {}
2394
- }
2571
+ },
2572
+ instructions: SERVER_INSTRUCTIONS
2395
2573
  }
2396
2574
  );
2397
2575
  server.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
@@ -2443,6 +2621,8 @@ function buildServer(options) {
2443
2621
  apiKey: options.apiKey,
2444
2622
  baseUrl,
2445
2623
  mcpVersion: version,
2624
+ localFiles: true,
2625
+ // stdio: the caller's own machine
2446
2626
  logger,
2447
2627
  buildStatusUrl: (jobId) => `${baseUrl}/api/v1/jobs/${encodeURIComponent(jobId)}`
2448
2628
  };
@@ -2515,8 +2695,10 @@ init_skills();
2515
2695
  init_poll();
2516
2696
 
2517
2697
  exports.PACKAGE_VERSION = PACKAGE_VERSION;
2698
+ exports.SERVER_INSTRUCTIONS = SERVER_INSTRUCTIONS;
2518
2699
  exports.buildServer = buildServer;
2519
2700
  exports.errorToActionableMessage = errorToActionableMessage;
2701
+ exports.findHostedSkill = findHostedSkill;
2520
2702
  exports.findSkill = findSkill;
2521
2703
  exports.makeAttributedClient = makeAttributedClient;
2522
2704
  exports.makeRedactedLogger = makeRedactedLogger;