@pixeldojo/mcp 0.9.0 → 0.9.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.js CHANGED
@@ -93,7 +93,7 @@ function pollResultToSkillResult(result, successText) {
93
93
  }
94
94
  return {
95
95
  kind: "handoff",
96
- text: `Job ${result.jobId} is still running after the 30s sync window. Use pixeldojo:status with this jobId to check completion, or follow ${result.statusUrl}.`,
96
+ text: `Job ${result.jobId} is still running after the 30s sync window. Use pixeldojo_status with this jobId to check completion, or follow ${result.statusUrl}.`,
97
97
  jobId: result.jobId,
98
98
  statusUrl: result.statusUrl
99
99
  };
@@ -186,6 +186,7 @@ var init_upload_helpers = __esm({
186
186
  function getFieldConstraints(apiId, client) {
187
187
  const cached = schemaCache.get(apiId);
188
188
  if (cached) return cached;
189
+ if (typeof client?.getModelSchema !== "function") return Promise.resolve(null);
189
190
  const pending = Promise.resolve().then(() => client.getModelSchema(apiId)).then(
190
191
  (res) => res.schema?.properties ?? {}
191
192
  ).catch(() => {
@@ -230,6 +231,46 @@ async function snapAspectRatio(apiId, requested, client, opts = {}) {
230
231
  if (!field.enum || field.enum.length === 0) return requested;
231
232
  return nearestAspectFromEnum(requested, field.enum);
232
233
  }
234
+ async function resolveReferenceField(apiId, client) {
235
+ const props = await getFieldConstraints(apiId, client);
236
+ if (props === null) return void 0;
237
+ for (const name of REFERENCE_FIELD_PRIORITY) {
238
+ const field = props[name];
239
+ if (field) return { name, isArray: field.type === "array" };
240
+ }
241
+ return null;
242
+ }
243
+ function resolutionHeight(value) {
244
+ const v = value.trim().toLowerCase();
245
+ if (v === "4k") return 2160;
246
+ const n = Number.parseInt(v, 10);
247
+ return Number.isFinite(n) ? n : null;
248
+ }
249
+ async function snapResolution(apiId, requested, client, opts = {}) {
250
+ if (!requested) return void 0;
251
+ const props = await getFieldConstraints(apiId, client);
252
+ if (props === null) {
253
+ return opts.onUnknown === "omit" ? void 0 : requested;
254
+ }
255
+ const field = props.resolution;
256
+ if (!field) return void 0;
257
+ if (!field.enum || field.enum.length === 0) return requested;
258
+ if (field.enum.includes(requested)) return requested;
259
+ const target = resolutionHeight(requested);
260
+ if (target === null) return requested;
261
+ let best = requested;
262
+ let bestDiff = Infinity;
263
+ for (const candidate of field.enum) {
264
+ const h = resolutionHeight(candidate);
265
+ if (h === null) continue;
266
+ const diff = Math.abs(h - target);
267
+ if (diff < bestDiff) {
268
+ bestDiff = diff;
269
+ best = candidate;
270
+ }
271
+ }
272
+ return best;
273
+ }
233
274
  async function snapDuration(apiId, requested, client, opts = {}) {
234
275
  if (requested === void 0) return void 0;
235
276
  const props = await getFieldConstraints(apiId, client);
@@ -268,10 +309,22 @@ async function resolveImageRefs(apiId, urls, client) {
268
309
  }
269
310
  return { key, value: urls[0], dropped: urls.length - 1 };
270
311
  }
271
- var schemaCache, REF_KEYS;
312
+ var schemaCache, REFERENCE_FIELD_PRIORITY, REF_KEYS;
272
313
  var init_schema_snap = __esm({
273
314
  "src/skills/schema-snap.ts"() {
274
315
  schemaCache = /* @__PURE__ */ new Map();
316
+ REFERENCE_FIELD_PRIORITY = [
317
+ "image_url",
318
+ "start_image_url",
319
+ "input_image",
320
+ "image",
321
+ "reference_images",
322
+ "reference_urls",
323
+ "reference_image_urls",
324
+ "image_urls",
325
+ "image_input",
326
+ "images"
327
+ ];
275
328
  REF_KEYS = [
276
329
  "image_urls",
277
330
  "images",
@@ -284,7 +337,7 @@ var init_schema_snap = __esm({
284
337
  ];
285
338
  }
286
339
  });
287
- function buildPayload(apiId, input) {
340
+ function buildPayload(apiId, input, refField, referenceUrls = []) {
288
341
  const body = { prompt: input.prompt };
289
342
  if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
290
343
  if (input.preset) body.preset = input.preset;
@@ -302,8 +355,16 @@ function buildPayload(apiId, input) {
302
355
  }
303
356
  break;
304
357
  default:
305
- if (input.image_url) body.image_url = input.image_url;
358
+ if (input.image_url) {
359
+ const refs = referenceUrls.length > 0 ? referenceUrls : [input.image_url];
360
+ if (refField) {
361
+ body[refField.name] = refField.isArray ? refs : refs[0];
362
+ } else {
363
+ body.image_url = refs[0];
364
+ }
365
+ }
306
366
  if (input.duration !== void 0) body.duration = input.duration;
367
+ if (input.resolution) body.resolution = input.resolution;
307
368
  break;
308
369
  }
309
370
  return body;
@@ -311,39 +372,51 @@ function buildPayload(apiId, input) {
311
372
  async function run(input, ctx) {
312
373
  const apiId = input.model ?? DEFAULT_MODEL;
313
374
  let resolved = input;
314
- if (!input.image_url && input.image_path) {
315
- try {
316
- const uploadedUrl = await uploadLocalFile(input.image_path, ctx.client);
317
- resolved = { ...input, image_url: uploadedUrl };
318
- } catch (err) {
319
- if (err instanceof SkillUploadError) {
320
- return { kind: "text", text: `pixeldojo:generate (upload): ${err.userMessage}` };
321
- }
322
- throw err;
375
+ let referenceUrls = [];
376
+ try {
377
+ const singleUrl = input.image_url ?? (input.image_path ? await uploadLocalFile(input.image_path, ctx.client) : void 0);
378
+ const manyUrls = [...input.image_urls ?? []];
379
+ for (const p of input.image_paths ?? []) {
380
+ manyUrls.push(await uploadLocalFile(p, ctx.client));
381
+ }
382
+ referenceUrls = [...singleUrl ? [singleUrl] : [], ...manyUrls].filter(
383
+ (u, i, all) => all.indexOf(u) === i
384
+ );
385
+ if (referenceUrls[0] && referenceUrls[0] !== input.image_url) {
386
+ resolved = { ...input, image_url: referenceUrls[0] };
323
387
  }
388
+ } catch (err) {
389
+ if (err instanceof SkillUploadError) {
390
+ return { kind: "text", text: `pixeldojo_generate (upload): ${err.userMessage}` };
391
+ }
392
+ throw err;
324
393
  }
325
394
  if (apiId === "grok-video-extend") {
326
395
  if (!resolved.image_url) {
327
396
  return {
328
397
  kind: "text",
329
- text: 'pixeldojo:generate with model="grok-video-extend" requires image_url (the source video URL to extend, or image_path for a local file).'
398
+ text: 'pixeldojo_generate with model="grok-video-extend" requires image_url (the source video URL to extend, or image_path for a local file).'
330
399
  };
331
400
  }
332
401
  if (resolved.duration === void 0) {
333
402
  return {
334
403
  kind: "text",
335
- text: 'pixeldojo:generate with model="grok-video-extend" requires duration (extension length in seconds).'
404
+ text: 'pixeldojo_generate with model="grok-video-extend" requires duration (extension length in seconds).'
336
405
  };
337
406
  }
338
407
  }
408
+ let refField;
339
409
  if (!VIDEO_INPUT_MODELS.has(apiId)) {
340
- const [aspect_ratio, duration] = await Promise.all([
410
+ const [aspect_ratio, duration, resolution, ref] = await Promise.all([
341
411
  snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client),
342
- snapDuration(apiId, resolved.duration, ctx.client)
412
+ snapDuration(apiId, resolved.duration, ctx.client),
413
+ snapResolution(apiId, resolved.resolution, ctx.client),
414
+ resolved.image_url ? resolveReferenceField(apiId, ctx.client) : Promise.resolve(void 0)
343
415
  ]);
344
- resolved = { ...resolved, aspect_ratio, duration };
416
+ resolved = { ...resolved, aspect_ratio, duration, resolution };
417
+ refField = ref;
345
418
  }
346
- const body = buildPayload(apiId, resolved);
419
+ const body = buildPayload(apiId, resolved, refField, referenceUrls);
347
420
  const submitted = await ctx.client.run(apiId, body);
348
421
  const result = await pollJobUntilDoneOrHandoff(
349
422
  submitted.jobId,
@@ -376,9 +449,18 @@ var init_generate = __esm({
376
449
  ),
377
450
  aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
378
451
  duration: z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
452
+ resolution: z.string().optional().describe(
453
+ 'Output resolution when the model supports it (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits. Omit to use the model default.'
454
+ ),
379
455
  image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
380
456
  image_path: z.string().min(1).optional().describe(
381
457
  "Absolute local file path to a reference image/video. The skill uploads it to PixelDojo first (24h temp storage) and then dispatches. Prefer this over image_url when the user has the file on disk."
458
+ ),
459
+ image_urls: z.array(z.string().url()).optional().describe(
460
+ "Multiple reference image URLs, for models that accept a reference set (character + wardrobe + location, for example). Models that take only one reference use the first and the rest are ignored."
461
+ ),
462
+ image_paths: z.array(z.string().min(1)).optional().describe(
463
+ "Multiple absolute local file paths. Each is uploaded to PixelDojo (24h temp storage) and used as a reference, same rules as image_urls."
382
464
  )
383
465
  });
384
466
  DEFAULT_MODEL = "google-nano-banana";
@@ -397,7 +479,21 @@ var init_generate = __esm({
397
479
  },
398
480
  aspect_ratio: { type: "string" },
399
481
  duration: { type: "integer" },
482
+ resolution: {
483
+ type: "string",
484
+ description: 'Output resolution when supported (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits.'
485
+ },
400
486
  image_url: { type: "string", format: "uri" },
487
+ image_urls: {
488
+ type: "array",
489
+ items: { type: "string", format: "uri" },
490
+ description: "Multiple reference image URLs for models that accept a reference set. Single-reference models use the first."
491
+ },
492
+ image_paths: {
493
+ type: "array",
494
+ items: { type: "string" },
495
+ description: "Multiple absolute local file paths, each uploaded to 24h temp storage and used as a reference."
496
+ },
401
497
  image_path: {
402
498
  type: "string",
403
499
  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."
@@ -406,7 +502,7 @@ var init_generate = __esm({
406
502
  required: ["prompt"]
407
503
  };
408
504
  generateSkill = {
409
- name: "pixeldojo:generate",
505
+ name: "pixeldojo_generate",
410
506
  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-image / image-to-video flows: pass `image_url` if you already have a URL, or `image_path` (absolute local path) and the skill uploads it to 24h temp storage automatically.",
411
507
  inputZod: InputSchema,
412
508
  inputJsonSchema: InputJsonSchema,
@@ -491,14 +587,14 @@ async function run2(input, ctx) {
491
587
  }
492
588
  } catch (err) {
493
589
  if (err instanceof SkillUploadError) {
494
- return { kind: "text", text: `pixeldojo:edit (upload): ${err.userMessage}` };
590
+ return { kind: "text", text: `pixeldojo_edit (upload): ${err.userMessage}` };
495
591
  }
496
592
  throw err;
497
593
  }
498
594
  if (!resolved.image_url && (!resolved.image_urls || resolved.image_urls.length === 0)) {
499
595
  return {
500
596
  kind: "text",
501
- text: "pixeldojo:edit requires at least one reference image. Pass image_url / image_urls (hosted URL) or image_path / image_paths (local file)."
597
+ text: "pixeldojo_edit requires at least one reference image. Pass image_url / image_urls (hosted URL) or image_path / image_paths (local file)."
502
598
  };
503
599
  }
504
600
  const apiId = resolved.model ?? DEFAULT_MODEL2;
@@ -592,7 +688,7 @@ var init_edit = __esm({
592
688
  required: ["prompt"]
593
689
  };
594
690
  editSkill = {
595
- name: "pixeldojo:edit",
691
+ name: "pixeldojo_edit",
596
692
  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).",
597
693
  inputZod: InputSchema2,
598
694
  inputJsonSchema: InputJsonSchema2,
@@ -607,14 +703,14 @@ async function run3(input, ctx) {
607
703
  if (!ALLOWED.includes(requested)) {
608
704
  return {
609
705
  kind: "text",
610
- text: `Unknown model "${requested}" for pixeldojo:character. Allowed: ${ALLOWED.join(", ")}.`
706
+ text: `Unknown model "${requested}" for pixeldojo_character. Allowed: ${ALLOWED.join(", ")}.`
611
707
  };
612
708
  }
613
709
  const apiId = requested;
614
710
  if (REQUIRES_REFERENCE.has(apiId) && !input.reference_image_url) {
615
711
  return {
616
712
  kind: "text",
617
- text: `pixeldojo:character with model="${apiId}" requires reference_image_url (the source image of the character to edit). For generation from a prompt alone, use model="${DEFAULT_MODEL3}".`
713
+ text: `pixeldojo_character with model="${apiId}" requires reference_image_url (the source image of the character to edit). For generation from a prompt alone, use model="${DEFAULT_MODEL3}".`
618
714
  };
619
715
  }
620
716
  const body = { prompt: input.prompt };
@@ -676,7 +772,7 @@ var init_character = __esm({
676
772
  required: ["prompt"]
677
773
  };
678
774
  characterSkill = {
679
- name: "pixeldojo:character",
775
+ name: "pixeldojo_character",
680
776
  description: "Generate or edit consistent characters across shots. Pass a reference_image_url for identity locking (seedream-5 by default).",
681
777
  inputZod: InputSchema3,
682
778
  inputJsonSchema: InputJsonSchema3,
@@ -691,7 +787,7 @@ async function run4(input, ctx) {
691
787
  if (!ALLOWED2.includes(requested)) {
692
788
  return {
693
789
  kind: "text",
694
- text: `Unknown model "${requested}" for pixeldojo:storyboard. Allowed: ${ALLOWED2.join(", ")}.`
790
+ text: `Unknown model "${requested}" for pixeldojo_storyboard. Allowed: ${ALLOWED2.join(", ")}.`
695
791
  };
696
792
  }
697
793
  const apiId = requested;
@@ -754,7 +850,7 @@ var init_storyboard = __esm({
754
850
  required: ["brief"]
755
851
  };
756
852
  storyboardSkill = {
757
- name: "pixeldojo:storyboard",
853
+ name: "pixeldojo_storyboard",
758
854
  description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
759
855
  inputZod: InputSchema4,
760
856
  inputJsonSchema: InputJsonSchema4,
@@ -769,7 +865,7 @@ async function run5(input, ctx) {
769
865
  if (!ALLOWED3.includes(requested)) {
770
866
  return {
771
867
  kind: "text",
772
- text: `Unknown model "${requested}" for pixeldojo:upscale. Allowed: ${ALLOWED3.join(", ")}.`
868
+ text: `Unknown model "${requested}" for pixeldojo_upscale. Allowed: ${ALLOWED3.join(", ")}.`
773
869
  };
774
870
  }
775
871
  const apiId = requested;
@@ -831,7 +927,7 @@ var init_upscale = __esm({
831
927
  required: ["image_url"]
832
928
  };
833
929
  upscaleSkill = {
834
- name: "pixeldojo:upscale",
930
+ name: "pixeldojo_upscale",
835
931
  description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
836
932
  inputZod: InputSchema5,
837
933
  inputJsonSchema: InputJsonSchema5,
@@ -909,14 +1005,14 @@ var InputSchema6, InputJsonSchema6, STUCK_HINT_MS, ABANDON_HINT_MS, statusSkill;
909
1005
  var init_status = __esm({
910
1006
  "src/skills/status.ts"() {
911
1007
  InputSchema6 = z.object({
912
- jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
1008
+ jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo_* skill handoff.")
913
1009
  });
914
1010
  InputJsonSchema6 = {
915
1011
  type: "object",
916
1012
  properties: {
917
1013
  jobId: {
918
1014
  type: "string",
919
- description: "Job id returned from a prior pixeldojo:* skill handoff."
1015
+ description: "Job id returned from a prior pixeldojo_* skill handoff."
920
1016
  }
921
1017
  },
922
1018
  required: ["jobId"]
@@ -924,7 +1020,7 @@ var init_status = __esm({
924
1020
  STUCK_HINT_MS = 5 * 60 * 1e3;
925
1021
  ABANDON_HINT_MS = 12 * 60 * 1e3;
926
1022
  statusSkill = {
927
- name: "pixeldojo:status",
1023
+ name: "pixeldojo_status",
928
1024
  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.",
929
1025
  inputZod: InputSchema6,
930
1026
  inputJsonSchema: InputJsonSchema6,
@@ -969,8 +1065,8 @@ var init_from_url = __esm({
969
1065
  required: ["url"]
970
1066
  };
971
1067
  fromUrlSkill = {
972
- name: "pixeldojo:from_url",
973
- 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.",
1068
+ name: "pixeldojo_from_url",
1069
+ 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.",
974
1070
  inputZod: InputSchema7,
975
1071
  inputJsonSchema: InputJsonSchema7,
976
1072
  // No model dispatch — this skill calls a server-side scrape, not a generation.
@@ -1031,7 +1127,7 @@ async function plan(input, ctx) {
1031
1127
  ...sceneLines,
1032
1128
  "",
1033
1129
  `Nothing has been charged yet. Producing renders every shot (roughly 20-25 credits per shot at default engines, charged as each renders) plus a few credits for the soundtrack.`,
1034
- `Review or edit at ${ctx.baseUrl}/film-studio/${film.id} \u2014 or call pixeldojo:film with {"film_id": "${film.id}", "action": "produce"} to start production.`
1130
+ `Review or edit at ${ctx.baseUrl}/film-studio/${film.id} \u2014 or call pixeldojo_film with {"film_id": "${film.id}", "action": "produce"} to start production.`
1035
1131
  ].join("\n")
1036
1132
  };
1037
1133
  }
@@ -1046,7 +1142,7 @@ async function produce(filmId, ctx) {
1046
1142
  }
1047
1143
  return {
1048
1144
  kind: "text",
1049
- text: `Production started on "${data.film.title}": ${fired} frame(s) dispatched (each is a normal frame charge; the server's Frame check reviews every one). Poll pixeldojo:film with {"film_id": "${filmId}", "action": "status"} every ~30 seconds \u2014 each poll advances the pipeline.`
1145
+ text: `Production started on "${data.film.title}": ${fired} frame(s) dispatched (each is a normal frame charge; the server's Frame check reviews every one). Poll pixeldojo_film with {"film_id": "${filmId}", "action": "status"} every ~30 seconds \u2014 each poll advances the pipeline.`
1050
1146
  };
1051
1147
  }
1052
1148
  function buildScorePlanBody(film) {
@@ -1175,13 +1271,13 @@ async function run8(input, ctx) {
1175
1271
  var LOOKS, InputSchema8, InputJsonSchema8, shots, count, LOOK_MUSIC, ARC, filmSkill;
1176
1272
  var init_film = __esm({
1177
1273
  "src/skills/film.ts"() {
1178
- LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western"];
1274
+ LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
1179
1275
  InputSchema8 = z.object({
1180
1276
  idea: z.string().max(2e3).optional().describe("One-to-three sentence film idea. The studio writes the screenplay (free). Provide idea OR script for planning."),
1181
1277
  script: z.string().max(2e4).optional().describe("A full screenplay to plan instead of an idea (sluglines, action lines, dialogue)."),
1182
1278
  look: z.enum(LOOKS).default("live-action").describe("Visual style carried through frames, clips, and soundtrack."),
1183
1279
  aspect: z.enum(["16:9", "9:16"]).default("16:9"),
1184
- length: z.enum(["30s", "60s", "2-3min"]).default("60s").describe("Target length; drives the shot count (~6/12/24 shots)."),
1280
+ length: z.enum(["30s", "60s", "2-3min", "5min"]).default("60s").describe("Target length; drives the shot count (~6/12/24/60 shots)."),
1185
1281
  film_id: z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status."),
1186
1282
  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."),
1187
1283
  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."),
@@ -1194,7 +1290,7 @@ var init_film = __esm({
1194
1290
  script: { type: "string", description: "A full screenplay to plan instead of an idea." },
1195
1291
  look: { type: "string", enum: [...LOOKS], description: "Visual style. Default live-action." },
1196
1292
  aspect: { type: "string", enum: ["16:9", "9:16"], description: "Default 16:9." },
1197
- length: { type: "string", enum: ["30s", "60s", "2-3min"], description: "Target length (~6/12/24 shots). Default 60s." },
1293
+ length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Target length (~6/12/24/60 shots). Default 60s." },
1198
1294
  film_id: { type: "string", description: "Existing film id for produce/status." },
1199
1295
  action: { type: "string", enum: ["plan", "produce", "status"], description: "plan (free) | produce (charged) | status (poll every ~30s)." },
1200
1296
  soundtrack: { type: "string", enum: ["scene-synced", "flat", "none"], description: "Default scene-synced." },
@@ -1211,7 +1307,10 @@ var init_film = __esm({
1211
1307
  horror: "sparse horror underscore, low cello drones, dissonant string swells, creeping heartbeat pulse, unsettling quiet",
1212
1308
  "sci-fi": "atmospheric science-fiction score, analog synth pads, deep sub-bass pulses, shimmering arpeggios, vast and cold",
1213
1309
  documentary: "understated documentary score, felt piano, soft string beds, organic percussion textures, reflective and true",
1214
- western: "cinematic western score, lonely twangy guitar, dusty strings, distant horns, wide-open desert tension"
1310
+ western: "cinematic western score, lonely twangy guitar, dusty strings, distant horns, wide-open desert tension",
1311
+ romance: "tender romantic score, felt piano and warm strings, soft acoustic guitar, intimate and unhurried, gently swelling",
1312
+ "romantic-comedy": "bright romantic-comedy score, plucky ukulele and handclaps, warm brushed drums, sunny and flirtatious",
1313
+ comedy: "playful comedy score, bouncy pizzicato strings, cheeky woodwinds, light percussion hits punctuating the gags"
1215
1314
  };
1216
1315
  ARC = {
1217
1316
  opening: ["establishing the theme", "restrained", "quiet anticipation"],
@@ -1219,7 +1318,7 @@ var init_film = __esm({
1219
1318
  finale: ["climactic", "full arrangement", "emotional peak resolving to stillness"]
1220
1319
  };
1221
1320
  filmSkill = {
1222
- name: "pixeldojo:film",
1321
+ name: "pixeldojo_film",
1223
1322
  description: "Make a finished short film from one idea: screenplay, storyboard frames (AI-reviewed), video clips with spoken dialogue, scene-synced soundtrack, stitched export. Phased: plan is free and returns a cost estimate; produce starts charged generation; status advances production (poll every ~30s).",
1224
1323
  inputZod: InputSchema8,
1225
1324
  inputJsonSchema: InputJsonSchema8,
@@ -1358,8 +1457,8 @@ var init_ad = __esm({
1358
1457
  // Zod refine; no root-level oneOf (Grok rejects non-object root unions).
1359
1458
  };
1360
1459
  adSkill = {
1361
- name: "pixeldojo:ad",
1362
- 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.",
1460
+ name: "pixeldojo_ad",
1461
+ 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.",
1363
1462
  inputZod: InputSchema9,
1364
1463
  inputJsonSchema: InputJsonSchema9,
1365
1464
  // Composition skill — the server routes to the ad engine internally.
@@ -1391,7 +1490,7 @@ async function run10(input, ctx) {
1391
1490
  }
1392
1491
  return {
1393
1492
  kind: "handoff",
1394
- text: result.message ?? `Campaign ${result.campaignId} queued. Poll via pixeldojo:campaign_status.`,
1493
+ text: result.message ?? `Campaign ${result.campaignId} queued. Poll via pixeldojo_campaign_status.`,
1395
1494
  jobId: result.campaignId,
1396
1495
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
1397
1496
  };
@@ -1444,8 +1543,8 @@ var init_campaign = __esm({
1444
1543
  oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
1445
1544
  };
1446
1545
  campaignSkill = {
1447
- name: "pixeldojo:campaign",
1448
- description: "Generate a coherent product launch package (hero image + N lifestyle variants + optional vertical video) from one URL or product profile. Composes hero / lifestyle / video models server-side; returns assets in one call or a handoff with campaignId to poll via pixeldojo:campaign_status.",
1546
+ name: "pixeldojo_campaign",
1547
+ description: "Generate a coherent product launch package (hero image + N lifestyle variants + optional vertical video) from one URL or product profile. Composes hero / lifestyle / video models server-side; returns assets in one call or a handoff with campaignId to poll via pixeldojo_campaign_status.",
1449
1548
  inputZod: InputSchema10,
1450
1549
  inputJsonSchema: InputJsonSchema10,
1451
1550
  // Composition skill — internal sub-jobs route to per-tool models, no
@@ -1490,21 +1589,21 @@ var InputSchema11, InputJsonSchema11, campaignStatusSkill;
1490
1589
  var init_campaign_status = __esm({
1491
1590
  "src/skills/campaign-status.ts"() {
1492
1591
  InputSchema11 = z.object({
1493
- campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
1592
+ campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo_campaign handoff.")
1494
1593
  });
1495
1594
  InputJsonSchema11 = {
1496
1595
  type: "object",
1497
1596
  properties: {
1498
1597
  campaignId: {
1499
1598
  type: "string",
1500
- description: "Campaign id returned from a prior pixeldojo:campaign handoff."
1599
+ description: "Campaign id returned from a prior pixeldojo_campaign handoff."
1501
1600
  }
1502
1601
  },
1503
1602
  required: ["campaignId"]
1504
1603
  };
1505
1604
  campaignStatusSkill = {
1506
- name: "pixeldojo:campaign_status",
1507
- description: "Poll a campaign by campaignId returned from pixeldojo:campaign. Returns assets when every sub-job is done, or a handoff describing what is still in flight.",
1605
+ name: "pixeldojo_campaign_status",
1606
+ description: "Poll a campaign by campaignId returned from pixeldojo_campaign. Returns assets when every sub-job is done, or a handoff describing what is still in flight.",
1508
1607
  inputZod: InputSchema11,
1509
1608
  inputJsonSchema: InputJsonSchema11,
1510
1609
  allowedModels: [],
@@ -1519,7 +1618,7 @@ async function run12(input, ctx) {
1519
1618
  url = await uploadLocalFile(input.path, ctx.client);
1520
1619
  } catch (err) {
1521
1620
  if (err instanceof SkillUploadError) {
1522
- return { kind: "text", text: `pixeldojo:upload: ${err.userMessage}` };
1621
+ return { kind: "text", text: `pixeldojo_upload: ${err.userMessage}` };
1523
1622
  }
1524
1623
  throw err;
1525
1624
  }
@@ -1549,7 +1648,7 @@ var init_upload = __esm({
1549
1648
  required: ["path"]
1550
1649
  };
1551
1650
  uploadSkill = {
1552
- name: "pixeldojo:upload",
1651
+ name: "pixeldojo_upload",
1553
1652
  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.",
1554
1653
  inputZod: InputSchema12,
1555
1654
  inputJsonSchema: InputJsonSchema12,
@@ -1564,7 +1663,7 @@ async function run13(input, ctx) {
1564
1663
  if (!ALLOWED4.includes(requested)) {
1565
1664
  return {
1566
1665
  kind: "text",
1567
- text: `Unknown model "${requested}" for pixeldojo:audio. Allowed: ${ALLOWED4.join(", ")}.`
1666
+ text: `Unknown model "${requested}" for pixeldojo_audio. Allowed: ${ALLOWED4.join(", ")}.`
1568
1667
  };
1569
1668
  }
1570
1669
  const apiId = requested;
@@ -1575,7 +1674,7 @@ async function run13(input, ctx) {
1575
1674
  audioUrls = [...audioUrls, ...uploaded].slice(0, 3);
1576
1675
  } catch (err) {
1577
1676
  if (err instanceof SkillUploadError) {
1578
- return { kind: "text", text: `pixeldojo:audio (upload): ${err.userMessage}` };
1677
+ return { kind: "text", text: `pixeldojo_audio (upload): ${err.userMessage}` };
1579
1678
  }
1580
1679
  throw err;
1581
1680
  }
@@ -1641,7 +1740,7 @@ var init_audio = __esm({
1641
1740
  required: ["prompt"]
1642
1741
  };
1643
1742
  audioSkill = {
1644
- name: "pixeldojo:audio",
1743
+ name: "pixeldojo_audio",
1645
1744
  description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain TTS available).",
1646
1745
  inputZod: InputSchema13,
1647
1746
  inputJsonSchema: InputJsonSchema13,
@@ -1661,7 +1760,7 @@ async function run14(input, ctx) {
1661
1760
  if (res.items.length === 0) {
1662
1761
  return {
1663
1762
  kind: "text",
1664
- text: input.q ? `No library items match "${input.q}". Try a broader query, or omit q to list recent generations.` : "Your library is empty. Generate something with pixeldojo:generate first."
1763
+ text: input.q ? `No library items match "${input.q}". Try a broader query, or omit q to list recent generations.` : "Your library is empty. Generate something with pixeldojo_generate first."
1665
1764
  };
1666
1765
  }
1667
1766
  const lines = res.items.map((it) => {
@@ -1702,8 +1801,8 @@ var init_library = __esm({
1702
1801
  required: []
1703
1802
  };
1704
1803
  librarySkill = {
1705
- name: "pixeldojo:library",
1706
- description: "Search your media library: saved My Media items (permanent) and recent API/MCP generations (expire after 24h), newest first. Returns asset URLs to reuse as reference inputs. Keep a result forever with pixeldojo:save.",
1804
+ name: "pixeldojo_library",
1805
+ description: "Search your media library: saved My Media items (permanent) and recent API/MCP generations (expire after 24h), newest first. Returns asset URLs to reuse as reference inputs. Keep a result forever with pixeldojo_save.",
1707
1806
  inputZod: InputSchema14,
1708
1807
  inputJsonSchema: InputJsonSchema14,
1709
1808
  allowedModels: [],
@@ -1723,7 +1822,7 @@ async function run15(input, ctx) {
1723
1822
  return {
1724
1823
  kind: "text",
1725
1824
  text: `Saved to My Media (permanent): ${res.url}
1726
- It will appear at https://pixeldojo.ai/my-images and in pixeldojo:library searches.`
1825
+ It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
1727
1826
  };
1728
1827
  }
1729
1828
  var InputSchema15, InputJsonSchema15, saveSkill;
@@ -1748,7 +1847,7 @@ var init_save = __esm({
1748
1847
  required: ["url"]
1749
1848
  };
1750
1849
  saveSkill = {
1751
- name: "pixeldojo:save",
1850
+ name: "pixeldojo_save",
1752
1851
  description: "Save a generated image or video into the user's My Media library permanently. Generation outputs otherwise expire after 24 hours \u2014 call this when the user wants to keep a result.",
1753
1852
  inputZod: InputSchema15,
1754
1853
  inputJsonSchema: InputJsonSchema15,
@@ -1796,7 +1895,7 @@ async function runSave(input, ctx) {
1796
1895
  });
1797
1896
  return {
1798
1897
  kind: "text",
1799
- text: res.created ? `Saved workflow "${res.name}" (${res.steps} step${res.steps === 1 ? "" : "s"}). Run it with pixeldojo:run_workflow { name: "${res.name}" }.` : `Overwrote workflow "${res.name}" (${res.steps} step${res.steps === 1 ? "" : "s"}).`
1898
+ text: res.created ? `Saved workflow "${res.name}" (${res.steps} step${res.steps === 1 ? "" : "s"}). Run it with pixeldojo_run_workflow { name: "${res.name}" }.` : `Overwrote workflow "${res.name}" (${res.steps} step${res.steps === 1 ? "" : "s"}).`
1800
1899
  };
1801
1900
  }
1802
1901
  async function runList(_input, ctx) {
@@ -1804,7 +1903,7 @@ async function runList(_input, ctx) {
1804
1903
  if (res.count === 0) {
1805
1904
  return {
1806
1905
  kind: "text",
1807
- text: "No saved workflows yet. Save one with pixeldojo:save_workflow { name, steps: [{ skill, args }] }."
1906
+ text: "No saved workflows yet. Save one with pixeldojo_save_workflow { name, steps: [{ skill, args }] }."
1808
1907
  };
1809
1908
  }
1810
1909
  const lines = res.workflows.map(
@@ -1873,7 +1972,7 @@ async function runWorkflow(input, ctx) {
1873
1972
  } else if (result.kind === "handoff") {
1874
1973
  return {
1875
1974
  kind: "text",
1876
- text: `Workflow "${workflow.name}" is still on step ${i + 1} (${step.skill}) after an extended wait. Poll with pixeldojo:status { jobId: "${result.jobId}" }, then re-run the workflow once it completes.`
1975
+ text: `Workflow "${workflow.name}" is still on step ${i + 1} (${step.skill}) after an extended wait. Poll with pixeldojo_status { jobId: "${result.jobId}" }, then re-run the workflow once it completes.`
1877
1976
  };
1878
1977
  } else {
1879
1978
  outputs.push(`step ${i + 1} (${step.skill}): done (no asset URL)`);
@@ -1892,7 +1991,7 @@ var init_workflows = __esm({
1892
1991
  init_types();
1893
1992
  WORKFLOW_STEP_EXTRA_BUDGET_MS = 6e4;
1894
1993
  StepSchema = z.object({
1895
- skill: z.string().regex(/^pixeldojo:(generate|edit|character|storyboard|upscale|audio|upload|from_url)$/, {
1994
+ skill: z.string().regex(/^pixeldojo[:_](generate|edit|character|storyboard|upscale|audio|upload|from_url)$/, {
1896
1995
  message: "Runnable skills only: generate, edit, character, storyboard, upscale, audio, upload, from_url."
1897
1996
  }),
1898
1997
  args: z.record(z.string(), z.unknown()).optional()
@@ -1918,7 +2017,7 @@ var init_workflows = __esm({
1918
2017
  required: ["skill"]
1919
2018
  };
1920
2019
  saveWorkflowSkill = {
1921
- name: "pixeldojo:save_workflow",
2020
+ name: "pixeldojo_save_workflow",
1922
2021
  description: "Save a named multi-step skill chain (generate \u2192 edit \u2192 upscale \u2026). String args may contain {{input.<key>}} and {{prev}} placeholders. Saving to an existing name overwrites it.",
1923
2022
  inputZod: SaveInputSchema,
1924
2023
  inputJsonSchema: {
@@ -1935,7 +2034,7 @@ var init_workflows = __esm({
1935
2034
  run: runSave
1936
2035
  };
1937
2036
  runWorkflowSkill = {
1938
- name: "pixeldojo:run_workflow",
2037
+ name: "pixeldojo_run_workflow",
1939
2038
  description: "Run a saved workflow by name. Pass inputs for its {{input.<key>}} placeholders; each step feeds the next via {{prev}}.",
1940
2039
  inputZod: RunInputSchema,
1941
2040
  inputJsonSchema: {
@@ -1951,7 +2050,7 @@ var init_workflows = __esm({
1951
2050
  run: runWorkflow
1952
2051
  };
1953
2052
  listWorkflowsSkill = {
1954
- name: "pixeldojo:list_workflows",
2053
+ name: "pixeldojo_list_workflows",
1955
2054
  description: "List your saved workflows with step counts and run totals.",
1956
2055
  inputZod: ListInputSchema,
1957
2056
  inputJsonSchema: { type: "object", properties: {}, required: [] },
@@ -1987,7 +2086,8 @@ __export(skills_exports, {
1987
2086
  upscaleSkill: () => upscaleSkill
1988
2087
  });
1989
2088
  function findSkill(name) {
1990
- return ALL_SKILLS.find((s) => s.name === name);
2089
+ const wanted = name.replace(/^pixeldojo:/, "pixeldojo_");
2090
+ return ALL_SKILLS.find((s) => s.name === wanted);
1991
2091
  }
1992
2092
  var ALL_SKILLS;
1993
2093
  var init_skills = __esm({
@@ -2081,7 +2181,7 @@ function makeRedactedLogger(target = console) {
2081
2181
  }
2082
2182
 
2083
2183
  // src/server.ts
2084
- var PACKAGE_VERSION = "0.9.0" ;
2184
+ var PACKAGE_VERSION = "0.9.1" ;
2085
2185
  function buildServer(options) {
2086
2186
  if (!options.apiKey) {
2087
2187
  throw new Error(