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