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