@pixeldojo/mcp 0.8.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/README.md +15 -15
- package/dist/cli.cjs +531 -130
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +531 -130
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +518 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +518 -117
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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,7 +186,8 @@ 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
|
-
|
|
189
|
+
if (typeof client?.getModelSchema !== "function") return Promise.resolve(null);
|
|
190
|
+
const pending = Promise.resolve().then(() => client.getModelSchema(apiId)).then(
|
|
190
191
|
(res) => res.schema?.properties ?? {}
|
|
191
192
|
).catch(() => {
|
|
192
193
|
schemaCache.delete(apiId);
|
|
@@ -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);
|
|
@@ -254,13 +295,49 @@ async function snapDuration(apiId, requested, client, opts = {}) {
|
|
|
254
295
|
}
|
|
255
296
|
return requested;
|
|
256
297
|
}
|
|
257
|
-
|
|
298
|
+
async function resolveImageRefs(apiId, urls, client) {
|
|
299
|
+
if (urls.length === 0) return null;
|
|
300
|
+
const props = await getFieldConstraints(apiId, client);
|
|
301
|
+
if (props === null) return null;
|
|
302
|
+
const key = REF_KEYS.find((k) => props[k] !== void 0);
|
|
303
|
+
if (!key) return null;
|
|
304
|
+
const field = props[key];
|
|
305
|
+
if (field.type === "array") {
|
|
306
|
+
const cap = typeof field.maxItems === "number" ? field.maxItems : urls.length;
|
|
307
|
+
const kept = urls.slice(0, Math.max(1, cap));
|
|
308
|
+
return { key, value: kept, dropped: urls.length - kept.length };
|
|
309
|
+
}
|
|
310
|
+
return { key, value: urls[0], dropped: urls.length - 1 };
|
|
311
|
+
}
|
|
312
|
+
var schemaCache, REFERENCE_FIELD_PRIORITY, REF_KEYS;
|
|
258
313
|
var init_schema_snap = __esm({
|
|
259
314
|
"src/skills/schema-snap.ts"() {
|
|
260
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
|
+
];
|
|
328
|
+
REF_KEYS = [
|
|
329
|
+
"image_urls",
|
|
330
|
+
"images",
|
|
331
|
+
"image_input",
|
|
332
|
+
"reference_images",
|
|
333
|
+
"reference_urls",
|
|
334
|
+
"image_url",
|
|
335
|
+
"input_image",
|
|
336
|
+
"image"
|
|
337
|
+
];
|
|
261
338
|
}
|
|
262
339
|
});
|
|
263
|
-
function buildPayload(apiId, input) {
|
|
340
|
+
function buildPayload(apiId, input, refField, referenceUrls = []) {
|
|
264
341
|
const body = { prompt: input.prompt };
|
|
265
342
|
if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
|
|
266
343
|
if (input.preset) body.preset = input.preset;
|
|
@@ -278,8 +355,16 @@ function buildPayload(apiId, input) {
|
|
|
278
355
|
}
|
|
279
356
|
break;
|
|
280
357
|
default:
|
|
281
|
-
if (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
|
+
}
|
|
282
366
|
if (input.duration !== void 0) body.duration = input.duration;
|
|
367
|
+
if (input.resolution) body.resolution = input.resolution;
|
|
283
368
|
break;
|
|
284
369
|
}
|
|
285
370
|
return body;
|
|
@@ -287,39 +372,51 @@ function buildPayload(apiId, input) {
|
|
|
287
372
|
async function run(input, ctx) {
|
|
288
373
|
const apiId = input.model ?? DEFAULT_MODEL;
|
|
289
374
|
let resolved = input;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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] };
|
|
387
|
+
}
|
|
388
|
+
} catch (err) {
|
|
389
|
+
if (err instanceof SkillUploadError) {
|
|
390
|
+
return { kind: "text", text: `pixeldojo_generate (upload): ${err.userMessage}` };
|
|
299
391
|
}
|
|
392
|
+
throw err;
|
|
300
393
|
}
|
|
301
394
|
if (apiId === "grok-video-extend") {
|
|
302
395
|
if (!resolved.image_url) {
|
|
303
396
|
return {
|
|
304
397
|
kind: "text",
|
|
305
|
-
text: '
|
|
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).'
|
|
306
399
|
};
|
|
307
400
|
}
|
|
308
401
|
if (resolved.duration === void 0) {
|
|
309
402
|
return {
|
|
310
403
|
kind: "text",
|
|
311
|
-
text: '
|
|
404
|
+
text: 'pixeldojo_generate with model="grok-video-extend" requires duration (extension length in seconds).'
|
|
312
405
|
};
|
|
313
406
|
}
|
|
314
407
|
}
|
|
408
|
+
let refField;
|
|
315
409
|
if (!VIDEO_INPUT_MODELS.has(apiId)) {
|
|
316
|
-
const [aspect_ratio, duration] = await Promise.all([
|
|
410
|
+
const [aspect_ratio, duration, resolution, ref] = await Promise.all([
|
|
317
411
|
snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client),
|
|
318
|
-
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)
|
|
319
415
|
]);
|
|
320
|
-
resolved = { ...resolved, aspect_ratio, duration };
|
|
416
|
+
resolved = { ...resolved, aspect_ratio, duration, resolution };
|
|
417
|
+
refField = ref;
|
|
321
418
|
}
|
|
322
|
-
const body = buildPayload(apiId, resolved);
|
|
419
|
+
const body = buildPayload(apiId, resolved, refField, referenceUrls);
|
|
323
420
|
const submitted = await ctx.client.run(apiId, body);
|
|
324
421
|
const result = await pollJobUntilDoneOrHandoff(
|
|
325
422
|
submitted.jobId,
|
|
@@ -352,9 +449,18 @@ var init_generate = __esm({
|
|
|
352
449
|
),
|
|
353
450
|
aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
|
|
354
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
|
+
),
|
|
355
455
|
image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
|
|
356
456
|
image_path: z.string().min(1).optional().describe(
|
|
357
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."
|
|
358
464
|
)
|
|
359
465
|
});
|
|
360
466
|
DEFAULT_MODEL = "google-nano-banana";
|
|
@@ -373,7 +479,21 @@ var init_generate = __esm({
|
|
|
373
479
|
},
|
|
374
480
|
aspect_ratio: { type: "string" },
|
|
375
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
|
+
},
|
|
376
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
|
+
},
|
|
377
497
|
image_path: {
|
|
378
498
|
type: "string",
|
|
379
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."
|
|
@@ -382,7 +502,7 @@ var init_generate = __esm({
|
|
|
382
502
|
required: ["prompt"]
|
|
383
503
|
};
|
|
384
504
|
generateSkill = {
|
|
385
|
-
name: "
|
|
505
|
+
name: "pixeldojo_generate",
|
|
386
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.",
|
|
387
507
|
inputZod: InputSchema,
|
|
388
508
|
inputJsonSchema: InputJsonSchema,
|
|
@@ -409,12 +529,21 @@ function buildPayload2(apiId, input) {
|
|
|
409
529
|
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
410
530
|
};
|
|
411
531
|
case "flux-edit":
|
|
412
|
-
|
|
532
|
+
return {
|
|
533
|
+
prompt: input.prompt,
|
|
534
|
+
input_image_1: urls[0],
|
|
535
|
+
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
536
|
+
};
|
|
413
537
|
case "gpt-image-2-edit":
|
|
538
|
+
return {
|
|
539
|
+
prompt: input.prompt,
|
|
540
|
+
image_urls: urls.slice(0, 8),
|
|
541
|
+
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
542
|
+
};
|
|
543
|
+
case "xai-image-edit":
|
|
414
544
|
return {
|
|
415
545
|
prompt: input.prompt,
|
|
416
546
|
image_url: urls[0],
|
|
417
|
-
...urls.length > 1 ? { image_urls: urls } : {},
|
|
418
547
|
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
419
548
|
};
|
|
420
549
|
case "google-nano-banana":
|
|
@@ -430,6 +559,19 @@ function buildPayload2(apiId, input) {
|
|
|
430
559
|
};
|
|
431
560
|
}
|
|
432
561
|
}
|
|
562
|
+
async function buildBody(apiId, input, ctx) {
|
|
563
|
+
const urls = input.image_urls && input.image_urls.length > 0 ? input.image_urls : input.image_url ? [input.image_url] : [];
|
|
564
|
+
const refs = await resolveImageRefs(apiId, urls, ctx.client);
|
|
565
|
+
if (!refs) return { body: buildPayload2(apiId, input), dropped: 0 };
|
|
566
|
+
const fallback = buildPayload2(apiId, input);
|
|
567
|
+
const body = { ...fallback };
|
|
568
|
+
for (const key of ["image_url", "image_urls", "images", "image_input", "input_image_1"]) {
|
|
569
|
+
if (key !== refs.key) delete body[key];
|
|
570
|
+
}
|
|
571
|
+
body.prompt = input.prompt;
|
|
572
|
+
body[refs.key] = refs.value;
|
|
573
|
+
return { body, dropped: refs.dropped };
|
|
574
|
+
}
|
|
433
575
|
async function run2(input, ctx) {
|
|
434
576
|
let resolved = input;
|
|
435
577
|
try {
|
|
@@ -445,14 +587,14 @@ async function run2(input, ctx) {
|
|
|
445
587
|
}
|
|
446
588
|
} catch (err) {
|
|
447
589
|
if (err instanceof SkillUploadError) {
|
|
448
|
-
return { kind: "text", text: `
|
|
590
|
+
return { kind: "text", text: `pixeldojo_edit (upload): ${err.userMessage}` };
|
|
449
591
|
}
|
|
450
592
|
throw err;
|
|
451
593
|
}
|
|
452
594
|
if (!resolved.image_url && (!resolved.image_urls || resolved.image_urls.length === 0)) {
|
|
453
595
|
return {
|
|
454
596
|
kind: "text",
|
|
455
|
-
text: "
|
|
597
|
+
text: "pixeldojo_edit requires at least one reference image. Pass image_url / image_urls (hosted URL) or image_path / image_paths (local file)."
|
|
456
598
|
};
|
|
457
599
|
}
|
|
458
600
|
const apiId = resolved.model ?? DEFAULT_MODEL2;
|
|
@@ -460,7 +602,8 @@ async function run2(input, ctx) {
|
|
|
460
602
|
...resolved,
|
|
461
603
|
aspect_ratio: await snapAspectRatio(apiId, resolved.aspect_ratio, ctx.client)
|
|
462
604
|
};
|
|
463
|
-
const body =
|
|
605
|
+
const { body, dropped } = await buildBody(apiId, resolved, ctx);
|
|
606
|
+
const droppedNote = dropped > 0 ? ` (${dropped} reference${dropped === 1 ? "" : "s"} dropped \u2014 ${apiId} accepts fewer)` : "";
|
|
464
607
|
const submitted = await ctx.client.run(apiId, body);
|
|
465
608
|
const result = await pollJobUntilDoneOrHandoff(
|
|
466
609
|
submitted.jobId,
|
|
@@ -473,7 +616,7 @@ async function run2(input, ctx) {
|
|
|
473
616
|
);
|
|
474
617
|
return pollResultToSkillResult(
|
|
475
618
|
result,
|
|
476
|
-
(urls) => urls.length === 0 ? `Edit job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Edit complete. Asset: ${urls[0]}` : `Edit produced ${urls.length} assets. URLs: ${urls.join(", ")}`
|
|
619
|
+
(urls) => urls.length === 0 ? `Edit job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Edit complete${droppedNote}. Asset: ${urls[0]}` : `Edit produced ${urls.length} assets${droppedNote}. URLs: ${urls.join(", ")}`
|
|
477
620
|
);
|
|
478
621
|
}
|
|
479
622
|
var DEFAULT_MODEL2, ALLOWED_MODELS, InputSchema2, InputJsonSchema2, editSkill;
|
|
@@ -545,7 +688,7 @@ var init_edit = __esm({
|
|
|
545
688
|
required: ["prompt"]
|
|
546
689
|
};
|
|
547
690
|
editSkill = {
|
|
548
|
-
name: "
|
|
691
|
+
name: "pixeldojo_edit",
|
|
549
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).",
|
|
550
693
|
inputZod: InputSchema2,
|
|
551
694
|
inputJsonSchema: InputJsonSchema2,
|
|
@@ -560,14 +703,14 @@ async function run3(input, ctx) {
|
|
|
560
703
|
if (!ALLOWED.includes(requested)) {
|
|
561
704
|
return {
|
|
562
705
|
kind: "text",
|
|
563
|
-
text: `Unknown model "${requested}" for
|
|
706
|
+
text: `Unknown model "${requested}" for pixeldojo_character. Allowed: ${ALLOWED.join(", ")}.`
|
|
564
707
|
};
|
|
565
708
|
}
|
|
566
709
|
const apiId = requested;
|
|
567
710
|
if (REQUIRES_REFERENCE.has(apiId) && !input.reference_image_url) {
|
|
568
711
|
return {
|
|
569
712
|
kind: "text",
|
|
570
|
-
text: `
|
|
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}".`
|
|
571
714
|
};
|
|
572
715
|
}
|
|
573
716
|
const body = { prompt: input.prompt };
|
|
@@ -629,7 +772,7 @@ var init_character = __esm({
|
|
|
629
772
|
required: ["prompt"]
|
|
630
773
|
};
|
|
631
774
|
characterSkill = {
|
|
632
|
-
name: "
|
|
775
|
+
name: "pixeldojo_character",
|
|
633
776
|
description: "Generate or edit consistent characters across shots. Pass a reference_image_url for identity locking (seedream-5 by default).",
|
|
634
777
|
inputZod: InputSchema3,
|
|
635
778
|
inputJsonSchema: InputJsonSchema3,
|
|
@@ -644,7 +787,7 @@ async function run4(input, ctx) {
|
|
|
644
787
|
if (!ALLOWED2.includes(requested)) {
|
|
645
788
|
return {
|
|
646
789
|
kind: "text",
|
|
647
|
-
text: `Unknown model "${requested}" for
|
|
790
|
+
text: `Unknown model "${requested}" for pixeldojo_storyboard. Allowed: ${ALLOWED2.join(", ")}.`
|
|
648
791
|
};
|
|
649
792
|
}
|
|
650
793
|
const apiId = requested;
|
|
@@ -707,7 +850,7 @@ var init_storyboard = __esm({
|
|
|
707
850
|
required: ["brief"]
|
|
708
851
|
};
|
|
709
852
|
storyboardSkill = {
|
|
710
|
-
name: "
|
|
853
|
+
name: "pixeldojo_storyboard",
|
|
711
854
|
description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
|
|
712
855
|
inputZod: InputSchema4,
|
|
713
856
|
inputJsonSchema: InputJsonSchema4,
|
|
@@ -722,7 +865,7 @@ async function run5(input, ctx) {
|
|
|
722
865
|
if (!ALLOWED3.includes(requested)) {
|
|
723
866
|
return {
|
|
724
867
|
kind: "text",
|
|
725
|
-
text: `Unknown model "${requested}" for
|
|
868
|
+
text: `Unknown model "${requested}" for pixeldojo_upscale. Allowed: ${ALLOWED3.join(", ")}.`
|
|
726
869
|
};
|
|
727
870
|
}
|
|
728
871
|
const apiId = requested;
|
|
@@ -784,7 +927,7 @@ var init_upscale = __esm({
|
|
|
784
927
|
required: ["image_url"]
|
|
785
928
|
};
|
|
786
929
|
upscaleSkill = {
|
|
787
|
-
name: "
|
|
930
|
+
name: "pixeldojo_upscale",
|
|
788
931
|
description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
|
|
789
932
|
inputZod: InputSchema5,
|
|
790
933
|
inputJsonSchema: InputJsonSchema5,
|
|
@@ -862,14 +1005,14 @@ var InputSchema6, InputJsonSchema6, STUCK_HINT_MS, ABANDON_HINT_MS, statusSkill;
|
|
|
862
1005
|
var init_status = __esm({
|
|
863
1006
|
"src/skills/status.ts"() {
|
|
864
1007
|
InputSchema6 = z.object({
|
|
865
|
-
jobId: z.string().min(1).max(200).describe("Job id returned from a prior
|
|
1008
|
+
jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo_* skill handoff.")
|
|
866
1009
|
});
|
|
867
1010
|
InputJsonSchema6 = {
|
|
868
1011
|
type: "object",
|
|
869
1012
|
properties: {
|
|
870
1013
|
jobId: {
|
|
871
1014
|
type: "string",
|
|
872
|
-
description: "Job id returned from a prior
|
|
1015
|
+
description: "Job id returned from a prior pixeldojo_* skill handoff."
|
|
873
1016
|
}
|
|
874
1017
|
},
|
|
875
1018
|
required: ["jobId"]
|
|
@@ -877,7 +1020,7 @@ var init_status = __esm({
|
|
|
877
1020
|
STUCK_HINT_MS = 5 * 60 * 1e3;
|
|
878
1021
|
ABANDON_HINT_MS = 12 * 60 * 1e3;
|
|
879
1022
|
statusSkill = {
|
|
880
|
-
name: "
|
|
1023
|
+
name: "pixeldojo_status",
|
|
881
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.",
|
|
882
1025
|
inputZod: InputSchema6,
|
|
883
1026
|
inputJsonSchema: InputJsonSchema6,
|
|
@@ -922,8 +1065,8 @@ var init_from_url = __esm({
|
|
|
922
1065
|
required: ["url"]
|
|
923
1066
|
};
|
|
924
1067
|
fromUrlSkill = {
|
|
925
|
-
name: "
|
|
926
|
-
description: "Extract product info (name, description, images) from any product page URL. Use this as the first step before
|
|
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.",
|
|
927
1070
|
inputZod: InputSchema7,
|
|
928
1071
|
inputJsonSchema: InputJsonSchema7,
|
|
929
1072
|
// No model dispatch — this skill calls a server-side scrape, not a generation.
|
|
@@ -933,7 +1076,259 @@ var init_from_url = __esm({
|
|
|
933
1076
|
};
|
|
934
1077
|
}
|
|
935
1078
|
});
|
|
1079
|
+
async function filmApi(ctx, method, path, body) {
|
|
1080
|
+
const res = await fetch(`${ctx.baseUrl}${path}`, {
|
|
1081
|
+
method,
|
|
1082
|
+
headers: {
|
|
1083
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
1084
|
+
"Content-Type": "application/json",
|
|
1085
|
+
"x-pixeldojo-source": `mcp/${ctx.mcpVersion}`,
|
|
1086
|
+
"x-pixeldojo-skill": "film"
|
|
1087
|
+
},
|
|
1088
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
1089
|
+
});
|
|
1090
|
+
const data = await res.json().catch(() => ({}));
|
|
1091
|
+
return { status: res.status, data };
|
|
1092
|
+
}
|
|
1093
|
+
function progressLine(f) {
|
|
1094
|
+
const all = shots(f);
|
|
1095
|
+
const frames = all.filter((sh) => ["frame-ready", "approved", "clip-generating", "clip-ready"].includes(sh.status) || sh.frameUrl).length;
|
|
1096
|
+
const clips = all.filter((sh) => sh.status === "clip-ready").length;
|
|
1097
|
+
const rendering = all.filter((sh) => sh.status === "clip-generating").length;
|
|
1098
|
+
return `frames ${frames}/${all.length}, clips ${clips}/${all.length}${rendering > 0 ? ` (${rendering} rendering)` : ""}`;
|
|
1099
|
+
}
|
|
1100
|
+
async function plan(input, ctx) {
|
|
1101
|
+
if (!input.idea?.trim() && !input.script?.trim()) {
|
|
1102
|
+
return { kind: "text", text: "Provide an `idea` (we write the script, free) or a full `script` to plan a film." };
|
|
1103
|
+
}
|
|
1104
|
+
const created = await filmApi(ctx, "POST", "/api/films", {
|
|
1105
|
+
aspect: input.aspect,
|
|
1106
|
+
look: input.look,
|
|
1107
|
+
lengthTarget: input.length,
|
|
1108
|
+
assetIds: []
|
|
1109
|
+
});
|
|
1110
|
+
if (!created.data.film) return { kind: "text", text: `Could not create the film: ${created.data.error ?? created.status}` };
|
|
1111
|
+
const id = created.data.film.id;
|
|
1112
|
+
let script = input.script?.trim() ?? "";
|
|
1113
|
+
if (!script) {
|
|
1114
|
+
const wrote = await filmApi(ctx, "POST", `/api/films/${id}/script`, { idea: input.idea.trim() });
|
|
1115
|
+
script = wrote.data.film?.script ?? "";
|
|
1116
|
+
if (!script) return { kind: "text", text: `Script writing failed: ${wrote.data.error ?? wrote.status}` };
|
|
1117
|
+
}
|
|
1118
|
+
const planned = await filmApi(ctx, "POST", `/api/films/${id}/plan`, { script });
|
|
1119
|
+
const film = planned.data.film;
|
|
1120
|
+
if (!film) return { kind: "text", text: `Shot planning failed: ${planned.data.error ?? planned.status}` };
|
|
1121
|
+
const total = shots(film).length;
|
|
1122
|
+
const sceneLines = film.scenes.map((s, i) => ` Scene ${i + 1} "${s.title}"${s.location ? ` [${s.location}]` : ""}: ${s.shots.length} shots`);
|
|
1123
|
+
return {
|
|
1124
|
+
kind: "text",
|
|
1125
|
+
text: [
|
|
1126
|
+
`Planned "${film.title}" (film_id: ${film.id}) \u2014 ${film.scenes.length} scenes, ${total} shots, ${input.look}, ${input.aspect}.`,
|
|
1127
|
+
...sceneLines,
|
|
1128
|
+
"",
|
|
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.`,
|
|
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.`
|
|
1131
|
+
].join("\n")
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
async function produce(filmId, ctx) {
|
|
1135
|
+
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}`);
|
|
1136
|
+
if (!data.film) return { kind: "text", text: `Film not found: ${data.error ?? filmId}` };
|
|
1137
|
+
const pending = shots(data.film).filter((sh) => sh.status === "idle" || sh.status === "failed" && !sh.frameUrl);
|
|
1138
|
+
let fired = 0;
|
|
1139
|
+
for (const sh of pending.slice(0, 6)) {
|
|
1140
|
+
const r = await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "frame" });
|
|
1141
|
+
if (r.status === 200) fired += 1;
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
kind: "text",
|
|
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.`
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
function buildScorePlanBody(film) {
|
|
1149
|
+
const scenes = film.scenes.filter((s) => s.shots.length > 0);
|
|
1150
|
+
const sections = scenes.flatMap((scene, i) => {
|
|
1151
|
+
const runtime = Math.max(3e3, scene.shots.reduce((n, sh) => n + (sh.durationSec || 0), 0) * 1e3);
|
|
1152
|
+
const styles = i === 0 ? ARC.opening : i === scenes.length - 1 ? ARC.finale : ARC.middle;
|
|
1153
|
+
const parts = Math.max(1, Math.ceil(runtime / 12e4));
|
|
1154
|
+
const partMs = Math.round(runtime / parts);
|
|
1155
|
+
return Array.from({ length: parts }, (_, part) => ({
|
|
1156
|
+
section_name: parts > 1 ? `${scene.title} (part ${part + 1})` : scene.title,
|
|
1157
|
+
positive_local_styles: styles,
|
|
1158
|
+
negative_local_styles: [],
|
|
1159
|
+
duration_ms: part === parts - 1 ? runtime - partMs * (parts - 1) : partMs,
|
|
1160
|
+
lines: []
|
|
1161
|
+
}));
|
|
1162
|
+
});
|
|
1163
|
+
return {
|
|
1164
|
+
composition_plan: {
|
|
1165
|
+
positive_global_styles: (LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]).split(", "),
|
|
1166
|
+
negative_global_styles: ["vocals", "singing", "lyrics"],
|
|
1167
|
+
sections
|
|
1168
|
+
},
|
|
1169
|
+
respect_sections_durations: true,
|
|
1170
|
+
output_format: "mp3_standard"
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
function buildFlatMusicBody(film, all) {
|
|
1174
|
+
const runtimeSec = all.reduce((n, sh) => n + (sh.durationSec || 0), 0);
|
|
1175
|
+
const ms = Math.min(6e5, Math.max(5e3, Math.ceil(runtimeSec * 1e3 / 5e3) * 5e3));
|
|
1176
|
+
return {
|
|
1177
|
+
prompt: `${LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]}. Instrumental only, no vocals. One consistent theme, loopable as a background bed under dialogue.`,
|
|
1178
|
+
music_length_ms: ms,
|
|
1179
|
+
force_instrumental: true,
|
|
1180
|
+
output_format: "mp3_standard"
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
async function advance(filmId, soundtrack, musicJobId, ctx) {
|
|
1184
|
+
const { data } = await filmApi(ctx, "GET", `/api/films/${filmId}?refresh=1`);
|
|
1185
|
+
const film = data.film;
|
|
1186
|
+
if (!film) return { kind: "text", text: `Film not found: ${data.error ?? filmId}` };
|
|
1187
|
+
const all = shots(film);
|
|
1188
|
+
if (film.status === "exported" && film.exportUrl) {
|
|
1189
|
+
return {
|
|
1190
|
+
kind: "text",
|
|
1191
|
+
text: `Film finished: "${film.title}"
|
|
1192
|
+
Watch/download: ${film.exportUrl}
|
|
1193
|
+
Workspace: ${ctx.baseUrl}/film-studio/${film.id}
|
|
1194
|
+
It is saved in the Library folder "Film: ${film.title}".`
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
if (film.exportError) {
|
|
1198
|
+
return { kind: "text", text: `Export failed: ${film.exportError} Poll again with action "status" to retry.` };
|
|
1199
|
+
}
|
|
1200
|
+
const idleFrames = all.filter((sh) => sh.status === "idle");
|
|
1201
|
+
for (const sh of idleFrames.slice(0, 6)) {
|
|
1202
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "frame" });
|
|
1203
|
+
}
|
|
1204
|
+
const framesDone = all.every((sh) => ["frame-ready", "clip-generating", "clip-ready"].includes(sh.status));
|
|
1205
|
+
if (framesDone && all.some((sh) => sh.status === "frame-ready" && !sh.approved)) {
|
|
1206
|
+
await filmApi(ctx, "PATCH", `/api/films/${filmId}`, {
|
|
1207
|
+
scenes: film.scenes.map((s) => ({
|
|
1208
|
+
id: s.id,
|
|
1209
|
+
title: s.title,
|
|
1210
|
+
location: s.location,
|
|
1211
|
+
shots: s.shots.map((sh) => ({
|
|
1212
|
+
id: sh.id,
|
|
1213
|
+
prompt: sh.prompt,
|
|
1214
|
+
characterIds: sh.characterIds ?? [],
|
|
1215
|
+
elementIds: sh.elementIds ?? [],
|
|
1216
|
+
engine: sh.engine,
|
|
1217
|
+
cameraPreset: sh.cameraPreset,
|
|
1218
|
+
approved: true,
|
|
1219
|
+
durationSec: sh.durationSec
|
|
1220
|
+
}))
|
|
1221
|
+
}))
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
const inFlight = count(film, (sh) => sh.status === "clip-generating");
|
|
1225
|
+
const clipQueue = all.filter((sh) => (sh.status === "frame-ready" || sh.status === "failed" && Boolean(sh.frameUrl)) && framesDone);
|
|
1226
|
+
for (const sh of clipQueue.slice(0, Math.max(0, 4 - inFlight))) {
|
|
1227
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "clip" });
|
|
1228
|
+
}
|
|
1229
|
+
const clipsDone = all.length > 0 && all.every((sh) => sh.status === "clip-ready");
|
|
1230
|
+
if (clipsDone) {
|
|
1231
|
+
const hasMusic = film.audioStems.some((st) => st.kind === "music");
|
|
1232
|
+
if (soundtrack !== "none" && !hasMusic) {
|
|
1233
|
+
if (musicJobId) {
|
|
1234
|
+
const job = await ctx.client.getJob(musicJobId);
|
|
1235
|
+
const out = job.output;
|
|
1236
|
+
const outUrl = out?.audio ?? out?.video ?? out?.images?.[0];
|
|
1237
|
+
if (job.status === "completed" && typeof outUrl === "string") {
|
|
1238
|
+
await filmApi(ctx, "PATCH", `/api/films/${filmId}`, { audioStems: [{ kind: "music", url: outUrl }] });
|
|
1239
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/export`, {});
|
|
1240
|
+
return { kind: "text", text: `Score attached \u2014 export started. Poll again in ~60s with {"film_id": "${filmId}", "action": "status"} for the finished film URL.` };
|
|
1241
|
+
}
|
|
1242
|
+
if (job.status === "failed") {
|
|
1243
|
+
return { kind: "text", text: `The score failed (${job.error ?? "unknown"}) and was refunded. Poll again to retry, or pass soundtrack "none" to export without music.` };
|
|
1244
|
+
}
|
|
1245
|
+
return { kind: "text", text: `Score still composing. Poll again in ~20s with {"film_id": "${filmId}", "action": "status", "music_job_id": "${musicJobId}"}.` };
|
|
1246
|
+
}
|
|
1247
|
+
const body = soundtrack === "scene-synced" ? buildScorePlanBody(film) : buildFlatMusicBody(film, all);
|
|
1248
|
+
const dispatch = await ctx.client.run("text-to-music", body);
|
|
1249
|
+
return { kind: "text", text: `All ${all.length} clips ready \u2014 the ${soundtrack} score is composing (charged like any music generation). Poll again in ~20s with {"film_id": "${filmId}", "action": "status", "music_job_id": "${dispatch.jobId}"}.` };
|
|
1250
|
+
}
|
|
1251
|
+
await filmApi(ctx, "POST", `/api/films/${filmId}/export`, {});
|
|
1252
|
+
return { kind: "text", text: `All clips ready \u2014 export started (${progressLine(film)}). Poll again in ~60s with action "status" for the finished film URL.` };
|
|
1253
|
+
}
|
|
1254
|
+
const failed = all.filter((sh) => sh.status === "failed");
|
|
1255
|
+
return {
|
|
1256
|
+
kind: "text",
|
|
1257
|
+
text: [
|
|
1258
|
+
`In progress: ${progressLine(film)}.`,
|
|
1259
|
+
failed.length > 0 ? `${failed.length} shot(s) failed and were re-queued (${failed[0]?.errorMessage?.slice(0, 100) ?? ""}).` : "",
|
|
1260
|
+
`Poll again in ~30s with {"film_id": "${filmId}", "action": "status"}.`
|
|
1261
|
+
].filter(Boolean).join("\n")
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
936
1264
|
async function run8(input, ctx) {
|
|
1265
|
+
const action = input.action === "plan" && input.film_id ? "status" : input.action;
|
|
1266
|
+
if (action === "plan") return plan(input, ctx);
|
|
1267
|
+
if (!input.film_id) return { kind: "text", text: "produce/status need the film_id returned by the plan step." };
|
|
1268
|
+
if (action === "produce") return produce(input.film_id, ctx);
|
|
1269
|
+
return advance(input.film_id, input.soundtrack, input.music_job_id, ctx);
|
|
1270
|
+
}
|
|
1271
|
+
var LOOKS, InputSchema8, InputJsonSchema8, shots, count, LOOK_MUSIC, ARC, filmSkill;
|
|
1272
|
+
var init_film = __esm({
|
|
1273
|
+
"src/skills/film.ts"() {
|
|
1274
|
+
LOOKS = ["live-action", "anime", "3d-animation", "noir", "horror", "sci-fi", "documentary", "western", "romance", "romantic-comedy", "comedy"];
|
|
1275
|
+
InputSchema8 = z.object({
|
|
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."),
|
|
1277
|
+
script: z.string().max(2e4).optional().describe("A full screenplay to plan instead of an idea (sluglines, action lines, dialogue)."),
|
|
1278
|
+
look: z.enum(LOOKS).default("live-action").describe("Visual style carried through frames, clips, and soundtrack."),
|
|
1279
|
+
aspect: z.enum(["16:9", "9:16"]).default("16:9"),
|
|
1280
|
+
length: z.enum(["30s", "60s", "2-3min", "5min"]).default("60s").describe("Target length; drives the shot count (~6/12/24/60 shots)."),
|
|
1281
|
+
film_id: z.string().max(40).optional().describe("Existing film id (from a previous call) for produce/status."),
|
|
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."),
|
|
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."),
|
|
1284
|
+
music_job_id: z.string().max(80).optional().describe("Internal: pass back the music job id from a previous status reply so the composing score is attached instead of re-charged.")
|
|
1285
|
+
});
|
|
1286
|
+
InputJsonSchema8 = {
|
|
1287
|
+
type: "object",
|
|
1288
|
+
properties: {
|
|
1289
|
+
idea: { type: "string", description: "One-to-three sentence film idea. The studio writes the screenplay (free)." },
|
|
1290
|
+
script: { type: "string", description: "A full screenplay to plan instead of an idea." },
|
|
1291
|
+
look: { type: "string", enum: [...LOOKS], description: "Visual style. Default live-action." },
|
|
1292
|
+
aspect: { type: "string", enum: ["16:9", "9:16"], description: "Default 16:9." },
|
|
1293
|
+
length: { type: "string", enum: ["30s", "60s", "2-3min", "5min"], description: "Target length (~6/12/24/60 shots). Default 60s." },
|
|
1294
|
+
film_id: { type: "string", description: "Existing film id for produce/status." },
|
|
1295
|
+
action: { type: "string", enum: ["plan", "produce", "status"], description: "plan (free) | produce (charged) | status (poll every ~30s)." },
|
|
1296
|
+
soundtrack: { type: "string", enum: ["scene-synced", "flat", "none"], description: "Default scene-synced." },
|
|
1297
|
+
music_job_id: { type: "string", description: "Pass back from a previous status reply while the score is composing." }
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
shots = (f) => f.scenes.flatMap((s) => s.shots);
|
|
1301
|
+
count = (f, pred) => shots(f).filter(pred).length;
|
|
1302
|
+
LOOK_MUSIC = {
|
|
1303
|
+
"live-action": "modern cinematic underscore, warm strings and soft felt piano, subtle rhythmic pulse, slow emotional build",
|
|
1304
|
+
anime: "sweeping anime film score, lush strings and piano, taiko drum accents, soaring heartfelt theme",
|
|
1305
|
+
"3d-animation": "playful heartfelt family-animation score, pizzicato strings, celesta and warm woodwinds, gentle sense of wonder",
|
|
1306
|
+
noir: "smoky film-noir jazz underscore, brushed drums, upright bass, distant muted trumpet, slow simmering tension",
|
|
1307
|
+
horror: "sparse horror underscore, low cello drones, dissonant string swells, creeping heartbeat pulse, unsettling quiet",
|
|
1308
|
+
"sci-fi": "atmospheric science-fiction score, analog synth pads, deep sub-bass pulses, shimmering arpeggios, vast and cold",
|
|
1309
|
+
documentary: "understated documentary score, felt piano, soft string beds, organic percussion textures, reflective and true",
|
|
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"
|
|
1314
|
+
};
|
|
1315
|
+
ARC = {
|
|
1316
|
+
opening: ["establishing the theme", "restrained", "quiet anticipation"],
|
|
1317
|
+
middle: ["building tension", "rising intensity", "forward motion"],
|
|
1318
|
+
finale: ["climactic", "full arrangement", "emotional peak resolving to stillness"]
|
|
1319
|
+
};
|
|
1320
|
+
filmSkill = {
|
|
1321
|
+
name: "pixeldojo_film",
|
|
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).",
|
|
1323
|
+
inputZod: InputSchema8,
|
|
1324
|
+
inputJsonSchema: InputJsonSchema8,
|
|
1325
|
+
allowedModels: [],
|
|
1326
|
+
defaultModel: "",
|
|
1327
|
+
run: run8
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
async function run9(input, ctx) {
|
|
937
1332
|
const productImages = [
|
|
938
1333
|
...input.product_image_urls ?? [],
|
|
939
1334
|
...await Promise.all((input.product_image_paths ?? []).map((p) => uploadLocalFile(p, ctx.client)))
|
|
@@ -976,13 +1371,13 @@ async function run8(input, ctx) {
|
|
|
976
1371
|
(urls) => urls.length === 0 ? `Ad job ${submitted.jobId} completed but returned no assets.` : `Ad rendered (style: ${styleLabel}${submitted.ad?.product?.name ? `, product: ${submitted.ad.product.name}` : ""}). Video: ${urls[0]}`
|
|
977
1372
|
);
|
|
978
1373
|
}
|
|
979
|
-
var
|
|
1374
|
+
var InputSchema9, InputJsonSchema9, adSkill;
|
|
980
1375
|
var init_ad = __esm({
|
|
981
1376
|
"src/skills/ad.ts"() {
|
|
982
1377
|
init_types();
|
|
983
1378
|
init_poll();
|
|
984
1379
|
init_upload_helpers();
|
|
985
|
-
|
|
1380
|
+
InputSchema9 = z.object({
|
|
986
1381
|
style: z.string().max(80).optional().describe(
|
|
987
1382
|
"Ad style slug (default ugc-testimonial). Catalog: GET /api/v1/marketing/presets \u2014 e.g. ugc-testimonial, unboxing-reveal, unboxing-asmr, virtual-try-on, street-style, product-showcase, macro-details, crush-test, hyper-motion, before-after, day-in-the-life, the-gift, tv-spot."
|
|
988
1383
|
),
|
|
@@ -1008,7 +1403,7 @@ var init_ad = __esm({
|
|
|
1008
1403
|
path: ["product_url"]
|
|
1009
1404
|
}
|
|
1010
1405
|
);
|
|
1011
|
-
|
|
1406
|
+
InputJsonSchema9 = {
|
|
1012
1407
|
type: "object",
|
|
1013
1408
|
properties: {
|
|
1014
1409
|
style: {
|
|
@@ -1062,18 +1457,18 @@ var init_ad = __esm({
|
|
|
1062
1457
|
// Zod refine; no root-level oneOf (Grok rejects non-object root unions).
|
|
1063
1458
|
};
|
|
1064
1459
|
adSkill = {
|
|
1065
|
-
name: "
|
|
1066
|
-
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
|
|
1067
|
-
inputZod:
|
|
1068
|
-
inputJsonSchema:
|
|
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.",
|
|
1462
|
+
inputZod: InputSchema9,
|
|
1463
|
+
inputJsonSchema: InputJsonSchema9,
|
|
1069
1464
|
// Composition skill — the server routes to the ad engine internally.
|
|
1070
1465
|
allowedModels: [],
|
|
1071
1466
|
defaultModel: "",
|
|
1072
|
-
run:
|
|
1467
|
+
run: run9
|
|
1073
1468
|
};
|
|
1074
1469
|
}
|
|
1075
1470
|
});
|
|
1076
|
-
async function
|
|
1471
|
+
async function run10(input, ctx) {
|
|
1077
1472
|
const client = ctx.client;
|
|
1078
1473
|
const result = await client.runCampaign(input);
|
|
1079
1474
|
if (result.kind === "campaign") {
|
|
@@ -1095,15 +1490,15 @@ async function run9(input, ctx) {
|
|
|
1095
1490
|
}
|
|
1096
1491
|
return {
|
|
1097
1492
|
kind: "handoff",
|
|
1098
|
-
text: result.message ?? `Campaign ${result.campaignId} queued. Poll via
|
|
1493
|
+
text: result.message ?? `Campaign ${result.campaignId} queued. Poll via pixeldojo_campaign_status.`,
|
|
1099
1494
|
jobId: result.campaignId,
|
|
1100
1495
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
1101
1496
|
};
|
|
1102
1497
|
}
|
|
1103
|
-
var
|
|
1498
|
+
var InputSchema10, InputJsonSchema10, campaignSkill;
|
|
1104
1499
|
var init_campaign = __esm({
|
|
1105
1500
|
"src/skills/campaign.ts"() {
|
|
1106
|
-
|
|
1501
|
+
InputSchema10 = z.object({
|
|
1107
1502
|
productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
|
|
1108
1503
|
product: z.object({
|
|
1109
1504
|
name: z.string().min(1).max(200),
|
|
@@ -1119,7 +1514,7 @@ var init_campaign = __esm({
|
|
|
1119
1514
|
message: "productUrl or product is required",
|
|
1120
1515
|
path: ["productUrl"]
|
|
1121
1516
|
});
|
|
1122
|
-
|
|
1517
|
+
InputJsonSchema10 = {
|
|
1123
1518
|
type: "object",
|
|
1124
1519
|
properties: {
|
|
1125
1520
|
productUrl: {
|
|
@@ -1148,19 +1543,19 @@ var init_campaign = __esm({
|
|
|
1148
1543
|
oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
|
|
1149
1544
|
};
|
|
1150
1545
|
campaignSkill = {
|
|
1151
|
-
name: "
|
|
1152
|
-
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
|
|
1153
|
-
inputZod:
|
|
1154
|
-
inputJsonSchema:
|
|
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.",
|
|
1548
|
+
inputZod: InputSchema10,
|
|
1549
|
+
inputJsonSchema: InputJsonSchema10,
|
|
1155
1550
|
// Composition skill — internal sub-jobs route to per-tool models, no
|
|
1156
1551
|
// single allowed-models list applies here.
|
|
1157
1552
|
allowedModels: [],
|
|
1158
1553
|
defaultModel: "",
|
|
1159
|
-
run:
|
|
1554
|
+
run: run10
|
|
1160
1555
|
};
|
|
1161
1556
|
}
|
|
1162
1557
|
});
|
|
1163
|
-
async function
|
|
1558
|
+
async function run11(input, ctx) {
|
|
1164
1559
|
const client = ctx.client;
|
|
1165
1560
|
const result = await client.getCampaign(input.campaignId);
|
|
1166
1561
|
if (result.kind === "campaign") {
|
|
@@ -1190,40 +1585,40 @@ async function run10(input, ctx) {
|
|
|
1190
1585
|
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
1191
1586
|
};
|
|
1192
1587
|
}
|
|
1193
|
-
var
|
|
1588
|
+
var InputSchema11, InputJsonSchema11, campaignStatusSkill;
|
|
1194
1589
|
var init_campaign_status = __esm({
|
|
1195
1590
|
"src/skills/campaign-status.ts"() {
|
|
1196
|
-
|
|
1197
|
-
campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior
|
|
1591
|
+
InputSchema11 = z.object({
|
|
1592
|
+
campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo_campaign handoff.")
|
|
1198
1593
|
});
|
|
1199
|
-
|
|
1594
|
+
InputJsonSchema11 = {
|
|
1200
1595
|
type: "object",
|
|
1201
1596
|
properties: {
|
|
1202
1597
|
campaignId: {
|
|
1203
1598
|
type: "string",
|
|
1204
|
-
description: "Campaign id returned from a prior
|
|
1599
|
+
description: "Campaign id returned from a prior pixeldojo_campaign handoff."
|
|
1205
1600
|
}
|
|
1206
1601
|
},
|
|
1207
1602
|
required: ["campaignId"]
|
|
1208
1603
|
};
|
|
1209
1604
|
campaignStatusSkill = {
|
|
1210
|
-
name: "
|
|
1211
|
-
description: "Poll a campaign by campaignId returned from
|
|
1212
|
-
inputZod:
|
|
1213
|
-
inputJsonSchema:
|
|
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.",
|
|
1607
|
+
inputZod: InputSchema11,
|
|
1608
|
+
inputJsonSchema: InputJsonSchema11,
|
|
1214
1609
|
allowedModels: [],
|
|
1215
1610
|
defaultModel: "",
|
|
1216
|
-
run:
|
|
1611
|
+
run: run11
|
|
1217
1612
|
};
|
|
1218
1613
|
}
|
|
1219
1614
|
});
|
|
1220
|
-
async function
|
|
1615
|
+
async function run12(input, ctx) {
|
|
1221
1616
|
let url;
|
|
1222
1617
|
try {
|
|
1223
1618
|
url = await uploadLocalFile(input.path, ctx.client);
|
|
1224
1619
|
} catch (err) {
|
|
1225
1620
|
if (err instanceof SkillUploadError) {
|
|
1226
|
-
return { kind: "text", text: `
|
|
1621
|
+
return { kind: "text", text: `pixeldojo_upload: ${err.userMessage}` };
|
|
1227
1622
|
}
|
|
1228
1623
|
throw err;
|
|
1229
1624
|
}
|
|
@@ -1233,16 +1628,16 @@ async function run11(input, ctx) {
|
|
|
1233
1628
|
(temp storage; auto-deletes in ~24h \u2014 pass this URL as image_url to :generate or :edit)`
|
|
1234
1629
|
};
|
|
1235
1630
|
}
|
|
1236
|
-
var
|
|
1631
|
+
var InputSchema12, InputJsonSchema12, uploadSkill;
|
|
1237
1632
|
var init_upload = __esm({
|
|
1238
1633
|
"src/skills/upload.ts"() {
|
|
1239
1634
|
init_upload_helpers();
|
|
1240
|
-
|
|
1635
|
+
InputSchema12 = z.object({
|
|
1241
1636
|
path: z.string().min(1).max(4096).describe(
|
|
1242
1637
|
"Absolute local file path to upload. JPEG, PNG, WebP, GIF (\u226450MB) or MP4, WebM, MOV (\u2264200MB). Returns a public URL on the temp CDN; storage auto-expires after 24 hours."
|
|
1243
1638
|
)
|
|
1244
1639
|
});
|
|
1245
|
-
|
|
1640
|
+
InputJsonSchema12 = {
|
|
1246
1641
|
type: "object",
|
|
1247
1642
|
properties: {
|
|
1248
1643
|
path: {
|
|
@@ -1253,22 +1648,22 @@ var init_upload = __esm({
|
|
|
1253
1648
|
required: ["path"]
|
|
1254
1649
|
};
|
|
1255
1650
|
uploadSkill = {
|
|
1256
|
-
name: "
|
|
1651
|
+
name: "pixeldojo_upload",
|
|
1257
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.",
|
|
1258
|
-
inputZod:
|
|
1259
|
-
inputJsonSchema:
|
|
1653
|
+
inputZod: InputSchema12,
|
|
1654
|
+
inputJsonSchema: InputJsonSchema12,
|
|
1260
1655
|
allowedModels: [],
|
|
1261
1656
|
defaultModel: "",
|
|
1262
|
-
run:
|
|
1657
|
+
run: run12
|
|
1263
1658
|
};
|
|
1264
1659
|
}
|
|
1265
1660
|
});
|
|
1266
|
-
async function
|
|
1661
|
+
async function run13(input, ctx) {
|
|
1267
1662
|
const requested = input.model ?? DEFAULT_MODEL6;
|
|
1268
1663
|
if (!ALLOWED4.includes(requested)) {
|
|
1269
1664
|
return {
|
|
1270
1665
|
kind: "text",
|
|
1271
|
-
text: `Unknown model "${requested}" for
|
|
1666
|
+
text: `Unknown model "${requested}" for pixeldojo_audio. Allowed: ${ALLOWED4.join(", ")}.`
|
|
1272
1667
|
};
|
|
1273
1668
|
}
|
|
1274
1669
|
const apiId = requested;
|
|
@@ -1279,7 +1674,7 @@ async function run12(input, ctx) {
|
|
|
1279
1674
|
audioUrls = [...audioUrls, ...uploaded].slice(0, 3);
|
|
1280
1675
|
} catch (err) {
|
|
1281
1676
|
if (err instanceof SkillUploadError) {
|
|
1282
|
-
return { kind: "text", text: `
|
|
1677
|
+
return { kind: "text", text: `pixeldojo_audio (upload): ${err.userMessage}` };
|
|
1283
1678
|
}
|
|
1284
1679
|
throw err;
|
|
1285
1680
|
}
|
|
@@ -1312,7 +1707,7 @@ async function run12(input, ctx) {
|
|
|
1312
1707
|
(urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
|
|
1313
1708
|
);
|
|
1314
1709
|
}
|
|
1315
|
-
var ALLOWED4, DEFAULT_MODEL6,
|
|
1710
|
+
var ALLOWED4, DEFAULT_MODEL6, InputSchema13, InputJsonSchema13, audioSkill;
|
|
1316
1711
|
var init_audio = __esm({
|
|
1317
1712
|
"src/skills/audio.ts"() {
|
|
1318
1713
|
init_poll();
|
|
@@ -1320,7 +1715,7 @@ var init_audio = __esm({
|
|
|
1320
1715
|
init_types();
|
|
1321
1716
|
ALLOWED4 = ["seed-audio", "text-to-speech"];
|
|
1322
1717
|
DEFAULT_MODEL6 = "seed-audio";
|
|
1323
|
-
|
|
1718
|
+
InputSchema13 = z.object({
|
|
1324
1719
|
prompt: z.string().min(1).describe(
|
|
1325
1720
|
"A script for speech, or a scene description for cinematic audio (dialogue + effects + music). Reference uploaded clips as @Audio1..@Audio3."
|
|
1326
1721
|
),
|
|
@@ -1331,7 +1726,7 @@ var init_audio = __esm({
|
|
|
1331
1726
|
output_format: z.string().optional().describe("seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3, wav, flac, pcm."),
|
|
1332
1727
|
speed: z.number().min(0.5).max(2).optional().describe("Speech speed multiplier (0.5\u20132).")
|
|
1333
1728
|
});
|
|
1334
|
-
|
|
1729
|
+
InputJsonSchema13 = {
|
|
1335
1730
|
type: "object",
|
|
1336
1731
|
properties: {
|
|
1337
1732
|
prompt: { type: "string" },
|
|
@@ -1345,17 +1740,17 @@ var init_audio = __esm({
|
|
|
1345
1740
|
required: ["prompt"]
|
|
1346
1741
|
};
|
|
1347
1742
|
audioSkill = {
|
|
1348
|
-
name: "
|
|
1743
|
+
name: "pixeldojo_audio",
|
|
1349
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).",
|
|
1350
|
-
inputZod:
|
|
1351
|
-
inputJsonSchema:
|
|
1745
|
+
inputZod: InputSchema13,
|
|
1746
|
+
inputJsonSchema: InputJsonSchema13,
|
|
1352
1747
|
allowedModels: [...ALLOWED4],
|
|
1353
1748
|
defaultModel: DEFAULT_MODEL6,
|
|
1354
|
-
run:
|
|
1749
|
+
run: run13
|
|
1355
1750
|
};
|
|
1356
1751
|
}
|
|
1357
1752
|
});
|
|
1358
|
-
async function
|
|
1753
|
+
async function run14(input, ctx) {
|
|
1359
1754
|
const res = await ctx.client.listLibrary({
|
|
1360
1755
|
q: input.q,
|
|
1361
1756
|
model: input.model,
|
|
@@ -1365,7 +1760,7 @@ async function run13(input, ctx) {
|
|
|
1365
1760
|
if (res.items.length === 0) {
|
|
1366
1761
|
return {
|
|
1367
1762
|
kind: "text",
|
|
1368
|
-
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
|
|
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."
|
|
1369
1764
|
};
|
|
1370
1765
|
}
|
|
1371
1766
|
const lines = res.items.map((it) => {
|
|
@@ -1386,16 +1781,16 @@ ${lines.join("\n")}
|
|
|
1386
1781
|
Pass any URL back into :generate, :edit, :character, or :upscale as a reference input.`
|
|
1387
1782
|
};
|
|
1388
1783
|
}
|
|
1389
|
-
var
|
|
1784
|
+
var InputSchema14, InputJsonSchema14, librarySkill;
|
|
1390
1785
|
var init_library = __esm({
|
|
1391
1786
|
"src/skills/library.ts"() {
|
|
1392
|
-
|
|
1787
|
+
InputSchema14 = z.object({
|
|
1393
1788
|
q: z.string().optional().describe("Substring match against the prompt that produced the asset."),
|
|
1394
1789
|
model: z.string().optional().describe("Restrict to a single model (apiId)."),
|
|
1395
1790
|
modality: z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
|
|
1396
1791
|
limit: z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
|
|
1397
1792
|
});
|
|
1398
|
-
|
|
1793
|
+
InputJsonSchema14 = {
|
|
1399
1794
|
type: "object",
|
|
1400
1795
|
properties: {
|
|
1401
1796
|
q: { type: "string" },
|
|
@@ -1406,17 +1801,17 @@ var init_library = __esm({
|
|
|
1406
1801
|
required: []
|
|
1407
1802
|
};
|
|
1408
1803
|
librarySkill = {
|
|
1409
|
-
name: "
|
|
1410
|
-
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
|
|
1411
|
-
inputZod:
|
|
1412
|
-
inputJsonSchema:
|
|
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.",
|
|
1806
|
+
inputZod: InputSchema14,
|
|
1807
|
+
inputJsonSchema: InputJsonSchema14,
|
|
1413
1808
|
allowedModels: [],
|
|
1414
1809
|
defaultModel: "",
|
|
1415
|
-
run:
|
|
1810
|
+
run: run14
|
|
1416
1811
|
};
|
|
1417
1812
|
}
|
|
1418
1813
|
});
|
|
1419
|
-
async function
|
|
1814
|
+
async function run15(input, ctx) {
|
|
1420
1815
|
const res = await ctx.client.saveMedia({
|
|
1421
1816
|
url: input.url,
|
|
1422
1817
|
prompt: input.prompt,
|
|
@@ -1427,20 +1822,20 @@ async function run14(input, ctx) {
|
|
|
1427
1822
|
return {
|
|
1428
1823
|
kind: "text",
|
|
1429
1824
|
text: `Saved to My Media (permanent): ${res.url}
|
|
1430
|
-
It will appear at https://pixeldojo.ai/my-images and in
|
|
1825
|
+
It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
|
|
1431
1826
|
};
|
|
1432
1827
|
}
|
|
1433
|
-
var
|
|
1828
|
+
var InputSchema15, InputJsonSchema15, saveSkill;
|
|
1434
1829
|
var init_save = __esm({
|
|
1435
1830
|
"src/skills/save.ts"() {
|
|
1436
|
-
|
|
1831
|
+
InputSchema15 = z.object({
|
|
1437
1832
|
url: z.string().url().describe("The asset URL to save (image or video)."),
|
|
1438
1833
|
prompt: z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
|
|
1439
1834
|
tool_name: z.string().optional().describe("Tool/model slug that produced it."),
|
|
1440
1835
|
modality: z.enum(["image", "video", "audio"]).optional(),
|
|
1441
1836
|
job_id: z.string().optional().describe("The jobId that produced this asset, when known.")
|
|
1442
1837
|
});
|
|
1443
|
-
|
|
1838
|
+
InputJsonSchema15 = {
|
|
1444
1839
|
type: "object",
|
|
1445
1840
|
properties: {
|
|
1446
1841
|
url: { type: "string", format: "uri" },
|
|
@@ -1452,13 +1847,13 @@ var init_save = __esm({
|
|
|
1452
1847
|
required: ["url"]
|
|
1453
1848
|
};
|
|
1454
1849
|
saveSkill = {
|
|
1455
|
-
name: "
|
|
1850
|
+
name: "pixeldojo_save",
|
|
1456
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.",
|
|
1457
|
-
inputZod:
|
|
1458
|
-
inputJsonSchema:
|
|
1852
|
+
inputZod: InputSchema15,
|
|
1853
|
+
inputJsonSchema: InputJsonSchema15,
|
|
1459
1854
|
allowedModels: [],
|
|
1460
1855
|
defaultModel: "",
|
|
1461
|
-
run:
|
|
1856
|
+
run: run15
|
|
1462
1857
|
};
|
|
1463
1858
|
}
|
|
1464
1859
|
});
|
|
@@ -1500,7 +1895,7 @@ async function runSave(input, ctx) {
|
|
|
1500
1895
|
});
|
|
1501
1896
|
return {
|
|
1502
1897
|
kind: "text",
|
|
1503
|
-
text: res.created ? `Saved workflow "${res.name}" (${res.steps} step${res.steps === 1 ? "" : "s"}). Run it with
|
|
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"}).`
|
|
1504
1899
|
};
|
|
1505
1900
|
}
|
|
1506
1901
|
async function runList(_input, ctx) {
|
|
@@ -1508,7 +1903,7 @@ async function runList(_input, ctx) {
|
|
|
1508
1903
|
if (res.count === 0) {
|
|
1509
1904
|
return {
|
|
1510
1905
|
kind: "text",
|
|
1511
|
-
text: "No saved workflows yet. Save one with
|
|
1906
|
+
text: "No saved workflows yet. Save one with pixeldojo_save_workflow { name, steps: [{ skill, args }] }."
|
|
1512
1907
|
};
|
|
1513
1908
|
}
|
|
1514
1909
|
const lines = res.workflows.map(
|
|
@@ -1577,7 +1972,7 @@ async function runWorkflow(input, ctx) {
|
|
|
1577
1972
|
} else if (result.kind === "handoff") {
|
|
1578
1973
|
return {
|
|
1579
1974
|
kind: "text",
|
|
1580
|
-
text: `Workflow "${workflow.name}" is still on step ${i + 1} (${step.skill}) after an extended wait. Poll with
|
|
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.`
|
|
1581
1976
|
};
|
|
1582
1977
|
} else {
|
|
1583
1978
|
outputs.push(`step ${i + 1} (${step.skill}): done (no asset URL)`);
|
|
@@ -1596,7 +1991,7 @@ var init_workflows = __esm({
|
|
|
1596
1991
|
init_types();
|
|
1597
1992
|
WORKFLOW_STEP_EXTRA_BUDGET_MS = 6e4;
|
|
1598
1993
|
StepSchema = z.object({
|
|
1599
|
-
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)$/, {
|
|
1600
1995
|
message: "Runnable skills only: generate, edit, character, storyboard, upscale, audio, upload, from_url."
|
|
1601
1996
|
}),
|
|
1602
1997
|
args: z.record(z.string(), z.unknown()).optional()
|
|
@@ -1622,7 +2017,7 @@ var init_workflows = __esm({
|
|
|
1622
2017
|
required: ["skill"]
|
|
1623
2018
|
};
|
|
1624
2019
|
saveWorkflowSkill = {
|
|
1625
|
-
name: "
|
|
2020
|
+
name: "pixeldojo_save_workflow",
|
|
1626
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.",
|
|
1627
2022
|
inputZod: SaveInputSchema,
|
|
1628
2023
|
inputJsonSchema: {
|
|
@@ -1639,7 +2034,7 @@ var init_workflows = __esm({
|
|
|
1639
2034
|
run: runSave
|
|
1640
2035
|
};
|
|
1641
2036
|
runWorkflowSkill = {
|
|
1642
|
-
name: "
|
|
2037
|
+
name: "pixeldojo_run_workflow",
|
|
1643
2038
|
description: "Run a saved workflow by name. Pass inputs for its {{input.<key>}} placeholders; each step feeds the next via {{prev}}.",
|
|
1644
2039
|
inputZod: RunInputSchema,
|
|
1645
2040
|
inputJsonSchema: {
|
|
@@ -1655,7 +2050,7 @@ var init_workflows = __esm({
|
|
|
1655
2050
|
run: runWorkflow
|
|
1656
2051
|
};
|
|
1657
2052
|
listWorkflowsSkill = {
|
|
1658
|
-
name: "
|
|
2053
|
+
name: "pixeldojo_list_workflows",
|
|
1659
2054
|
description: "List your saved workflows with step counts and run totals.",
|
|
1660
2055
|
inputZod: ListInputSchema,
|
|
1661
2056
|
inputJsonSchema: { type: "object", properties: {}, required: [] },
|
|
@@ -1676,6 +2071,7 @@ __export(skills_exports, {
|
|
|
1676
2071
|
campaignStatusSkill: () => campaignStatusSkill,
|
|
1677
2072
|
characterSkill: () => characterSkill,
|
|
1678
2073
|
editSkill: () => editSkill,
|
|
2074
|
+
filmSkill: () => filmSkill,
|
|
1679
2075
|
findSkill: () => findSkill,
|
|
1680
2076
|
fromUrlSkill: () => fromUrlSkill,
|
|
1681
2077
|
generateSkill: () => generateSkill,
|
|
@@ -1690,7 +2086,8 @@ __export(skills_exports, {
|
|
|
1690
2086
|
upscaleSkill: () => upscaleSkill
|
|
1691
2087
|
});
|
|
1692
2088
|
function findSkill(name) {
|
|
1693
|
-
|
|
2089
|
+
const wanted = name.replace(/^pixeldojo:/, "pixeldojo_");
|
|
2090
|
+
return ALL_SKILLS.find((s) => s.name === wanted);
|
|
1694
2091
|
}
|
|
1695
2092
|
var ALL_SKILLS;
|
|
1696
2093
|
var init_skills = __esm({
|
|
@@ -1702,6 +2099,7 @@ var init_skills = __esm({
|
|
|
1702
2099
|
init_upscale();
|
|
1703
2100
|
init_status();
|
|
1704
2101
|
init_from_url();
|
|
2102
|
+
init_film();
|
|
1705
2103
|
init_ad();
|
|
1706
2104
|
init_campaign();
|
|
1707
2105
|
init_campaign_status();
|
|
@@ -1727,7 +2125,8 @@ var init_skills = __esm({
|
|
|
1727
2125
|
uploadSkill,
|
|
1728
2126
|
adSkill,
|
|
1729
2127
|
campaignSkill,
|
|
1730
|
-
campaignStatusSkill
|
|
2128
|
+
campaignStatusSkill,
|
|
2129
|
+
filmSkill
|
|
1731
2130
|
];
|
|
1732
2131
|
}
|
|
1733
2132
|
});
|
|
@@ -1782,7 +2181,7 @@ function makeRedactedLogger(target = console) {
|
|
|
1782
2181
|
}
|
|
1783
2182
|
|
|
1784
2183
|
// src/server.ts
|
|
1785
|
-
var PACKAGE_VERSION = "0.
|
|
2184
|
+
var PACKAGE_VERSION = "0.9.1" ;
|
|
1786
2185
|
function buildServer(options) {
|
|
1787
2186
|
if (!options.apiKey) {
|
|
1788
2187
|
throw new Error(
|
|
@@ -1849,6 +2248,8 @@ function buildServer(options) {
|
|
|
1849
2248
|
});
|
|
1850
2249
|
const ctx = {
|
|
1851
2250
|
client,
|
|
2251
|
+
apiKey: options.apiKey,
|
|
2252
|
+
baseUrl,
|
|
1852
2253
|
mcpVersion: version,
|
|
1853
2254
|
logger,
|
|
1854
2255
|
buildStatusUrl: (jobId) => `${baseUrl}/api/v1/jobs/${encodeURIComponent(jobId)}`
|