@pixeldojo/mcp 0.2.1 → 0.3.0
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 +383 -63
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +383 -63
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +383 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +383 -63
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,8 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { PixelDojoClient } from '@pixeldojo/sdk';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
+
import { stat, readFile } from 'fs/promises';
|
|
7
|
+
import { basename, extname } from 'path';
|
|
6
8
|
|
|
7
9
|
// src/server.ts
|
|
8
10
|
function makeAttributedClient(options) {
|
|
@@ -106,6 +108,71 @@ function extractAssetUrls(job) {
|
|
|
106
108
|
if (output?.video) return [output.video];
|
|
107
109
|
return [];
|
|
108
110
|
}
|
|
111
|
+
var MAX_IMAGE_BYTES = 50 * 1024 * 1024;
|
|
112
|
+
var MAX_VIDEO_BYTES = 200 * 1024 * 1024;
|
|
113
|
+
var EXT_TO_MIME = {
|
|
114
|
+
".jpg": "image/jpeg",
|
|
115
|
+
".jpeg": "image/jpeg",
|
|
116
|
+
".png": "image/png",
|
|
117
|
+
".webp": "image/webp",
|
|
118
|
+
".gif": "image/gif",
|
|
119
|
+
".mp4": "video/mp4",
|
|
120
|
+
".webm": "video/webm",
|
|
121
|
+
".mov": "video/quicktime"
|
|
122
|
+
};
|
|
123
|
+
var SkillUploadError = class extends Error {
|
|
124
|
+
constructor(userMessage) {
|
|
125
|
+
super(userMessage);
|
|
126
|
+
this.userMessage = userMessage;
|
|
127
|
+
this.name = "SkillUploadError";
|
|
128
|
+
}
|
|
129
|
+
userMessage;
|
|
130
|
+
};
|
|
131
|
+
async function uploadLocalFile(path, client) {
|
|
132
|
+
const ext = extname(path).toLowerCase();
|
|
133
|
+
const mime = EXT_TO_MIME[ext];
|
|
134
|
+
if (!mime) {
|
|
135
|
+
throw new SkillUploadError(
|
|
136
|
+
`Unsupported file extension "${ext || "(none)"}" for "${path}". Allowed: ${Object.keys(EXT_TO_MIME).join(", ")}.`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
let size;
|
|
140
|
+
try {
|
|
141
|
+
const info = await stat(path);
|
|
142
|
+
if (!info.isFile()) {
|
|
143
|
+
throw new SkillUploadError(`"${path}" is not a file.`);
|
|
144
|
+
}
|
|
145
|
+
size = info.size;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (err instanceof SkillUploadError) throw err;
|
|
148
|
+
const code = err?.code;
|
|
149
|
+
if (code === "ENOENT") {
|
|
150
|
+
throw new SkillUploadError(
|
|
151
|
+
`File not found at "${path}". Pass an absolute path.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
throw new SkillUploadError(
|
|
155
|
+
`Cannot read "${path}": ${err instanceof Error ? err.message : String(err)}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const isImage = mime.startsWith("image/");
|
|
159
|
+
const limit = isImage ? MAX_IMAGE_BYTES : MAX_VIDEO_BYTES;
|
|
160
|
+
if (size > limit) {
|
|
161
|
+
const gotMB = (size / 1024 / 1024).toFixed(1);
|
|
162
|
+
const limitMB = (limit / 1024 / 1024).toFixed(0);
|
|
163
|
+
throw new SkillUploadError(
|
|
164
|
+
`"${basename(path)}" is ${gotMB}MB; limit for ${isImage ? "images" : "videos"} is ${limitMB}MB. Downscale or re-encode and try again.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const buffer = await readFile(path);
|
|
168
|
+
const blob = new Blob([new Uint8Array(buffer)], { type: mime });
|
|
169
|
+
const filename = basename(path);
|
|
170
|
+
const response = await client.uploadImage(blob, filename);
|
|
171
|
+
return response.url;
|
|
172
|
+
}
|
|
173
|
+
async function uploadLocalFiles(paths, client) {
|
|
174
|
+
return Promise.all(paths.map((p) => uploadLocalFile(p, client)));
|
|
175
|
+
}
|
|
109
176
|
|
|
110
177
|
// src/skills/generate.ts
|
|
111
178
|
var InputSchema = z.object({
|
|
@@ -118,7 +185,10 @@ var InputSchema = z.object({
|
|
|
118
185
|
),
|
|
119
186
|
aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
|
|
120
187
|
duration: z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
|
|
121
|
-
image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools.")
|
|
188
|
+
image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
|
|
189
|
+
image_path: z.string().min(1).optional().describe(
|
|
190
|
+
"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."
|
|
191
|
+
)
|
|
122
192
|
});
|
|
123
193
|
var DEFAULT_MODEL = "google-nano-banana";
|
|
124
194
|
var InputJsonSchema = {
|
|
@@ -135,17 +205,67 @@ var InputJsonSchema = {
|
|
|
135
205
|
},
|
|
136
206
|
aspect_ratio: { type: "string" },
|
|
137
207
|
duration: { type: "integer" },
|
|
138
|
-
image_url: { type: "string", format: "uri" }
|
|
208
|
+
image_url: { type: "string", format: "uri" },
|
|
209
|
+
image_path: {
|
|
210
|
+
type: "string",
|
|
211
|
+
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."
|
|
212
|
+
}
|
|
139
213
|
},
|
|
140
214
|
required: ["prompt"]
|
|
141
215
|
};
|
|
142
|
-
|
|
143
|
-
const apiId = input.model ?? DEFAULT_MODEL;
|
|
216
|
+
function buildPayload(apiId, input) {
|
|
144
217
|
const body = { prompt: input.prompt };
|
|
145
218
|
if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
|
|
146
|
-
if (input.duration !== void 0) body.duration = input.duration;
|
|
147
|
-
if (input.image_url) body.image_url = input.image_url;
|
|
148
219
|
if (input.preset) body.preset = input.preset;
|
|
220
|
+
switch (apiId) {
|
|
221
|
+
case "grok-video-extend":
|
|
222
|
+
case "wan-video-edit":
|
|
223
|
+
case "seedance-video-edit":
|
|
224
|
+
if (input.image_url) {
|
|
225
|
+
body.video_url = input.image_url;
|
|
226
|
+
body.image_url = input.image_url;
|
|
227
|
+
}
|
|
228
|
+
if (input.duration !== void 0) {
|
|
229
|
+
body.video_duration = input.duration;
|
|
230
|
+
body.duration = input.duration;
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
default:
|
|
234
|
+
if (input.image_url) body.image_url = input.image_url;
|
|
235
|
+
if (input.duration !== void 0) body.duration = input.duration;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
return body;
|
|
239
|
+
}
|
|
240
|
+
async function run(input, ctx) {
|
|
241
|
+
const apiId = input.model ?? DEFAULT_MODEL;
|
|
242
|
+
let resolved = input;
|
|
243
|
+
if (!input.image_url && input.image_path) {
|
|
244
|
+
try {
|
|
245
|
+
const uploadedUrl = await uploadLocalFile(input.image_path, ctx.client);
|
|
246
|
+
resolved = { ...input, image_url: uploadedUrl };
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (err instanceof SkillUploadError) {
|
|
249
|
+
return { kind: "text", text: `pixeldojo:generate (upload): ${err.userMessage}` };
|
|
250
|
+
}
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (apiId === "grok-video-extend") {
|
|
255
|
+
if (!resolved.image_url) {
|
|
256
|
+
return {
|
|
257
|
+
kind: "text",
|
|
258
|
+
text: 'pixeldojo:generate with model="grok-video-extend" requires image_url (the source video URL to extend, or image_path for a local file).'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (resolved.duration === void 0) {
|
|
262
|
+
return {
|
|
263
|
+
kind: "text",
|
|
264
|
+
text: 'pixeldojo:generate with model="grok-video-extend" requires duration (extension length in seconds).'
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const body = buildPayload(apiId, resolved);
|
|
149
269
|
const submitted = await ctx.client.run(apiId, body);
|
|
150
270
|
const result = await pollJobUntilDoneOrHandoff(
|
|
151
271
|
submitted.jobId,
|
|
@@ -163,39 +283,191 @@ async function run(input, ctx) {
|
|
|
163
283
|
}
|
|
164
284
|
var generateSkill = {
|
|
165
285
|
name: "pixeldojo:generate",
|
|
166
|
-
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.",
|
|
286
|
+
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.",
|
|
167
287
|
inputZod: InputSchema,
|
|
168
288
|
inputJsonSchema: InputJsonSchema,
|
|
169
289
|
allowedModels: null,
|
|
170
290
|
defaultModel: DEFAULT_MODEL,
|
|
171
291
|
run
|
|
172
292
|
};
|
|
173
|
-
var
|
|
174
|
-
var
|
|
293
|
+
var DEFAULT_MODEL2 = "wan-2.7-image-edit";
|
|
294
|
+
var ALLOWED_MODELS = [
|
|
295
|
+
"wan-2.7-image-edit",
|
|
296
|
+
"wan-2.6-image-edit",
|
|
297
|
+
"flux-edit",
|
|
298
|
+
"xai-image-edit",
|
|
299
|
+
"p-image-edit",
|
|
300
|
+
"gpt-image-2-edit",
|
|
301
|
+
"hidream-edit",
|
|
302
|
+
"google-nano-banana"
|
|
303
|
+
];
|
|
175
304
|
var InputSchema2 = z.object({
|
|
305
|
+
prompt: z.string().min(1).max(5e3).describe("Edit instruction."),
|
|
306
|
+
image_url: z.string().url().optional().describe("Single reference image to edit. Pass image_urls for multi-image edits."),
|
|
307
|
+
image_urls: z.array(z.string().url()).min(1).max(9).optional().describe("Multiple reference images (1-9). Used for composition/multi-ref edits."),
|
|
308
|
+
image_path: z.string().min(1).optional().describe(
|
|
309
|
+
"Absolute local file path. Skill uploads it (24h temp storage) and edits it. Prefer over image_url when the user has the file on disk."
|
|
310
|
+
),
|
|
311
|
+
image_paths: z.array(z.string().min(1)).min(1).max(9).optional().describe(
|
|
312
|
+
"Multiple absolute local file paths (1-9) for multi-image edits. Each uploaded to 24h temp storage before dispatch."
|
|
313
|
+
),
|
|
314
|
+
model: z.enum(ALLOWED_MODELS).optional().describe(
|
|
315
|
+
"Override the default edit model. Defaults to wan-2.7-image-edit (most popular)."
|
|
316
|
+
),
|
|
317
|
+
aspect_ratio: z.string().optional().describe('Aspect ratio when supported (e.g. "1:1", "16:9", "match_input_image").')
|
|
318
|
+
});
|
|
319
|
+
var InputJsonSchema2 = {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
prompt: { type: "string", description: "Edit instruction." },
|
|
323
|
+
image_url: {
|
|
324
|
+
type: "string",
|
|
325
|
+
format: "uri",
|
|
326
|
+
description: "Single reference image. Pass image_urls for multi-image edits."
|
|
327
|
+
},
|
|
328
|
+
image_urls: {
|
|
329
|
+
type: "array",
|
|
330
|
+
items: { type: "string", format: "uri" },
|
|
331
|
+
minItems: 1,
|
|
332
|
+
maxItems: 9,
|
|
333
|
+
description: "Multiple reference images (1-9)."
|
|
334
|
+
},
|
|
335
|
+
image_path: {
|
|
336
|
+
type: "string",
|
|
337
|
+
description: "Absolute local file path. Skill uploads it (24h temp storage) and edits the resulting URL. Prefer over image_url when the user has a local file."
|
|
338
|
+
},
|
|
339
|
+
image_paths: {
|
|
340
|
+
type: "array",
|
|
341
|
+
items: { type: "string" },
|
|
342
|
+
minItems: 1,
|
|
343
|
+
maxItems: 9,
|
|
344
|
+
description: "Multiple absolute local file paths (1-9). Each uploaded before dispatch."
|
|
345
|
+
},
|
|
346
|
+
model: {
|
|
347
|
+
type: "string",
|
|
348
|
+
enum: [...ALLOWED_MODELS],
|
|
349
|
+
description: "Override edit model. Default: wan-2.7-image-edit."
|
|
350
|
+
},
|
|
351
|
+
aspect_ratio: { type: "string" }
|
|
352
|
+
},
|
|
353
|
+
required: ["prompt"]
|
|
354
|
+
};
|
|
355
|
+
function buildPayload2(apiId, input) {
|
|
356
|
+
const urls = input.image_urls && input.image_urls.length > 0 ? input.image_urls : input.image_url ? [input.image_url] : [];
|
|
357
|
+
switch (apiId) {
|
|
358
|
+
case "wan-2.7-image-edit":
|
|
359
|
+
case "wan-2.6-image-edit":
|
|
360
|
+
return {
|
|
361
|
+
prompt: input.prompt,
|
|
362
|
+
image_urls: urls
|
|
363
|
+
// wan-edit uses `size` not aspect_ratio. Default 2K, let model defaults handle it otherwise.
|
|
364
|
+
};
|
|
365
|
+
case "p-image-edit":
|
|
366
|
+
return {
|
|
367
|
+
prompt: input.prompt,
|
|
368
|
+
images: urls.slice(0, 5),
|
|
369
|
+
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
370
|
+
};
|
|
371
|
+
case "flux-edit":
|
|
372
|
+
case "xai-image-edit":
|
|
373
|
+
case "gpt-image-2-edit":
|
|
374
|
+
case "hidream-edit":
|
|
375
|
+
return {
|
|
376
|
+
prompt: input.prompt,
|
|
377
|
+
image_url: urls[0],
|
|
378
|
+
...urls.length > 1 ? { image_urls: urls } : {},
|
|
379
|
+
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
380
|
+
};
|
|
381
|
+
case "google-nano-banana":
|
|
382
|
+
return {
|
|
383
|
+
prompt: input.prompt,
|
|
384
|
+
image_input: urls,
|
|
385
|
+
...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
|
|
386
|
+
};
|
|
387
|
+
default:
|
|
388
|
+
return {
|
|
389
|
+
prompt: input.prompt,
|
|
390
|
+
image_url: urls[0]
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async function run2(input, ctx) {
|
|
395
|
+
let resolved = input;
|
|
396
|
+
try {
|
|
397
|
+
if (input.image_paths && input.image_paths.length > 0) {
|
|
398
|
+
const urls = await uploadLocalFiles(input.image_paths, ctx.client);
|
|
399
|
+
resolved = {
|
|
400
|
+
...input,
|
|
401
|
+
image_urls: [...input.image_urls ?? [], ...urls]
|
|
402
|
+
};
|
|
403
|
+
} else if (input.image_path && !input.image_url) {
|
|
404
|
+
const url = await uploadLocalFile(input.image_path, ctx.client);
|
|
405
|
+
resolved = { ...input, image_url: url };
|
|
406
|
+
}
|
|
407
|
+
} catch (err) {
|
|
408
|
+
if (err instanceof SkillUploadError) {
|
|
409
|
+
return { kind: "text", text: `pixeldojo:edit (upload): ${err.userMessage}` };
|
|
410
|
+
}
|
|
411
|
+
throw err;
|
|
412
|
+
}
|
|
413
|
+
if (!resolved.image_url && (!resolved.image_urls || resolved.image_urls.length === 0)) {
|
|
414
|
+
return {
|
|
415
|
+
kind: "text",
|
|
416
|
+
text: "pixeldojo:edit requires at least one reference image. Pass image_url / image_urls (hosted URL) or image_path / image_paths (local file)."
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const apiId = resolved.model ?? DEFAULT_MODEL2;
|
|
420
|
+
const body = buildPayload2(apiId, resolved);
|
|
421
|
+
const submitted = await ctx.client.run(apiId, body);
|
|
422
|
+
const result = await pollJobUntilDoneOrHandoff(
|
|
423
|
+
submitted.jobId,
|
|
424
|
+
(id) => ctx.client.getJob(id),
|
|
425
|
+
ctx.buildStatusUrl,
|
|
426
|
+
{
|
|
427
|
+
now: ctx.pollOverrides?.now,
|
|
428
|
+
sleep: ctx.pollOverrides?.sleep
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
return pollResultToSkillResult(
|
|
432
|
+
result,
|
|
433
|
+
(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(", ")}`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
var editSkill = {
|
|
437
|
+
name: "pixeldojo:edit",
|
|
438
|
+
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).",
|
|
439
|
+
inputZod: InputSchema2,
|
|
440
|
+
inputJsonSchema: InputJsonSchema2,
|
|
441
|
+
allowedModels: [...ALLOWED_MODELS],
|
|
442
|
+
defaultModel: DEFAULT_MODEL2,
|
|
443
|
+
run: run2
|
|
444
|
+
};
|
|
445
|
+
var ALLOWED = ["ideogram-character", "flux-edit", "gpt-image-2-edit"];
|
|
446
|
+
var DEFAULT_MODEL3 = "ideogram-character";
|
|
447
|
+
var InputSchema3 = z.object({
|
|
176
448
|
prompt: z.string().min(1).max(8e3),
|
|
177
|
-
model: z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${
|
|
449
|
+
model: z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`),
|
|
178
450
|
reference_image_url: z.string().url().optional().describe(
|
|
179
451
|
"Reference image of the character. Required by flux-edit and gpt-image-2-edit; optional for ideogram-character."
|
|
180
452
|
),
|
|
181
453
|
aspect_ratio: z.string().optional()
|
|
182
454
|
});
|
|
183
|
-
var
|
|
455
|
+
var InputJsonSchema3 = {
|
|
184
456
|
type: "object",
|
|
185
457
|
properties: {
|
|
186
458
|
prompt: { type: "string" },
|
|
187
459
|
model: {
|
|
188
460
|
type: "string",
|
|
189
461
|
enum: ALLOWED,
|
|
190
|
-
description: `One of: ${ALLOWED.join(", ")}. Defaults to ${
|
|
462
|
+
description: `One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`
|
|
191
463
|
},
|
|
192
464
|
reference_image_url: { type: "string", format: "uri" },
|
|
193
465
|
aspect_ratio: { type: "string" }
|
|
194
466
|
},
|
|
195
467
|
required: ["prompt"]
|
|
196
468
|
};
|
|
197
|
-
async function
|
|
198
|
-
const requested = input.model ??
|
|
469
|
+
async function run3(input, ctx) {
|
|
470
|
+
const requested = input.model ?? DEFAULT_MODEL3;
|
|
199
471
|
if (!ALLOWED.includes(requested)) {
|
|
200
472
|
return {
|
|
201
473
|
kind: "text",
|
|
@@ -203,6 +475,12 @@ async function run2(input, ctx) {
|
|
|
203
475
|
};
|
|
204
476
|
}
|
|
205
477
|
const apiId = requested;
|
|
478
|
+
if ((apiId === "flux-edit" || apiId === "gpt-image-2-edit") && !input.reference_image_url) {
|
|
479
|
+
return {
|
|
480
|
+
kind: "text",
|
|
481
|
+
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="ideogram-character".`
|
|
482
|
+
};
|
|
483
|
+
}
|
|
206
484
|
const body = { prompt: input.prompt };
|
|
207
485
|
if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
|
|
208
486
|
if (input.reference_image_url) {
|
|
@@ -227,21 +505,21 @@ async function run2(input, ctx) {
|
|
|
227
505
|
var characterSkill = {
|
|
228
506
|
name: "pixeldojo:character",
|
|
229
507
|
description: "Generate or edit consistent characters across shots. Pass a reference_image_url for edit-style models.",
|
|
230
|
-
inputZod:
|
|
231
|
-
inputJsonSchema:
|
|
508
|
+
inputZod: InputSchema3,
|
|
509
|
+
inputJsonSchema: InputJsonSchema3,
|
|
232
510
|
allowedModels: [...ALLOWED],
|
|
233
|
-
defaultModel:
|
|
234
|
-
run:
|
|
511
|
+
defaultModel: DEFAULT_MODEL3,
|
|
512
|
+
run: run3
|
|
235
513
|
};
|
|
236
514
|
var ALLOWED2 = ["veo-3.1", "kling-video", "seedance-2"];
|
|
237
|
-
var
|
|
238
|
-
var
|
|
515
|
+
var DEFAULT_MODEL4 = "veo-3.1";
|
|
516
|
+
var InputSchema4 = z.object({
|
|
239
517
|
brief: z.string().min(1).max(8e3).describe("Storyboard brief: scene, shots, mood, action."),
|
|
240
|
-
model: z.string().optional().describe(`One of: ${ALLOWED2.join(", ")}. Defaults to ${
|
|
518
|
+
model: z.string().optional().describe(`One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`),
|
|
241
519
|
aspect_ratio: z.string().optional(),
|
|
242
520
|
duration: z.number().int().positive().optional()
|
|
243
521
|
});
|
|
244
|
-
var
|
|
522
|
+
var InputJsonSchema4 = {
|
|
245
523
|
type: "object",
|
|
246
524
|
properties: {
|
|
247
525
|
brief: {
|
|
@@ -251,15 +529,15 @@ var InputJsonSchema3 = {
|
|
|
251
529
|
model: {
|
|
252
530
|
type: "string",
|
|
253
531
|
enum: ALLOWED2,
|
|
254
|
-
description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${
|
|
532
|
+
description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`
|
|
255
533
|
},
|
|
256
534
|
aspect_ratio: { type: "string" },
|
|
257
535
|
duration: { type: "integer" }
|
|
258
536
|
},
|
|
259
537
|
required: ["brief"]
|
|
260
538
|
};
|
|
261
|
-
async function
|
|
262
|
-
const requested = input.model ??
|
|
539
|
+
async function run4(input, ctx) {
|
|
540
|
+
const requested = input.model ?? DEFAULT_MODEL4;
|
|
263
541
|
if (!ALLOWED2.includes(requested)) {
|
|
264
542
|
return {
|
|
265
543
|
kind: "text",
|
|
@@ -288,21 +566,21 @@ async function run3(input, ctx) {
|
|
|
288
566
|
var storyboardSkill = {
|
|
289
567
|
name: "pixeldojo:storyboard",
|
|
290
568
|
description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
|
|
291
|
-
inputZod:
|
|
292
|
-
inputJsonSchema:
|
|
569
|
+
inputZod: InputSchema4,
|
|
570
|
+
inputJsonSchema: InputJsonSchema4,
|
|
293
571
|
allowedModels: [...ALLOWED2],
|
|
294
|
-
defaultModel:
|
|
295
|
-
run:
|
|
572
|
+
defaultModel: DEFAULT_MODEL4,
|
|
573
|
+
run: run4
|
|
296
574
|
};
|
|
297
575
|
var ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance"];
|
|
298
|
-
var
|
|
299
|
-
var
|
|
576
|
+
var DEFAULT_MODEL5 = "magnific-upscaler";
|
|
577
|
+
var InputSchema5 = z.object({
|
|
300
578
|
image_url: z.string().url().describe("URL of the source image to upscale."),
|
|
301
|
-
model: z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${
|
|
579
|
+
model: z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL5}.`),
|
|
302
580
|
scale_factor: z.number().int().min(1).max(4).optional().describe("Magnific only: 1, 2, or 4. Defaults to 2."),
|
|
303
581
|
creativity: z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
|
|
304
582
|
});
|
|
305
|
-
var
|
|
583
|
+
var InputJsonSchema5 = {
|
|
306
584
|
type: "object",
|
|
307
585
|
properties: {
|
|
308
586
|
image_url: { type: "string", format: "uri" },
|
|
@@ -312,8 +590,8 @@ var InputJsonSchema4 = {
|
|
|
312
590
|
},
|
|
313
591
|
required: ["image_url"]
|
|
314
592
|
};
|
|
315
|
-
async function
|
|
316
|
-
const requested = input.model ??
|
|
593
|
+
async function run5(input, ctx) {
|
|
594
|
+
const requested = input.model ?? DEFAULT_MODEL5;
|
|
317
595
|
if (!ALLOWED3.includes(requested)) {
|
|
318
596
|
return {
|
|
319
597
|
kind: "text",
|
|
@@ -347,16 +625,16 @@ async function run4(input, ctx) {
|
|
|
347
625
|
var upscaleSkill = {
|
|
348
626
|
name: "pixeldojo:upscale",
|
|
349
627
|
description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
|
|
350
|
-
inputZod:
|
|
351
|
-
inputJsonSchema:
|
|
628
|
+
inputZod: InputSchema5,
|
|
629
|
+
inputJsonSchema: InputJsonSchema5,
|
|
352
630
|
allowedModels: [...ALLOWED3],
|
|
353
|
-
defaultModel:
|
|
354
|
-
run:
|
|
631
|
+
defaultModel: DEFAULT_MODEL5,
|
|
632
|
+
run: run5
|
|
355
633
|
};
|
|
356
|
-
var
|
|
634
|
+
var InputSchema6 = z.object({
|
|
357
635
|
jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
|
|
358
636
|
});
|
|
359
|
-
var
|
|
637
|
+
var InputJsonSchema6 = {
|
|
360
638
|
type: "object",
|
|
361
639
|
properties: {
|
|
362
640
|
jobId: {
|
|
@@ -368,7 +646,7 @@ var InputJsonSchema5 = {
|
|
|
368
646
|
};
|
|
369
647
|
var STUCK_HINT_MS = 5 * 60 * 1e3;
|
|
370
648
|
var ABANDON_HINT_MS = 12 * 60 * 1e3;
|
|
371
|
-
async function
|
|
649
|
+
async function run6(input, ctx) {
|
|
372
650
|
const job = await ctx.client.getJob(input.jobId);
|
|
373
651
|
if (job.status === "completed") {
|
|
374
652
|
const urls = extractAssetUrls2(job);
|
|
@@ -435,17 +713,17 @@ function extractAssetUrls2(job) {
|
|
|
435
713
|
var statusSkill = {
|
|
436
714
|
name: "pixeldojo:status",
|
|
437
715
|
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.",
|
|
438
|
-
inputZod:
|
|
439
|
-
inputJsonSchema:
|
|
716
|
+
inputZod: InputSchema6,
|
|
717
|
+
inputJsonSchema: InputJsonSchema6,
|
|
440
718
|
// status is special — it doesn't dispatch to a model
|
|
441
719
|
allowedModels: [],
|
|
442
720
|
defaultModel: "",
|
|
443
|
-
run:
|
|
721
|
+
run: run6
|
|
444
722
|
};
|
|
445
|
-
var
|
|
723
|
+
var InputSchema7 = z.object({
|
|
446
724
|
url: z.string().min(1).max(2048).describe("Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata.")
|
|
447
725
|
});
|
|
448
|
-
var
|
|
726
|
+
var InputJsonSchema7 = {
|
|
449
727
|
type: "object",
|
|
450
728
|
properties: {
|
|
451
729
|
url: {
|
|
@@ -455,7 +733,7 @@ var InputJsonSchema6 = {
|
|
|
455
733
|
},
|
|
456
734
|
required: ["url"]
|
|
457
735
|
};
|
|
458
|
-
async function
|
|
736
|
+
async function run7(input, ctx) {
|
|
459
737
|
const product = await ctx.client.extractProduct(input.url);
|
|
460
738
|
const summary = [
|
|
461
739
|
`Product: ${product.name}`,
|
|
@@ -475,14 +753,14 @@ function truncate(input, max) {
|
|
|
475
753
|
var fromUrlSkill = {
|
|
476
754
|
name: "pixeldojo:from_url",
|
|
477
755
|
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:
|
|
479
|
-
inputJsonSchema:
|
|
756
|
+
inputZod: InputSchema7,
|
|
757
|
+
inputJsonSchema: InputJsonSchema7,
|
|
480
758
|
// No model dispatch — this skill calls a server-side scrape, not a generation.
|
|
481
759
|
allowedModels: [],
|
|
482
760
|
defaultModel: "",
|
|
483
|
-
run:
|
|
761
|
+
run: run7
|
|
484
762
|
};
|
|
485
|
-
var
|
|
763
|
+
var InputSchema8 = z.object({
|
|
486
764
|
productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
|
|
487
765
|
product: z.object({
|
|
488
766
|
name: z.string().min(1).max(200),
|
|
@@ -498,7 +776,7 @@ var InputSchema7 = z.object({
|
|
|
498
776
|
message: "productUrl or product is required",
|
|
499
777
|
path: ["productUrl"]
|
|
500
778
|
});
|
|
501
|
-
var
|
|
779
|
+
var InputJsonSchema8 = {
|
|
502
780
|
type: "object",
|
|
503
781
|
properties: {
|
|
504
782
|
productUrl: {
|
|
@@ -526,7 +804,7 @@ var InputJsonSchema7 = {
|
|
|
526
804
|
},
|
|
527
805
|
oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
|
|
528
806
|
};
|
|
529
|
-
async function
|
|
807
|
+
async function run8(input, ctx) {
|
|
530
808
|
const client = ctx.client;
|
|
531
809
|
const result = await client.runCampaign(input);
|
|
532
810
|
if (result.kind === "campaign") {
|
|
@@ -556,18 +834,18 @@ async function run7(input, ctx) {
|
|
|
556
834
|
var campaignSkill = {
|
|
557
835
|
name: "pixeldojo:campaign",
|
|
558
836
|
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:
|
|
560
|
-
inputJsonSchema:
|
|
837
|
+
inputZod: InputSchema8,
|
|
838
|
+
inputJsonSchema: InputJsonSchema8,
|
|
561
839
|
// Composition skill — internal sub-jobs route to per-tool models, no
|
|
562
840
|
// single allowed-models list applies here.
|
|
563
841
|
allowedModels: [],
|
|
564
842
|
defaultModel: "",
|
|
565
|
-
run:
|
|
843
|
+
run: run8
|
|
566
844
|
};
|
|
567
|
-
var
|
|
845
|
+
var InputSchema9 = z.object({
|
|
568
846
|
campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
|
|
569
847
|
});
|
|
570
|
-
var
|
|
848
|
+
var InputJsonSchema9 = {
|
|
571
849
|
type: "object",
|
|
572
850
|
properties: {
|
|
573
851
|
campaignId: {
|
|
@@ -577,7 +855,7 @@ var InputJsonSchema8 = {
|
|
|
577
855
|
},
|
|
578
856
|
required: ["campaignId"]
|
|
579
857
|
};
|
|
580
|
-
async function
|
|
858
|
+
async function run9(input, ctx) {
|
|
581
859
|
const client = ctx.client;
|
|
582
860
|
const result = await client.getCampaign(input.campaignId);
|
|
583
861
|
if (result.kind === "campaign") {
|
|
@@ -610,21 +888,63 @@ async function run8(input, ctx) {
|
|
|
610
888
|
var campaignStatusSkill = {
|
|
611
889
|
name: "pixeldojo:campaign_status",
|
|
612
890
|
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:
|
|
614
|
-
inputJsonSchema:
|
|
891
|
+
inputZod: InputSchema9,
|
|
892
|
+
inputJsonSchema: InputJsonSchema9,
|
|
615
893
|
allowedModels: [],
|
|
616
894
|
defaultModel: "",
|
|
617
|
-
run:
|
|
895
|
+
run: run9
|
|
896
|
+
};
|
|
897
|
+
var InputSchema10 = z.object({
|
|
898
|
+
path: z.string().min(1).max(4096).describe(
|
|
899
|
+
"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."
|
|
900
|
+
)
|
|
901
|
+
});
|
|
902
|
+
var InputJsonSchema10 = {
|
|
903
|
+
type: "object",
|
|
904
|
+
properties: {
|
|
905
|
+
path: {
|
|
906
|
+
type: "string",
|
|
907
|
+
description: "Absolute local file path. Allowed: JPEG, PNG, WebP, GIF (\u226450MB) or MP4, WebM, MOV (\u2264200MB). Returns a public URL on the temp CDN; auto-expires after 24h."
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
required: ["path"]
|
|
911
|
+
};
|
|
912
|
+
async function run10(input, ctx) {
|
|
913
|
+
let url;
|
|
914
|
+
try {
|
|
915
|
+
url = await uploadLocalFile(input.path, ctx.client);
|
|
916
|
+
} catch (err) {
|
|
917
|
+
if (err instanceof SkillUploadError) {
|
|
918
|
+
return { kind: "text", text: `pixeldojo:upload: ${err.userMessage}` };
|
|
919
|
+
}
|
|
920
|
+
throw err;
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
kind: "text",
|
|
924
|
+
text: `Uploaded ${basename(input.path)} \u2192 ${url}
|
|
925
|
+
(temp storage; auto-deletes in ~24h \u2014 pass this URL as image_url to :generate or :edit)`
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
var uploadSkill = {
|
|
929
|
+
name: "pixeldojo:upload",
|
|
930
|
+
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.",
|
|
931
|
+
inputZod: InputSchema10,
|
|
932
|
+
inputJsonSchema: InputJsonSchema10,
|
|
933
|
+
allowedModels: [],
|
|
934
|
+
defaultModel: "",
|
|
935
|
+
run: run10
|
|
618
936
|
};
|
|
619
937
|
|
|
620
938
|
// src/skills/index.ts
|
|
621
939
|
var ALL_SKILLS = [
|
|
622
940
|
generateSkill,
|
|
941
|
+
editSkill,
|
|
623
942
|
characterSkill,
|
|
624
943
|
storyboardSkill,
|
|
625
944
|
upscaleSkill,
|
|
626
945
|
statusSkill,
|
|
627
946
|
fromUrlSkill,
|
|
947
|
+
uploadSkill,
|
|
628
948
|
campaignSkill,
|
|
629
949
|
campaignStatusSkill
|
|
630
950
|
];
|