@pixeldojo/mcp 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +220 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +220 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +220 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +220 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -113,6 +113,9 @@ var InputSchema = z.object({
|
|
|
113
113
|
model: z.string().optional().describe(
|
|
114
114
|
'Override the model. Any apiId from /api/v1/models (e.g. "flux", "google-nano-banana", "veo-3.1", "kling-video"). Omit to use the default.'
|
|
115
115
|
),
|
|
116
|
+
preset: z.string().optional().describe(
|
|
117
|
+
'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean", "magazine-cover-text"). Browse the catalog at https://pixeldojo.ai/presets. Explicit fields override preset values.'
|
|
118
|
+
),
|
|
116
119
|
aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
|
|
117
120
|
duration: z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
|
|
118
121
|
image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools.")
|
|
@@ -126,6 +129,10 @@ var InputJsonSchema = {
|
|
|
126
129
|
type: "string",
|
|
127
130
|
description: 'Override the model. Any apiId from /api/v1/models (e.g. "flux", "google-nano-banana", "veo-3.1", "kling-video"). Omit to use the default.'
|
|
128
131
|
},
|
|
132
|
+
preset: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: 'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean"). Explicit fields override preset values.'
|
|
135
|
+
},
|
|
129
136
|
aspect_ratio: { type: "string" },
|
|
130
137
|
duration: { type: "integer" },
|
|
131
138
|
image_url: { type: "string", format: "uri" }
|
|
@@ -138,6 +145,7 @@ async function run(input, ctx) {
|
|
|
138
145
|
if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
|
|
139
146
|
if (input.duration !== void 0) body.duration = input.duration;
|
|
140
147
|
if (input.image_url) body.image_url = input.image_url;
|
|
148
|
+
if (input.preset) body.preset = input.preset;
|
|
141
149
|
const submitted = await ctx.client.run(apiId, body);
|
|
142
150
|
const result = await pollJobUntilDoneOrHandoff(
|
|
143
151
|
submitted.jobId,
|
|
@@ -358,6 +366,8 @@ var InputJsonSchema5 = {
|
|
|
358
366
|
},
|
|
359
367
|
required: ["jobId"]
|
|
360
368
|
};
|
|
369
|
+
var STUCK_HINT_MS = 5 * 60 * 1e3;
|
|
370
|
+
var ABANDON_HINT_MS = 12 * 60 * 1e3;
|
|
361
371
|
async function run5(input, ctx) {
|
|
362
372
|
const job = await ctx.client.getJob(input.jobId);
|
|
363
373
|
if (job.status === "completed") {
|
|
@@ -375,13 +385,44 @@ async function run5(input, ctx) {
|
|
|
375
385
|
text: `Job ${input.jobId} failed: ${errMsg}`
|
|
376
386
|
};
|
|
377
387
|
}
|
|
388
|
+
const apiId = job.apiId ?? "unknown";
|
|
389
|
+
const ageMs = jobAgeMs(job);
|
|
390
|
+
const ageLabel = formatAge(ageMs);
|
|
391
|
+
let hint;
|
|
392
|
+
if (ageMs >= ABANDON_HINT_MS) {
|
|
393
|
+
hint = `That's well past the typical generation window for any model on PixelDojo. The job is almost certainly stuck. Submit a fresh request, or open ${ctx.buildStatusUrl(input.jobId)} for the raw record.`;
|
|
394
|
+
} else if (ageMs >= STUCK_HINT_MS) {
|
|
395
|
+
hint = `That's longer than most generations should take. Poll once more, or submit a fresh request if the next check still says ${job.status}.`;
|
|
396
|
+
} else {
|
|
397
|
+
hint = `Try again in a few seconds, or follow ${ctx.buildStatusUrl(input.jobId)}.`;
|
|
398
|
+
}
|
|
378
399
|
return {
|
|
379
400
|
kind: "handoff",
|
|
380
|
-
text: `Job ${input.jobId} is
|
|
401
|
+
text: `Job ${input.jobId} (${apiId}) is ${job.status} after ${ageLabel}. ${hint}`,
|
|
381
402
|
jobId: input.jobId,
|
|
382
403
|
statusUrl: ctx.buildStatusUrl(input.jobId)
|
|
383
404
|
};
|
|
384
405
|
}
|
|
406
|
+
function jobAgeMs(job) {
|
|
407
|
+
const createdAt = job.createdAt;
|
|
408
|
+
if (!createdAt) return 0;
|
|
409
|
+
const t = Date.parse(createdAt);
|
|
410
|
+
if (Number.isNaN(t)) return 0;
|
|
411
|
+
const age = Date.now() - t;
|
|
412
|
+
return age >= 0 ? age : 0;
|
|
413
|
+
}
|
|
414
|
+
function formatAge(ms) {
|
|
415
|
+
if (ms < 6e4) return `${Math.max(1, Math.round(ms / 1e3))}s`;
|
|
416
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
417
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
418
|
+
const seconds = totalSeconds % 60;
|
|
419
|
+
if (minutes < 60) {
|
|
420
|
+
return seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;
|
|
421
|
+
}
|
|
422
|
+
const hours = Math.floor(minutes / 60);
|
|
423
|
+
const remMinutes = minutes % 60;
|
|
424
|
+
return remMinutes === 0 ? `${hours}h` : `${hours}h ${remMinutes}m`;
|
|
425
|
+
}
|
|
385
426
|
function extractAssetUrls2(job) {
|
|
386
427
|
if (job.assets && job.assets.length > 0) return job.assets.map((a) => a.url);
|
|
387
428
|
const output = job.output;
|
|
@@ -401,6 +442,180 @@ var statusSkill = {
|
|
|
401
442
|
defaultModel: "",
|
|
402
443
|
run: run5
|
|
403
444
|
};
|
|
445
|
+
var InputSchema6 = z.object({
|
|
446
|
+
url: z.string().min(1).max(2048).describe("Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata.")
|
|
447
|
+
});
|
|
448
|
+
var InputJsonSchema6 = {
|
|
449
|
+
type: "object",
|
|
450
|
+
properties: {
|
|
451
|
+
url: {
|
|
452
|
+
type: "string",
|
|
453
|
+
description: "Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata."
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
required: ["url"]
|
|
457
|
+
};
|
|
458
|
+
async function run6(input, ctx) {
|
|
459
|
+
const product = await ctx.client.extractProduct(input.url);
|
|
460
|
+
const summary = [
|
|
461
|
+
`Product: ${product.name}`,
|
|
462
|
+
product.description ? `Description: ${truncate(product.description, 280)}` : null,
|
|
463
|
+
`Images (${product.images.length}): ${product.images.slice(0, 3).join(", ")}${product.images.length > 3 ? `, +${product.images.length - 3} more` : ""}`,
|
|
464
|
+
`Source strategy: ${product.source}`
|
|
465
|
+
].filter(Boolean).join("\n");
|
|
466
|
+
return {
|
|
467
|
+
kind: "text",
|
|
468
|
+
text: summary
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function truncate(input, max) {
|
|
472
|
+
if (input.length <= max) return input;
|
|
473
|
+
return input.slice(0, max - 3) + "...";
|
|
474
|
+
}
|
|
475
|
+
var fromUrlSkill = {
|
|
476
|
+
name: "pixeldojo:from_url",
|
|
477
|
+
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.",
|
|
478
|
+
inputZod: InputSchema6,
|
|
479
|
+
inputJsonSchema: InputJsonSchema6,
|
|
480
|
+
// No model dispatch — this skill calls a server-side scrape, not a generation.
|
|
481
|
+
allowedModels: [],
|
|
482
|
+
defaultModel: "",
|
|
483
|
+
run: run6
|
|
484
|
+
};
|
|
485
|
+
var InputSchema7 = z.object({
|
|
486
|
+
productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
|
|
487
|
+
product: z.object({
|
|
488
|
+
name: z.string().min(1).max(200),
|
|
489
|
+
description: z.string().max(2e3).optional(),
|
|
490
|
+
images: z.array(z.string().url()).max(5).optional()
|
|
491
|
+
}).optional().describe("Manual product fields, used when productUrl is omitted."),
|
|
492
|
+
tone: z.string().max(120).optional().describe('Optional creative direction ("premium minimalist", "playful Y2K"). Threaded through every prompt.'),
|
|
493
|
+
variants: z.number().int().min(1).max(8).optional().describe("Lifestyle shots in addition to the hero (default 4, max 8)."),
|
|
494
|
+
includeVideo: z.boolean().optional().describe("Whether to also generate a vertical launch video (default true)."),
|
|
495
|
+
videoAspectRatio: z.enum(["9:16", "16:9", "1:1"]).optional().describe("Aspect ratio of the launch video (default 9:16 for Reels/TikTok)."),
|
|
496
|
+
videoMode: z.enum(["lifestyle", "cgi", "ugc", "skip"]).optional().describe("Video styling \u2014 lifestyle (default), cgi, ugc, or skip to omit video.")
|
|
497
|
+
}).refine((v) => v.productUrl || v.product, {
|
|
498
|
+
message: "productUrl or product is required",
|
|
499
|
+
path: ["productUrl"]
|
|
500
|
+
});
|
|
501
|
+
var InputJsonSchema7 = {
|
|
502
|
+
type: "object",
|
|
503
|
+
properties: {
|
|
504
|
+
productUrl: {
|
|
505
|
+
type: "string",
|
|
506
|
+
format: "uri",
|
|
507
|
+
description: "Product page URL. We extract name + description + images automatically."
|
|
508
|
+
},
|
|
509
|
+
product: {
|
|
510
|
+
type: "object",
|
|
511
|
+
properties: {
|
|
512
|
+
name: { type: "string" },
|
|
513
|
+
description: { type: "string" },
|
|
514
|
+
images: { type: "array", items: { type: "string", format: "uri" } }
|
|
515
|
+
},
|
|
516
|
+
required: ["name"]
|
|
517
|
+
},
|
|
518
|
+
tone: {
|
|
519
|
+
type: "string",
|
|
520
|
+
description: "Creative direction threaded through every prompt."
|
|
521
|
+
},
|
|
522
|
+
variants: { type: "integer", minimum: 1, maximum: 8 },
|
|
523
|
+
includeVideo: { type: "boolean" },
|
|
524
|
+
videoAspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1"] },
|
|
525
|
+
videoMode: { type: "string", enum: ["lifestyle", "cgi", "ugc", "skip"] }
|
|
526
|
+
},
|
|
527
|
+
oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
|
|
528
|
+
};
|
|
529
|
+
async function run7(input, ctx) {
|
|
530
|
+
const client = ctx.client;
|
|
531
|
+
const result = await client.runCampaign(input);
|
|
532
|
+
if (result.kind === "campaign") {
|
|
533
|
+
const assets = result.assets ?? [];
|
|
534
|
+
const lines = [
|
|
535
|
+
`Campaign ${result.campaignId} for "${result.product?.name ?? "product"}":`,
|
|
536
|
+
...assets.map(
|
|
537
|
+
(a) => ` - ${a.role}${a.variantIndex !== void 0 ? ` #${a.variantIndex}` : ""} (${a.kind}, ${a.apiId}): ${a.url}`
|
|
538
|
+
),
|
|
539
|
+
"",
|
|
540
|
+
`Total credits: ${result.totalCreditCost ?? 0}.`,
|
|
541
|
+
...result.warnings && result.warnings.length > 0 ? [`Warnings: ${result.warnings.join("; ")}`] : []
|
|
542
|
+
];
|
|
543
|
+
return {
|
|
544
|
+
kind: "assets",
|
|
545
|
+
text: lines.join("\n"),
|
|
546
|
+
assetUrls: assets.map((a) => a.url)
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
kind: "handoff",
|
|
551
|
+
text: result.message ?? `Campaign ${result.campaignId} queued. Poll via pixeldojo:campaign_status.`,
|
|
552
|
+
jobId: result.campaignId,
|
|
553
|
+
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
var campaignSkill = {
|
|
557
|
+
name: "pixeldojo:campaign",
|
|
558
|
+
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.",
|
|
559
|
+
inputZod: InputSchema7,
|
|
560
|
+
inputJsonSchema: InputJsonSchema7,
|
|
561
|
+
// Composition skill — internal sub-jobs route to per-tool models, no
|
|
562
|
+
// single allowed-models list applies here.
|
|
563
|
+
allowedModels: [],
|
|
564
|
+
defaultModel: "",
|
|
565
|
+
run: run7
|
|
566
|
+
};
|
|
567
|
+
var InputSchema8 = z.object({
|
|
568
|
+
campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
|
|
569
|
+
});
|
|
570
|
+
var InputJsonSchema8 = {
|
|
571
|
+
type: "object",
|
|
572
|
+
properties: {
|
|
573
|
+
campaignId: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description: "Campaign id returned from a prior pixeldojo:campaign handoff."
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
required: ["campaignId"]
|
|
579
|
+
};
|
|
580
|
+
async function run8(input, ctx) {
|
|
581
|
+
const client = ctx.client;
|
|
582
|
+
const result = await client.getCampaign(input.campaignId);
|
|
583
|
+
if (result.kind === "campaign") {
|
|
584
|
+
const assets = result.assets ?? [];
|
|
585
|
+
const lines = [
|
|
586
|
+
`Campaign ${result.campaignId} complete (${assets.length} assets):`,
|
|
587
|
+
...assets.map(
|
|
588
|
+
(a) => ` - ${a.role}${a.variantIndex !== void 0 ? ` #${a.variantIndex}` : ""} (${a.kind}, ${a.apiId}): ${a.url}`
|
|
589
|
+
),
|
|
590
|
+
"",
|
|
591
|
+
`Total credits: ${result.totalCreditCost ?? 0}.`,
|
|
592
|
+
...result.warnings && result.warnings.length > 0 ? [`Warnings: ${result.warnings.join("; ")}`] : []
|
|
593
|
+
];
|
|
594
|
+
return {
|
|
595
|
+
kind: "assets",
|
|
596
|
+
text: lines.join("\n"),
|
|
597
|
+
assetUrls: assets.map((a) => a.url)
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const stillRunning = (result.subJobs ?? []).filter(
|
|
601
|
+
(s) => s.status !== "completed" && s.status !== "failed"
|
|
602
|
+
);
|
|
603
|
+
return {
|
|
604
|
+
kind: "handoff",
|
|
605
|
+
text: `Campaign ${result.campaignId}: ` + (stillRunning.length > 0 ? `${stillRunning.length} of ${result.subJobs?.length ?? 0} sub-jobs still ${stillRunning[0].status}. Wait a few seconds, then poll again.` : `aggregate state pending. Poll again shortly.`),
|
|
606
|
+
jobId: result.campaignId,
|
|
607
|
+
statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
var campaignStatusSkill = {
|
|
611
|
+
name: "pixeldojo:campaign_status",
|
|
612
|
+
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.",
|
|
613
|
+
inputZod: InputSchema8,
|
|
614
|
+
inputJsonSchema: InputJsonSchema8,
|
|
615
|
+
allowedModels: [],
|
|
616
|
+
defaultModel: "",
|
|
617
|
+
run: run8
|
|
618
|
+
};
|
|
404
619
|
|
|
405
620
|
// src/skills/index.ts
|
|
406
621
|
var ALL_SKILLS = [
|
|
@@ -408,7 +623,10 @@ var ALL_SKILLS = [
|
|
|
408
623
|
characterSkill,
|
|
409
624
|
storyboardSkill,
|
|
410
625
|
upscaleSkill,
|
|
411
|
-
statusSkill
|
|
626
|
+
statusSkill,
|
|
627
|
+
fromUrlSkill,
|
|
628
|
+
campaignSkill,
|
|
629
|
+
campaignStatusSkill
|
|
412
630
|
];
|
|
413
631
|
function findSkill(name) {
|
|
414
632
|
return ALL_SKILLS.find((s) => s.name === name);
|