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