@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/cli.cjs CHANGED
@@ -95,7 +95,7 @@ function pollResultToSkillResult(result, successText) {
95
95
  }
96
96
  return {
97
97
  kind: "handoff",
98
- 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}.`,
98
+ 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}.`,
99
99
  jobId: result.jobId,
100
100
  statusUrl: result.statusUrl
101
101
  };
@@ -188,6 +188,7 @@ var init_upload_helpers = __esm({
188
188
  function getFieldConstraints(apiId, client) {
189
189
  const cached = schemaCache.get(apiId);
190
190
  if (cached) return cached;
191
+ if (typeof client?.getModelSchema !== "function") return Promise.resolve(null);
191
192
  const pending = Promise.resolve().then(() => client.getModelSchema(apiId)).then(
192
193
  (res) => res.schema?.properties ?? {}
193
194
  ).catch(() => {
@@ -232,6 +233,46 @@ async function snapAspectRatio(apiId, requested, client, opts = {}) {
232
233
  if (!field.enum || field.enum.length === 0) return requested;
233
234
  return nearestAspectFromEnum(requested, field.enum);
234
235
  }
236
+ async function resolveReferenceField(apiId, client) {
237
+ const props = await getFieldConstraints(apiId, client);
238
+ if (props === null) return void 0;
239
+ for (const name of REFERENCE_FIELD_PRIORITY) {
240
+ const field = props[name];
241
+ if (field) return { name, isArray: field.type === "array" };
242
+ }
243
+ return null;
244
+ }
245
+ function resolutionHeight(value) {
246
+ const v = value.trim().toLowerCase();
247
+ if (v === "4k") return 2160;
248
+ const n = Number.parseInt(v, 10);
249
+ return Number.isFinite(n) ? n : null;
250
+ }
251
+ async function snapResolution(apiId, requested, client, opts = {}) {
252
+ if (!requested) return void 0;
253
+ const props = await getFieldConstraints(apiId, client);
254
+ if (props === null) {
255
+ return opts.onUnknown === "omit" ? void 0 : requested;
256
+ }
257
+ const field = props.resolution;
258
+ if (!field) return void 0;
259
+ if (!field.enum || field.enum.length === 0) return requested;
260
+ if (field.enum.includes(requested)) return requested;
261
+ const target = resolutionHeight(requested);
262
+ if (target === null) return requested;
263
+ let best = requested;
264
+ let bestDiff = Infinity;
265
+ for (const candidate of field.enum) {
266
+ const h = resolutionHeight(candidate);
267
+ if (h === null) continue;
268
+ const diff = Math.abs(h - target);
269
+ if (diff < bestDiff) {
270
+ bestDiff = diff;
271
+ best = candidate;
272
+ }
273
+ }
274
+ return best;
275
+ }
235
276
  async function snapDuration(apiId, requested, client, opts = {}) {
236
277
  if (requested === void 0) return void 0;
237
278
  const props = await getFieldConstraints(apiId, client);
@@ -270,10 +311,22 @@ async function resolveImageRefs(apiId, urls, client) {
270
311
  }
271
312
  return { key, value: urls[0], dropped: urls.length - 1 };
272
313
  }
273
- var schemaCache, REF_KEYS;
314
+ var schemaCache, REFERENCE_FIELD_PRIORITY, REF_KEYS;
274
315
  var init_schema_snap = __esm({
275
316
  "src/skills/schema-snap.ts"() {
276
317
  schemaCache = /* @__PURE__ */ new Map();
318
+ REFERENCE_FIELD_PRIORITY = [
319
+ "image_url",
320
+ "start_image_url",
321
+ "input_image",
322
+ "image",
323
+ "reference_images",
324
+ "reference_urls",
325
+ "reference_image_urls",
326
+ "image_urls",
327
+ "image_input",
328
+ "images"
329
+ ];
277
330
  REF_KEYS = [
278
331
  "image_urls",
279
332
  "images",
@@ -286,7 +339,7 @@ var init_schema_snap = __esm({
286
339
  ];
287
340
  }
288
341
  });
289
- function buildPayload(apiId, input) {
342
+ function buildPayload(apiId, input, refField, referenceUrls = []) {
290
343
  const body = { prompt: input.prompt };
291
344
  if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
292
345
  if (input.preset) body.preset = input.preset;
@@ -304,8 +357,16 @@ function buildPayload(apiId, input) {
304
357
  }
305
358
  break;
306
359
  default:
307
- if (input.image_url) body.image_url = input.image_url;
360
+ if (input.image_url) {
361
+ const refs = referenceUrls.length > 0 ? referenceUrls : [input.image_url];
362
+ if (refField) {
363
+ body[refField.name] = refField.isArray ? refs : refs[0];
364
+ } else {
365
+ body.image_url = refs[0];
366
+ }
367
+ }
308
368
  if (input.duration !== void 0) body.duration = input.duration;
369
+ if (input.resolution) body.resolution = input.resolution;
309
370
  break;
310
371
  }
311
372
  return body;
@@ -313,39 +374,51 @@ function buildPayload(apiId, input) {
313
374
  async function run(input, ctx) {
314
375
  const apiId = input.model ?? DEFAULT_MODEL;
315
376
  let resolved = input;
316
- if (!input.image_url && input.image_path) {
317
- try {
318
- const uploadedUrl = await uploadLocalFile(input.image_path, ctx.client);
319
- resolved = { ...input, image_url: uploadedUrl };
320
- } catch (err) {
321
- if (err instanceof SkillUploadError) {
322
- return { kind: "text", text: `pixeldojo:generate (upload): ${err.userMessage}` };
323
- }
324
- throw err;
377
+ let referenceUrls = [];
378
+ try {
379
+ const singleUrl = input.image_url ?? (input.image_path ? await uploadLocalFile(input.image_path, ctx.client) : void 0);
380
+ const manyUrls = [...input.image_urls ?? []];
381
+ for (const p of input.image_paths ?? []) {
382
+ manyUrls.push(await uploadLocalFile(p, ctx.client));
383
+ }
384
+ referenceUrls = [...singleUrl ? [singleUrl] : [], ...manyUrls].filter(
385
+ (u, i, all) => all.indexOf(u) === i
386
+ );
387
+ if (referenceUrls[0] && referenceUrls[0] !== input.image_url) {
388
+ resolved = { ...input, image_url: referenceUrls[0] };
325
389
  }
390
+ } catch (err) {
391
+ if (err instanceof SkillUploadError) {
392
+ return { kind: "text", text: `pixeldojo_generate (upload): ${err.userMessage}` };
393
+ }
394
+ throw err;
326
395
  }
327
396
  if (apiId === "grok-video-extend") {
328
397
  if (!resolved.image_url) {
329
398
  return {
330
399
  kind: "text",
331
- text: 'pixeldojo:generate with model="grok-video-extend" requires image_url (the source video URL to extend, or image_path for a local file).'
400
+ text: 'pixeldojo_generate with model="grok-video-extend" requires image_url (the source video URL to extend, or image_path for a local file).'
332
401
  };
333
402
  }
334
403
  if (resolved.duration === void 0) {
335
404
  return {
336
405
  kind: "text",
337
- text: 'pixeldojo:generate with model="grok-video-extend" requires duration (extension length in seconds).'
406
+ text: 'pixeldojo_generate with model="grok-video-extend" requires duration (extension length in seconds).'
338
407
  };
339
408
  }
340
409
  }
410
+ let refField;
341
411
  if (!VIDEO_INPUT_MODELS.has(apiId)) {
342
- const [aspect_ratio, duration] = await Promise.all([
412
+ const [aspect_ratio, duration, resolution, ref] = await Promise.all([
343
413
  snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client),
344
- snapDuration(apiId, resolved.duration, ctx.client)
414
+ snapDuration(apiId, resolved.duration, ctx.client),
415
+ snapResolution(apiId, resolved.resolution, ctx.client),
416
+ resolved.image_url ? resolveReferenceField(apiId, ctx.client) : Promise.resolve(void 0)
345
417
  ]);
346
- resolved = { ...resolved, aspect_ratio, duration };
418
+ resolved = { ...resolved, aspect_ratio, duration, resolution };
419
+ refField = ref;
347
420
  }
348
- const body = buildPayload(apiId, resolved);
421
+ const body = buildPayload(apiId, resolved, refField, referenceUrls);
349
422
  const submitted = await ctx.client.run(apiId, body);
350
423
  const result = await pollJobUntilDoneOrHandoff(
351
424
  submitted.jobId,
@@ -378,9 +451,18 @@ var init_generate = __esm({
378
451
  ),
379
452
  aspect_ratio: zod.z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
380
453
  duration: zod.z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
454
+ resolution: zod.z.string().optional().describe(
455
+ 'Output resolution when the model supports it (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits. Omit to use the model default.'
456
+ ),
381
457
  image_url: zod.z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
382
458
  image_path: zod.z.string().min(1).optional().describe(
383
459
  "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."
460
+ ),
461
+ image_urls: zod.z.array(zod.z.string().url()).optional().describe(
462
+ "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."
463
+ ),
464
+ image_paths: zod.z.array(zod.z.string().min(1)).optional().describe(
465
+ "Multiple absolute local file paths. Each is uploaded to PixelDojo (24h temp storage) and used as a reference, same rules as image_urls."
384
466
  )
385
467
  });
386
468
  DEFAULT_MODEL = "google-nano-banana";
@@ -399,7 +481,21 @@ var init_generate = __esm({
399
481
  },
400
482
  aspect_ratio: { type: "string" },
401
483
  duration: { type: "integer" },
484
+ resolution: {
485
+ type: "string",
486
+ description: 'Output resolution when supported (e.g. "480p", "720p", "1080p", "4k"). Lower tiers cost fewer credits.'
487
+ },
402
488
  image_url: { type: "string", format: "uri" },
489
+ image_urls: {
490
+ type: "array",
491
+ items: { type: "string", format: "uri" },
492
+ description: "Multiple reference image URLs for models that accept a reference set. Single-reference models use the first."
493
+ },
494
+ image_paths: {
495
+ type: "array",
496
+ items: { type: "string" },
497
+ description: "Multiple absolute local file paths, each uploaded to 24h temp storage and used as a reference."
498
+ },
403
499
  image_path: {
404
500
  type: "string",
405
501
  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."
@@ -408,7 +504,7 @@ var init_generate = __esm({
408
504
  required: ["prompt"]
409
505
  };
410
506
  generateSkill = {
411
- name: "pixeldojo:generate",
507
+ name: "pixeldojo_generate",
412
508
  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.",
413
509
  inputZod: InputSchema,
414
510
  inputJsonSchema: InputJsonSchema,
@@ -493,14 +589,14 @@ async function run2(input, ctx) {
493
589
  }
494
590
  } catch (err) {
495
591
  if (err instanceof SkillUploadError) {
496
- return { kind: "text", text: `pixeldojo:edit (upload): ${err.userMessage}` };
592
+ return { kind: "text", text: `pixeldojo_edit (upload): ${err.userMessage}` };
497
593
  }
498
594
  throw err;
499
595
  }
500
596
  if (!resolved.image_url && (!resolved.image_urls || resolved.image_urls.length === 0)) {
501
597
  return {
502
598
  kind: "text",
503
- text: "pixeldojo:edit requires at least one reference image. Pass image_url / image_urls (hosted URL) or image_path / image_paths (local file)."
599
+ text: "pixeldojo_edit requires at least one reference image. Pass image_url / image_urls (hosted URL) or image_path / image_paths (local file)."
504
600
  };
505
601
  }
506
602
  const apiId = resolved.model ?? DEFAULT_MODEL2;
@@ -594,7 +690,7 @@ var init_edit = __esm({
594
690
  required: ["prompt"]
595
691
  };
596
692
  editSkill = {
597
- name: "pixeldojo:edit",
693
+ name: "pixeldojo_edit",
598
694
  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).",
599
695
  inputZod: InputSchema2,
600
696
  inputJsonSchema: InputJsonSchema2,
@@ -609,14 +705,14 @@ async function run3(input, ctx) {
609
705
  if (!ALLOWED.includes(requested)) {
610
706
  return {
611
707
  kind: "text",
612
- text: `Unknown model "${requested}" for pixeldojo:character. Allowed: ${ALLOWED.join(", ")}.`
708
+ text: `Unknown model "${requested}" for pixeldojo_character. Allowed: ${ALLOWED.join(", ")}.`
613
709
  };
614
710
  }
615
711
  const apiId = requested;
616
712
  if (REQUIRES_REFERENCE.has(apiId) && !input.reference_image_url) {
617
713
  return {
618
714
  kind: "text",
619
- 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}".`
715
+ 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}".`
620
716
  };
621
717
  }
622
718
  const body = { prompt: input.prompt };
@@ -678,7 +774,7 @@ var init_character = __esm({
678
774
  required: ["prompt"]
679
775
  };
680
776
  characterSkill = {
681
- name: "pixeldojo:character",
777
+ name: "pixeldojo_character",
682
778
  description: "Generate or edit consistent characters across shots. Pass a reference_image_url for identity locking (seedream-5 by default).",
683
779
  inputZod: InputSchema3,
684
780
  inputJsonSchema: InputJsonSchema3,
@@ -693,7 +789,7 @@ async function run4(input, ctx) {
693
789
  if (!ALLOWED2.includes(requested)) {
694
790
  return {
695
791
  kind: "text",
696
- text: `Unknown model "${requested}" for pixeldojo:storyboard. Allowed: ${ALLOWED2.join(", ")}.`
792
+ text: `Unknown model "${requested}" for pixeldojo_storyboard. Allowed: ${ALLOWED2.join(", ")}.`
697
793
  };
698
794
  }
699
795
  const apiId = requested;
@@ -756,7 +852,7 @@ var init_storyboard = __esm({
756
852
  required: ["brief"]
757
853
  };
758
854
  storyboardSkill = {
759
- name: "pixeldojo:storyboard",
855
+ name: "pixeldojo_storyboard",
760
856
  description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
761
857
  inputZod: InputSchema4,
762
858
  inputJsonSchema: InputJsonSchema4,
@@ -771,7 +867,7 @@ async function run5(input, ctx) {
771
867
  if (!ALLOWED3.includes(requested)) {
772
868
  return {
773
869
  kind: "text",
774
- text: `Unknown model "${requested}" for pixeldojo:upscale. Allowed: ${ALLOWED3.join(", ")}.`
870
+ text: `Unknown model "${requested}" for pixeldojo_upscale. Allowed: ${ALLOWED3.join(", ")}.`
775
871
  };
776
872
  }
777
873
  const apiId = requested;
@@ -833,7 +929,7 @@ var init_upscale = __esm({
833
929
  required: ["image_url"]
834
930
  };
835
931
  upscaleSkill = {
836
- name: "pixeldojo:upscale",
932
+ name: "pixeldojo_upscale",
837
933
  description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
838
934
  inputZod: InputSchema5,
839
935
  inputJsonSchema: InputJsonSchema5,
@@ -911,14 +1007,14 @@ var InputSchema6, InputJsonSchema6, STUCK_HINT_MS, ABANDON_HINT_MS, statusSkill;
911
1007
  var init_status = __esm({
912
1008
  "src/skills/status.ts"() {
913
1009
  InputSchema6 = zod.z.object({
914
- jobId: zod.z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
1010
+ jobId: zod.z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo_* skill handoff.")
915
1011
  });
916
1012
  InputJsonSchema6 = {
917
1013
  type: "object",
918
1014
  properties: {
919
1015
  jobId: {
920
1016
  type: "string",
921
- description: "Job id returned from a prior pixeldojo:* skill handoff."
1017
+ description: "Job id returned from a prior pixeldojo_* skill handoff."
922
1018
  }
923
1019
  },
924
1020
  required: ["jobId"]
@@ -926,7 +1022,7 @@ var init_status = __esm({
926
1022
  STUCK_HINT_MS = 5 * 60 * 1e3;
927
1023
  ABANDON_HINT_MS = 12 * 60 * 1e3;
928
1024
  statusSkill = {
929
- name: "pixeldojo:status",
1025
+ name: "pixeldojo_status",
930
1026
  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.",
931
1027
  inputZod: InputSchema6,
932
1028
  inputJsonSchema: InputJsonSchema6,
@@ -971,8 +1067,8 @@ var init_from_url = __esm({
971
1067
  required: ["url"]
972
1068
  };
973
1069
  fromUrlSkill = {
974
- name: "pixeldojo:from_url",
975
- 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.",
1070
+ name: "pixeldojo_from_url",
1071
+ 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.",
976
1072
  inputZod: InputSchema7,
977
1073
  inputJsonSchema: InputJsonSchema7,
978
1074
  // No model dispatch — this skill calls a server-side scrape, not a generation.
@@ -1033,7 +1129,7 @@ async function plan(input, ctx) {
1033
1129
  ...sceneLines,
1034
1130
  "",
1035
1131
  `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.`,
1036
- `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.`
1132
+ `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.`
1037
1133
  ].join("\n")
1038
1134
  };
1039
1135
  }
@@ -1048,7 +1144,7 @@ async function produce(filmId, ctx) {
1048
1144
  }
1049
1145
  return {
1050
1146
  kind: "text",
1051
- 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.`
1147
+ 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.`
1052
1148
  };
1053
1149
  }
1054
1150
  function buildScorePlanBody(film) {
@@ -1177,13 +1273,13 @@ async function run8(input, ctx) {
1177
1273
  var LOOKS, InputSchema8, InputJsonSchema8, shots, count, LOOK_MUSIC, ARC, filmSkill;
1178
1274
  var init_film = __esm({
1179
1275
  "src/skills/film.ts"() {
1180
- LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western"];
1276
+ LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
1181
1277
  InputSchema8 = zod.z.object({
1182
1278
  idea: zod.z.string().max(2e3).optional().describe("One-to-three sentence film idea. The studio writes the screenplay (free). Provide idea OR script for planning."),
1183
1279
  script: zod.z.string().max(2e4).optional().describe("A full screenplay to plan instead of an idea (sluglines, action lines, dialogue)."),
1184
1280
  look: zod.z.enum(LOOKS).default("live-action").describe("Visual style carried through frames, clips, and soundtrack."),
1185
1281
  aspect: zod.z.enum(["16:9", "9:16"]).default("16:9"),
1186
- length: zod.z.enum(["30s", "60s", "2-3min"]).default("60s").describe("Target length; drives the shot count (~6/12/24 shots)."),
1282
+ length: zod.z.enum(["30s", "60s", "2-3min", "5min"]).default("60s").describe("Target length; drives the shot count (~6/12/24/60 shots)."),
1187
1283
  film_id: zod.z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status."),
1188
1284
  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."),
1189
1285
  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."),
@@ -1196,7 +1292,7 @@ var init_film = __esm({
1196
1292
  script: { type: "string", description: "A full screenplay to plan instead of an idea." },
1197
1293
  look: { type: "string", enum: [...LOOKS], description: "Visual style. Default live-action." },
1198
1294
  aspect: { type: "string", enum: ["16:9", "9:16"], description: "Default 16:9." },
1199
- length: { type: "string", enum: ["30s", "60s", "2-3min"], description: "Target length (~6/12/24 shots). Default 60s." },
1295
+ length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Target length (~6/12/24/60 shots). Default 60s." },
1200
1296
  film_id: { type: "string", description: "Existing film id for produce/status." },
1201
1297
  action: { type: "string", enum: ["plan", "produce", "status"], description: "plan (free) | produce (charged) | status (poll every ~30s)." },
1202
1298
  soundtrack: { type: "string", enum: ["scene-synced", "flat", "none"], description: "Default scene-synced." },
@@ -1213,7 +1309,10 @@ var init_film = __esm({
1213
1309
  horror: "sparse horror underscore, low cello drones, dissonant string swells, creeping heartbeat pulse, unsettling quiet",
1214
1310
  "sci-fi": "atmospheric science-fiction score, analog synth pads, deep sub-bass pulses, shimmering arpeggios, vast and cold",
1215
1311
  documentary: "understated documentary score, felt piano, soft string beds, organic percussion textures, reflective and true",
1216
- western: "cinematic western score, lonely twangy guitar, dusty strings, distant horns, wide-open desert tension"
1312
+ western: "cinematic western score, lonely twangy guitar, dusty strings, distant horns, wide-open desert tension",
1313
+ romance: "tender romantic score, felt piano and warm strings, soft acoustic guitar, intimate and unhurried, gently swelling",
1314
+ "romantic-comedy": "bright romantic-comedy score, plucky ukulele and handclaps, warm brushed drums, sunny and flirtatious",
1315
+ comedy: "playful comedy score, bouncy pizzicato strings, cheeky woodwinds, light percussion hits punctuating the gags"
1217
1316
  };
1218
1317
  ARC = {
1219
1318
  opening: ["establishing the theme", "restrained", "quiet anticipation"],
@@ -1221,7 +1320,7 @@ var init_film = __esm({
1221
1320
  finale: ["climactic", "full arrangement", "emotional peak resolving to stillness"]
1222
1321
  };
1223
1322
  filmSkill = {
1224
- name: "pixeldojo:film",
1323
+ name: "pixeldojo_film",
1225
1324
  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).",
1226
1325
  inputZod: InputSchema8,
1227
1326
  inputJsonSchema: InputJsonSchema8,
@@ -1360,8 +1459,8 @@ var init_ad = __esm({
1360
1459
  // Zod refine; no root-level oneOf (Grok rejects non-object root unions).
1361
1460
  };
1362
1461
  adSkill = {
1363
- name: "pixeldojo:ad",
1364
- 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.",
1462
+ name: "pixeldojo_ad",
1463
+ 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.",
1365
1464
  inputZod: InputSchema9,
1366
1465
  inputJsonSchema: InputJsonSchema9,
1367
1466
  // Composition skill — the server routes to the ad engine internally.
@@ -1393,7 +1492,7 @@ async function run10(input, ctx) {
1393
1492
  }
1394
1493
  return {
1395
1494
  kind: "handoff",
1396
- text: result.message ?? `Campaign ${result.campaignId} queued. Poll via pixeldojo:campaign_status.`,
1495
+ text: result.message ?? `Campaign ${result.campaignId} queued. Poll via pixeldojo_campaign_status.`,
1397
1496
  jobId: result.campaignId,
1398
1497
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
1399
1498
  };
@@ -1446,8 +1545,8 @@ var init_campaign = __esm({
1446
1545
  oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
1447
1546
  };
1448
1547
  campaignSkill = {
1449
- name: "pixeldojo:campaign",
1450
- 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.",
1548
+ name: "pixeldojo_campaign",
1549
+ 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.",
1451
1550
  inputZod: InputSchema10,
1452
1551
  inputJsonSchema: InputJsonSchema10,
1453
1552
  // Composition skill — internal sub-jobs route to per-tool models, no
@@ -1492,21 +1591,21 @@ var InputSchema11, InputJsonSchema11, campaignStatusSkill;
1492
1591
  var init_campaign_status = __esm({
1493
1592
  "src/skills/campaign-status.ts"() {
1494
1593
  InputSchema11 = zod.z.object({
1495
- campaignId: zod.z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
1594
+ campaignId: zod.z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo_campaign handoff.")
1496
1595
  });
1497
1596
  InputJsonSchema11 = {
1498
1597
  type: "object",
1499
1598
  properties: {
1500
1599
  campaignId: {
1501
1600
  type: "string",
1502
- description: "Campaign id returned from a prior pixeldojo:campaign handoff."
1601
+ description: "Campaign id returned from a prior pixeldojo_campaign handoff."
1503
1602
  }
1504
1603
  },
1505
1604
  required: ["campaignId"]
1506
1605
  };
1507
1606
  campaignStatusSkill = {
1508
- name: "pixeldojo:campaign_status",
1509
- 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.",
1607
+ name: "pixeldojo_campaign_status",
1608
+ 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.",
1510
1609
  inputZod: InputSchema11,
1511
1610
  inputJsonSchema: InputJsonSchema11,
1512
1611
  allowedModels: [],
@@ -1521,7 +1620,7 @@ async function run12(input, ctx) {
1521
1620
  url = await uploadLocalFile(input.path, ctx.client);
1522
1621
  } catch (err) {
1523
1622
  if (err instanceof SkillUploadError) {
1524
- return { kind: "text", text: `pixeldojo:upload: ${err.userMessage}` };
1623
+ return { kind: "text", text: `pixeldojo_upload: ${err.userMessage}` };
1525
1624
  }
1526
1625
  throw err;
1527
1626
  }
@@ -1551,7 +1650,7 @@ var init_upload = __esm({
1551
1650
  required: ["path"]
1552
1651
  };
1553
1652
  uploadSkill = {
1554
- name: "pixeldojo:upload",
1653
+ name: "pixeldojo_upload",
1555
1654
  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.",
1556
1655
  inputZod: InputSchema12,
1557
1656
  inputJsonSchema: InputJsonSchema12,
@@ -1566,7 +1665,7 @@ async function run13(input, ctx) {
1566
1665
  if (!ALLOWED4.includes(requested)) {
1567
1666
  return {
1568
1667
  kind: "text",
1569
- text: `Unknown model "${requested}" for pixeldojo:audio. Allowed: ${ALLOWED4.join(", ")}.`
1668
+ text: `Unknown model "${requested}" for pixeldojo_audio. Allowed: ${ALLOWED4.join(", ")}.`
1570
1669
  };
1571
1670
  }
1572
1671
  const apiId = requested;
@@ -1577,7 +1676,7 @@ async function run13(input, ctx) {
1577
1676
  audioUrls = [...audioUrls, ...uploaded].slice(0, 3);
1578
1677
  } catch (err) {
1579
1678
  if (err instanceof SkillUploadError) {
1580
- return { kind: "text", text: `pixeldojo:audio (upload): ${err.userMessage}` };
1679
+ return { kind: "text", text: `pixeldojo_audio (upload): ${err.userMessage}` };
1581
1680
  }
1582
1681
  throw err;
1583
1682
  }
@@ -1643,7 +1742,7 @@ var init_audio = __esm({
1643
1742
  required: ["prompt"]
1644
1743
  };
1645
1744
  audioSkill = {
1646
- name: "pixeldojo:audio",
1745
+ name: "pixeldojo_audio",
1647
1746
  description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain TTS available).",
1648
1747
  inputZod: InputSchema13,
1649
1748
  inputJsonSchema: InputJsonSchema13,
@@ -1663,7 +1762,7 @@ async function run14(input, ctx) {
1663
1762
  if (res.items.length === 0) {
1664
1763
  return {
1665
1764
  kind: "text",
1666
- 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."
1765
+ 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."
1667
1766
  };
1668
1767
  }
1669
1768
  const lines = res.items.map((it) => {
@@ -1704,8 +1803,8 @@ var init_library = __esm({
1704
1803
  required: []
1705
1804
  };
1706
1805
  librarySkill = {
1707
- name: "pixeldojo:library",
1708
- 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.",
1806
+ name: "pixeldojo_library",
1807
+ 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.",
1709
1808
  inputZod: InputSchema14,
1710
1809
  inputJsonSchema: InputJsonSchema14,
1711
1810
  allowedModels: [],
@@ -1725,7 +1824,7 @@ async function run15(input, ctx) {
1725
1824
  return {
1726
1825
  kind: "text",
1727
1826
  text: `Saved to My Media (permanent): ${res.url}
1728
- It will appear at https://pixeldojo.ai/my-images and in pixeldojo:library searches.`
1827
+ It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
1729
1828
  };
1730
1829
  }
1731
1830
  var InputSchema15, InputJsonSchema15, saveSkill;
@@ -1750,7 +1849,7 @@ var init_save = __esm({
1750
1849
  required: ["url"]
1751
1850
  };
1752
1851
  saveSkill = {
1753
- name: "pixeldojo:save",
1852
+ name: "pixeldojo_save",
1754
1853
  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.",
1755
1854
  inputZod: InputSchema15,
1756
1855
  inputJsonSchema: InputJsonSchema15,
@@ -1798,7 +1897,7 @@ async function runSave(input, ctx) {
1798
1897
  });
1799
1898
  return {
1800
1899
  kind: "text",
1801
- 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"}).`
1900
+ 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"}).`
1802
1901
  };
1803
1902
  }
1804
1903
  async function runList(_input, ctx) {
@@ -1806,7 +1905,7 @@ async function runList(_input, ctx) {
1806
1905
  if (res.count === 0) {
1807
1906
  return {
1808
1907
  kind: "text",
1809
- text: "No saved workflows yet. Save one with pixeldojo:save_workflow { name, steps: [{ skill, args }] }."
1908
+ text: "No saved workflows yet. Save one with pixeldojo_save_workflow { name, steps: [{ skill, args }] }."
1810
1909
  };
1811
1910
  }
1812
1911
  const lines = res.workflows.map(
@@ -1875,7 +1974,7 @@ async function runWorkflow(input, ctx) {
1875
1974
  } else if (result.kind === "handoff") {
1876
1975
  return {
1877
1976
  kind: "text",
1878
- 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.`
1977
+ 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.`
1879
1978
  };
1880
1979
  } else {
1881
1980
  outputs.push(`step ${i + 1} (${step.skill}): done (no asset URL)`);
@@ -1894,7 +1993,7 @@ var init_workflows = __esm({
1894
1993
  init_types();
1895
1994
  WORKFLOW_STEP_EXTRA_BUDGET_MS = 6e4;
1896
1995
  StepSchema = zod.z.object({
1897
- skill: zod.z.string().regex(/^pixeldojo:(generate|edit|character|storyboard|upscale|audio|upload|from_url)$/, {
1996
+ skill: zod.z.string().regex(/^pixeldojo[:_](generate|edit|character|storyboard|upscale|audio|upload|from_url)$/, {
1898
1997
  message: "Runnable skills only: generate, edit, character, storyboard, upscale, audio, upload, from_url."
1899
1998
  }),
1900
1999
  args: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
@@ -1920,7 +2019,7 @@ var init_workflows = __esm({
1920
2019
  required: ["skill"]
1921
2020
  };
1922
2021
  saveWorkflowSkill = {
1923
- name: "pixeldojo:save_workflow",
2022
+ name: "pixeldojo_save_workflow",
1924
2023
  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.",
1925
2024
  inputZod: SaveInputSchema,
1926
2025
  inputJsonSchema: {
@@ -1937,7 +2036,7 @@ var init_workflows = __esm({
1937
2036
  run: runSave
1938
2037
  };
1939
2038
  runWorkflowSkill = {
1940
- name: "pixeldojo:run_workflow",
2039
+ name: "pixeldojo_run_workflow",
1941
2040
  description: "Run a saved workflow by name. Pass inputs for its {{input.<key>}} placeholders; each step feeds the next via {{prev}}.",
1942
2041
  inputZod: RunInputSchema,
1943
2042
  inputJsonSchema: {
@@ -1953,7 +2052,7 @@ var init_workflows = __esm({
1953
2052
  run: runWorkflow
1954
2053
  };
1955
2054
  listWorkflowsSkill = {
1956
- name: "pixeldojo:list_workflows",
2055
+ name: "pixeldojo_list_workflows",
1957
2056
  description: "List your saved workflows with step counts and run totals.",
1958
2057
  inputZod: ListInputSchema,
1959
2058
  inputJsonSchema: { type: "object", properties: {}, required: [] },
@@ -1989,7 +2088,8 @@ __export(skills_exports, {
1989
2088
  upscaleSkill: () => upscaleSkill
1990
2089
  });
1991
2090
  function findSkill(name) {
1992
- return ALL_SKILLS.find((s) => s.name === name);
2091
+ const wanted = name.replace(/^pixeldojo:/, "pixeldojo_");
2092
+ return ALL_SKILLS.find((s) => s.name === wanted);
1993
2093
  }
1994
2094
  var ALL_SKILLS;
1995
2095
  var init_skills = __esm({
@@ -2083,7 +2183,7 @@ function makeRedactedLogger(target = console) {
2083
2183
  }
2084
2184
 
2085
2185
  // src/server.ts
2086
- var PACKAGE_VERSION = "0.9.0" ;
2186
+ var PACKAGE_VERSION = "0.9.1" ;
2087
2187
  function buildServer(options) {
2088
2188
  if (!options.apiKey) {
2089
2189
  throw new Error(
@@ -2250,19 +2350,19 @@ function printInit() {
2250
2350
  `# Same shape as above.`,
2251
2351
  ``,
2252
2352
  `# 3. Restart your agent. You'll see these tools:`,
2253
- `# pixeldojo:generate Any prompt \u2192 image or video.`,
2254
- `# pixeldojo:edit Change an image with a text instruction.`,
2255
- `# pixeldojo:character Consistent characters across shots.`,
2256
- `# pixeldojo:storyboard Multi-shot scenes from one brief.`,
2257
- `# pixeldojo:upscale Enhance any image.`,
2258
- `# pixeldojo:audio Voices + effects + music in one clip.`,
2259
- `# pixeldojo:library Search your saved media + generations.`,
2260
- `# pixeldojo:save Keep a result in My Media permanently.`,
2261
- `# pixeldojo:*_workflow Save / run / list multi-step chains.`,
2262
- `# pixeldojo:upload Local file \u2192 temp URL for references.`,
2263
- `# pixeldojo:from_url Product URL \u2192 structured product info.`,
2264
- `# pixeldojo:campaign* Product URL \u2192 full launch pack.`,
2265
- `# pixeldojo:status Poll long-running jobs.`,
2353
+ `# pixeldojo_generate Any prompt \u2192 image or video.`,
2354
+ `# pixeldojo_edit Change an image with a text instruction.`,
2355
+ `# pixeldojo_character Consistent characters across shots.`,
2356
+ `# pixeldojo_storyboard Multi-shot scenes from one brief.`,
2357
+ `# pixeldojo_upscale Enhance any image.`,
2358
+ `# pixeldojo_audio Voices + effects + music in one clip.`,
2359
+ `# pixeldojo_library Search your saved media + generations.`,
2360
+ `# pixeldojo_save Keep a result in My Media permanently.`,
2361
+ `# pixeldojo_*_workflow Save / run / list multi-step chains.`,
2362
+ `# pixeldojo_upload Local file \u2192 temp URL for references.`,
2363
+ `# pixeldojo_from_url Product URL \u2192 structured product info.`,
2364
+ `# pixeldojo_campaign* Product URL \u2192 full launch pack.`,
2365
+ `# pixeldojo_status Poll long-running jobs.`,
2266
2366
  ``
2267
2367
  ].join("\n")
2268
2368
  );