@pixeldojo/mcp 0.2.1 → 0.4.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/index.cjs CHANGED
@@ -1,31 +1,24 @@
1
1
  'use strict';
2
2
 
3
+ var promises = require('fs/promises');
4
+ var path = require('path');
5
+ var zod = require('zod');
3
6
  var index_js = require('@modelcontextprotocol/sdk/server/index.js');
4
7
  var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
5
8
  var types_js = require('@modelcontextprotocol/sdk/types.js');
6
9
  var sdk = require('@pixeldojo/sdk');
7
- var zod = require('zod');
8
10
 
9
- // src/server.ts
10
- function makeAttributedClient(options) {
11
- const { mcpVersion, skillName, baseFetch, ...sdkOptions } = options;
12
- const real = baseFetch ?? globalThis.fetch.bind(globalThis);
13
- const wrappedFetch = (input, init) => {
14
- const headers = new Headers(init?.headers);
15
- headers.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
16
- if (skillName) headers.set("x-pixeldojo-skill", skillName);
17
- return real(input, { ...init, headers });
18
- };
19
- return new sdk.PixelDojoClient({
20
- ...sdkOptions,
21
- fetch: wrappedFetch
22
- });
23
- }
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
24
20
 
25
21
  // src/poll.ts
26
- var DEFAULT_BACKOFF = [1e3, 2e3, 4e3, 8e3];
27
- var DEFAULT_MAX_INTERVAL = 8e3;
28
- var DEFAULT_SYNC_BUDGET_MS = 3e4;
29
22
  function defaultSleep(ms, signal) {
30
23
  return new Promise((resolve, reject) => {
31
24
  if (signal?.aborted) {
@@ -74,6 +67,14 @@ async function pollJobUntilDoneOrHandoff(jobId, fetchJob, buildStatusUrl, option
74
67
  attempt++;
75
68
  }
76
69
  }
70
+ var DEFAULT_BACKOFF, DEFAULT_MAX_INTERVAL, DEFAULT_SYNC_BUDGET_MS;
71
+ var init_poll = __esm({
72
+ "src/poll.ts"() {
73
+ DEFAULT_BACKOFF = [1e3, 2e3, 4e3, 8e3];
74
+ DEFAULT_MAX_INTERVAL = 8e3;
75
+ DEFAULT_SYNC_BUDGET_MS = 3e4;
76
+ }
77
+ });
77
78
 
78
79
  // src/skills/types.ts
79
80
  function pollResultToSkillResult(result, successText) {
@@ -108,46 +109,133 @@ function extractAssetUrls(job) {
108
109
  if (output?.video) return [output.video];
109
110
  return [];
110
111
  }
111
-
112
- // src/skills/generate.ts
113
- var InputSchema = zod.z.object({
114
- prompt: zod.z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
115
- model: zod.z.string().optional().describe(
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
- ),
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
- ),
121
- aspect_ratio: zod.z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
122
- 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.")
112
+ var init_types = __esm({
113
+ "src/skills/types.ts"() {
114
+ }
124
115
  });
125
- var DEFAULT_MODEL = "google-nano-banana";
126
- var InputJsonSchema = {
127
- type: "object",
128
- properties: {
129
- prompt: { type: "string", description: "Prompt describing what to generate." },
130
- model: {
131
- type: "string",
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.'
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
- },
138
- aspect_ratio: { type: "string" },
139
- duration: { type: "integer" },
140
- image_url: { type: "string", format: "uri" }
141
- },
142
- required: ["prompt"]
143
- };
144
- async function run(input, ctx) {
145
- const apiId = input.model ?? DEFAULT_MODEL;
116
+ async function uploadLocalFile(path$1, client) {
117
+ const ext = path.extname(path$1).toLowerCase();
118
+ const mime = EXT_TO_MIME[ext];
119
+ if (!mime) {
120
+ throw new SkillUploadError(
121
+ `Unsupported file extension "${ext || "(none)"}" for "${path$1}". Allowed: ${Object.keys(EXT_TO_MIME).join(", ")}.`
122
+ );
123
+ }
124
+ let size;
125
+ try {
126
+ const info = await promises.stat(path$1);
127
+ if (!info.isFile()) {
128
+ throw new SkillUploadError(`"${path$1}" is not a file.`);
129
+ }
130
+ size = info.size;
131
+ } catch (err) {
132
+ if (err instanceof SkillUploadError) throw err;
133
+ const code = err?.code;
134
+ if (code === "ENOENT") {
135
+ throw new SkillUploadError(
136
+ `File not found at "${path$1}". Pass an absolute path.`
137
+ );
138
+ }
139
+ throw new SkillUploadError(
140
+ `Cannot read "${path$1}": ${err instanceof Error ? err.message : String(err)}`
141
+ );
142
+ }
143
+ const isImage = mime.startsWith("image/");
144
+ const limit = isImage ? MAX_IMAGE_BYTES : MAX_VIDEO_BYTES;
145
+ if (size > limit) {
146
+ const gotMB = (size / 1024 / 1024).toFixed(1);
147
+ const limitMB = (limit / 1024 / 1024).toFixed(0);
148
+ throw new SkillUploadError(
149
+ `"${path.basename(path$1)}" is ${gotMB}MB; limit for ${isImage ? "images" : "videos"} is ${limitMB}MB. Downscale or re-encode and try again.`
150
+ );
151
+ }
152
+ const buffer = await promises.readFile(path$1);
153
+ const blob = new Blob([new Uint8Array(buffer)], { type: mime });
154
+ const filename = path.basename(path$1);
155
+ const response = await client.uploadImage(blob, filename);
156
+ return response.url;
157
+ }
158
+ async function uploadLocalFiles(paths, client) {
159
+ return Promise.all(paths.map((p) => uploadLocalFile(p, client)));
160
+ }
161
+ var MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, EXT_TO_MIME, SkillUploadError;
162
+ var init_upload_helpers = __esm({
163
+ "src/skills/upload-helpers.ts"() {
164
+ MAX_IMAGE_BYTES = 50 * 1024 * 1024;
165
+ MAX_VIDEO_BYTES = 200 * 1024 * 1024;
166
+ EXT_TO_MIME = {
167
+ ".jpg": "image/jpeg",
168
+ ".jpeg": "image/jpeg",
169
+ ".png": "image/png",
170
+ ".webp": "image/webp",
171
+ ".gif": "image/gif",
172
+ ".mp4": "video/mp4",
173
+ ".webm": "video/webm",
174
+ ".mov": "video/quicktime"
175
+ };
176
+ SkillUploadError = class extends Error {
177
+ constructor(userMessage) {
178
+ super(userMessage);
179
+ this.userMessage = userMessage;
180
+ this.name = "SkillUploadError";
181
+ }
182
+ userMessage;
183
+ };
184
+ }
185
+ });
186
+ function buildPayload(apiId, input) {
146
187
  const body = { prompt: input.prompt };
147
188
  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
189
  if (input.preset) body.preset = input.preset;
190
+ switch (apiId) {
191
+ case "grok-video-extend":
192
+ case "wan-video-edit":
193
+ case "seedance-video-edit":
194
+ if (input.image_url) {
195
+ body.video_url = input.image_url;
196
+ body.image_url = input.image_url;
197
+ }
198
+ if (input.duration !== void 0) {
199
+ body.video_duration = input.duration;
200
+ body.duration = input.duration;
201
+ }
202
+ break;
203
+ default:
204
+ if (input.image_url) body.image_url = input.image_url;
205
+ if (input.duration !== void 0) body.duration = input.duration;
206
+ break;
207
+ }
208
+ return body;
209
+ }
210
+ async function run(input, ctx) {
211
+ const apiId = input.model ?? DEFAULT_MODEL;
212
+ let resolved = input;
213
+ if (!input.image_url && input.image_path) {
214
+ try {
215
+ const uploadedUrl = await uploadLocalFile(input.image_path, ctx.client);
216
+ resolved = { ...input, image_url: uploadedUrl };
217
+ } catch (err) {
218
+ if (err instanceof SkillUploadError) {
219
+ return { kind: "text", text: `pixeldojo:generate (upload): ${err.userMessage}` };
220
+ }
221
+ throw err;
222
+ }
223
+ }
224
+ if (apiId === "grok-video-extend") {
225
+ if (!resolved.image_url) {
226
+ return {
227
+ kind: "text",
228
+ text: 'pixeldojo:generate with model="grok-video-extend" requires image_url (the source video URL to extend, or image_path for a local file).'
229
+ };
230
+ }
231
+ if (resolved.duration === void 0) {
232
+ return {
233
+ kind: "text",
234
+ text: 'pixeldojo:generate with model="grok-video-extend" requires duration (extension length in seconds).'
235
+ };
236
+ }
237
+ }
238
+ const body = buildPayload(apiId, resolved);
151
239
  const submitted = await ctx.client.run(apiId, body);
152
240
  const result = await pollJobUntilDoneOrHandoff(
153
241
  submitted.jobId,
@@ -163,41 +251,221 @@ async function run(input, ctx) {
163
251
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Generated. Asset: ${urls[0]}` : `Generated ${urls.length} assets. URLs: ${urls.join(", ")}`
164
252
  );
165
253
  }
166
- var generateSkill = {
167
- 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.",
169
- inputZod: InputSchema,
170
- inputJsonSchema: InputJsonSchema,
171
- allowedModels: null,
172
- defaultModel: DEFAULT_MODEL,
173
- run
174
- };
175
- var ALLOWED = ["ideogram-character", "flux-edit", "gpt-image-2-edit"];
176
- var DEFAULT_MODEL2 = "ideogram-character";
177
- var InputSchema2 = zod.z.object({
178
- prompt: zod.z.string().min(1).max(8e3),
179
- model: zod.z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL2}.`),
180
- reference_image_url: zod.z.string().url().optional().describe(
181
- "Reference image of the character. Required by flux-edit and gpt-image-2-edit; optional for ideogram-character."
182
- ),
183
- aspect_ratio: zod.z.string().optional()
254
+ var InputSchema, DEFAULT_MODEL, InputJsonSchema, generateSkill;
255
+ var init_generate = __esm({
256
+ "src/skills/generate.ts"() {
257
+ init_poll();
258
+ init_types();
259
+ init_upload_helpers();
260
+ InputSchema = zod.z.object({
261
+ prompt: zod.z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
262
+ model: zod.z.string().optional().describe(
263
+ '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.'
264
+ ),
265
+ preset: zod.z.string().optional().describe(
266
+ '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.'
267
+ ),
268
+ aspect_ratio: zod.z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
269
+ duration: zod.z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
270
+ image_url: zod.z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
271
+ image_path: zod.z.string().min(1).optional().describe(
272
+ "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."
273
+ )
274
+ });
275
+ DEFAULT_MODEL = "google-nano-banana";
276
+ InputJsonSchema = {
277
+ type: "object",
278
+ properties: {
279
+ prompt: { type: "string", description: "Prompt describing what to generate." },
280
+ model: {
281
+ type: "string",
282
+ 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.'
283
+ },
284
+ preset: {
285
+ type: "string",
286
+ description: 'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean"). Explicit fields override preset values.'
287
+ },
288
+ aspect_ratio: { type: "string" },
289
+ duration: { type: "integer" },
290
+ image_url: { type: "string", format: "uri" },
291
+ image_path: {
292
+ type: "string",
293
+ 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."
294
+ }
295
+ },
296
+ required: ["prompt"]
297
+ };
298
+ generateSkill = {
299
+ name: "pixeldojo:generate",
300
+ 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.",
301
+ inputZod: InputSchema,
302
+ inputJsonSchema: InputJsonSchema,
303
+ allowedModels: null,
304
+ defaultModel: DEFAULT_MODEL,
305
+ run
306
+ };
307
+ }
184
308
  });
185
- var InputJsonSchema2 = {
186
- type: "object",
187
- properties: {
188
- prompt: { type: "string" },
189
- model: {
190
- type: "string",
191
- enum: ALLOWED,
192
- description: `One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL2}.`
193
- },
194
- reference_image_url: { type: "string", format: "uri" },
195
- aspect_ratio: { type: "string" }
196
- },
197
- required: ["prompt"]
198
- };
309
+ function buildPayload2(apiId, input) {
310
+ const urls = input.image_urls && input.image_urls.length > 0 ? input.image_urls : input.image_url ? [input.image_url] : [];
311
+ switch (apiId) {
312
+ case "wan-2.7-image-edit":
313
+ case "wan-2.6-image-edit":
314
+ return {
315
+ prompt: input.prompt,
316
+ image_urls: urls
317
+ // wan-edit uses `size` not aspect_ratio. Default 2K, let model defaults handle it otherwise.
318
+ };
319
+ case "p-image-edit":
320
+ return {
321
+ prompt: input.prompt,
322
+ images: urls.slice(0, 5),
323
+ ...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
324
+ };
325
+ case "flux-edit":
326
+ case "xai-image-edit":
327
+ case "gpt-image-2-edit":
328
+ return {
329
+ prompt: input.prompt,
330
+ image_url: urls[0],
331
+ ...urls.length > 1 ? { image_urls: urls } : {},
332
+ ...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
333
+ };
334
+ case "google-nano-banana":
335
+ return {
336
+ prompt: input.prompt,
337
+ image_input: urls,
338
+ ...input.aspect_ratio ? { aspect_ratio: input.aspect_ratio } : {}
339
+ };
340
+ default:
341
+ return {
342
+ prompt: input.prompt,
343
+ image_url: urls[0]
344
+ };
345
+ }
346
+ }
199
347
  async function run2(input, ctx) {
200
- const requested = input.model ?? DEFAULT_MODEL2;
348
+ let resolved = input;
349
+ try {
350
+ if (input.image_paths && input.image_paths.length > 0) {
351
+ const urls = await uploadLocalFiles(input.image_paths, ctx.client);
352
+ resolved = {
353
+ ...input,
354
+ image_urls: [...input.image_urls ?? [], ...urls]
355
+ };
356
+ } else if (input.image_path && !input.image_url) {
357
+ const url = await uploadLocalFile(input.image_path, ctx.client);
358
+ resolved = { ...input, image_url: url };
359
+ }
360
+ } catch (err) {
361
+ if (err instanceof SkillUploadError) {
362
+ return { kind: "text", text: `pixeldojo:edit (upload): ${err.userMessage}` };
363
+ }
364
+ throw err;
365
+ }
366
+ if (!resolved.image_url && (!resolved.image_urls || resolved.image_urls.length === 0)) {
367
+ return {
368
+ kind: "text",
369
+ text: "pixeldojo:edit requires at least one reference image. Pass image_url / image_urls (hosted URL) or image_path / image_paths (local file)."
370
+ };
371
+ }
372
+ const apiId = resolved.model ?? DEFAULT_MODEL2;
373
+ const body = buildPayload2(apiId, resolved);
374
+ const submitted = await ctx.client.run(apiId, body);
375
+ const result = await pollJobUntilDoneOrHandoff(
376
+ submitted.jobId,
377
+ (id) => ctx.client.getJob(id),
378
+ ctx.buildStatusUrl,
379
+ {
380
+ now: ctx.pollOverrides?.now,
381
+ sleep: ctx.pollOverrides?.sleep
382
+ }
383
+ );
384
+ return pollResultToSkillResult(
385
+ result,
386
+ (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(", ")}`
387
+ );
388
+ }
389
+ var DEFAULT_MODEL2, ALLOWED_MODELS, InputSchema2, InputJsonSchema2, editSkill;
390
+ var init_edit = __esm({
391
+ "src/skills/edit.ts"() {
392
+ init_poll();
393
+ init_types();
394
+ init_upload_helpers();
395
+ DEFAULT_MODEL2 = "wan-2.7-image-edit";
396
+ ALLOWED_MODELS = [
397
+ "wan-2.7-image-edit",
398
+ "wan-2.6-image-edit",
399
+ "flux-edit",
400
+ "xai-image-edit",
401
+ "p-image-edit",
402
+ "gpt-image-2-edit",
403
+ "google-nano-banana"
404
+ ];
405
+ InputSchema2 = zod.z.object({
406
+ prompt: zod.z.string().min(1).max(5e3).describe("Edit instruction."),
407
+ image_url: zod.z.string().url().optional().describe("Single reference image to edit. Pass image_urls for multi-image edits."),
408
+ 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."),
409
+ image_path: zod.z.string().min(1).optional().describe(
410
+ "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."
411
+ ),
412
+ image_paths: zod.z.array(zod.z.string().min(1)).min(1).max(9).optional().describe(
413
+ "Multiple absolute local file paths (1-9) for multi-image edits. Each uploaded to 24h temp storage before dispatch."
414
+ ),
415
+ model: zod.z.enum(ALLOWED_MODELS).optional().describe(
416
+ "Override the default edit model. Defaults to wan-2.7-image-edit (most popular)."
417
+ ),
418
+ aspect_ratio: zod.z.string().optional().describe('Aspect ratio when supported (e.g. "1:1", "16:9", "match_input_image").')
419
+ });
420
+ InputJsonSchema2 = {
421
+ type: "object",
422
+ properties: {
423
+ prompt: { type: "string", description: "Edit instruction." },
424
+ image_url: {
425
+ type: "string",
426
+ format: "uri",
427
+ description: "Single reference image. Pass image_urls for multi-image edits."
428
+ },
429
+ image_urls: {
430
+ type: "array",
431
+ items: { type: "string", format: "uri" },
432
+ minItems: 1,
433
+ maxItems: 9,
434
+ description: "Multiple reference images (1-9)."
435
+ },
436
+ image_path: {
437
+ type: "string",
438
+ 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."
439
+ },
440
+ image_paths: {
441
+ type: "array",
442
+ items: { type: "string" },
443
+ minItems: 1,
444
+ maxItems: 9,
445
+ description: "Multiple absolute local file paths (1-9). Each uploaded before dispatch."
446
+ },
447
+ model: {
448
+ type: "string",
449
+ enum: [...ALLOWED_MODELS],
450
+ description: "Override edit model. Default: wan-2.7-image-edit."
451
+ },
452
+ aspect_ratio: { type: "string" }
453
+ },
454
+ required: ["prompt"]
455
+ };
456
+ editSkill = {
457
+ name: "pixeldojo:edit",
458
+ 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).",
459
+ inputZod: InputSchema2,
460
+ inputJsonSchema: InputJsonSchema2,
461
+ allowedModels: [...ALLOWED_MODELS],
462
+ defaultModel: DEFAULT_MODEL2,
463
+ run: run2
464
+ };
465
+ }
466
+ });
467
+ async function run3(input, ctx) {
468
+ const requested = input.model ?? DEFAULT_MODEL3;
201
469
  if (!ALLOWED.includes(requested)) {
202
470
  return {
203
471
  kind: "text",
@@ -205,6 +473,12 @@ async function run2(input, ctx) {
205
473
  };
206
474
  }
207
475
  const apiId = requested;
476
+ if ((apiId === "flux-edit" || apiId === "gpt-image-2-edit") && !input.reference_image_url) {
477
+ return {
478
+ kind: "text",
479
+ 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".`
480
+ };
481
+ }
208
482
  const body = { prompt: input.prompt };
209
483
  if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
210
484
  if (input.reference_image_url) {
@@ -226,42 +500,48 @@ async function run2(input, ctx) {
226
500
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Character shot ready. Asset: ${urls[0]}`
227
501
  );
228
502
  }
229
- var characterSkill = {
230
- name: "pixeldojo:character",
231
- description: "Generate or edit consistent characters across shots. Pass a reference_image_url for edit-style models.",
232
- inputZod: InputSchema2,
233
- inputJsonSchema: InputJsonSchema2,
234
- allowedModels: [...ALLOWED],
235
- defaultModel: DEFAULT_MODEL2,
236
- run: run2
237
- };
238
- var ALLOWED2 = ["veo-3.1", "kling-video", "seedance-2"];
239
- var DEFAULT_MODEL3 = "veo-3.1";
240
- var InputSchema3 = zod.z.object({
241
- 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}.`),
243
- aspect_ratio: zod.z.string().optional(),
244
- duration: zod.z.number().int().positive().optional()
503
+ var ALLOWED, DEFAULT_MODEL3, InputSchema3, InputJsonSchema3, characterSkill;
504
+ var init_character = __esm({
505
+ "src/skills/character.ts"() {
506
+ init_poll();
507
+ init_types();
508
+ ALLOWED = ["ideogram-character", "flux-edit", "gpt-image-2-edit"];
509
+ DEFAULT_MODEL3 = "ideogram-character";
510
+ InputSchema3 = zod.z.object({
511
+ prompt: zod.z.string().min(1).max(8e3),
512
+ model: zod.z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`),
513
+ reference_image_url: zod.z.string().url().optional().describe(
514
+ "Reference image of the character. Required by flux-edit and gpt-image-2-edit; optional for ideogram-character."
515
+ ),
516
+ aspect_ratio: zod.z.string().optional()
517
+ });
518
+ InputJsonSchema3 = {
519
+ type: "object",
520
+ properties: {
521
+ prompt: { type: "string" },
522
+ model: {
523
+ type: "string",
524
+ enum: ALLOWED,
525
+ description: `One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`
526
+ },
527
+ reference_image_url: { type: "string", format: "uri" },
528
+ aspect_ratio: { type: "string" }
529
+ },
530
+ required: ["prompt"]
531
+ };
532
+ characterSkill = {
533
+ name: "pixeldojo:character",
534
+ description: "Generate or edit consistent characters across shots. Pass a reference_image_url for edit-style models.",
535
+ inputZod: InputSchema3,
536
+ inputJsonSchema: InputJsonSchema3,
537
+ allowedModels: [...ALLOWED],
538
+ defaultModel: DEFAULT_MODEL3,
539
+ run: run3
540
+ };
541
+ }
245
542
  });
246
- var InputJsonSchema3 = {
247
- type: "object",
248
- properties: {
249
- brief: {
250
- type: "string",
251
- description: "Storyboard brief: scene, shots, mood, action."
252
- },
253
- model: {
254
- type: "string",
255
- enum: ALLOWED2,
256
- description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`
257
- },
258
- aspect_ratio: { type: "string" },
259
- duration: { type: "integer" }
260
- },
261
- required: ["brief"]
262
- };
263
- async function run3(input, ctx) {
264
- const requested = input.model ?? DEFAULT_MODEL3;
543
+ async function run4(input, ctx) {
544
+ const requested = input.model ?? DEFAULT_MODEL4;
265
545
  if (!ALLOWED2.includes(requested)) {
266
546
  return {
267
547
  kind: "text",
@@ -287,35 +567,49 @@ async function run3(input, ctx) {
287
567
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Storyboard preview ready. Asset: ${urls[0]}`
288
568
  );
289
569
  }
290
- var storyboardSkill = {
291
- name: "pixeldojo:storyboard",
292
- description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
293
- inputZod: InputSchema3,
294
- inputJsonSchema: InputJsonSchema3,
295
- allowedModels: [...ALLOWED2],
296
- defaultModel: DEFAULT_MODEL3,
297
- run: run3
298
- };
299
- var ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance"];
300
- var DEFAULT_MODEL4 = "magnific-upscaler";
301
- var InputSchema4 = zod.z.object({
302
- 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}.`),
304
- scale_factor: zod.z.number().int().min(1).max(4).optional().describe("Magnific only: 1, 2, or 4. Defaults to 2."),
305
- creativity: zod.z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
570
+ var ALLOWED2, DEFAULT_MODEL4, InputSchema4, InputJsonSchema4, storyboardSkill;
571
+ var init_storyboard = __esm({
572
+ "src/skills/storyboard.ts"() {
573
+ init_poll();
574
+ init_types();
575
+ ALLOWED2 = ["veo-3.1", "kling-video", "seedance-2"];
576
+ DEFAULT_MODEL4 = "veo-3.1";
577
+ InputSchema4 = zod.z.object({
578
+ brief: zod.z.string().min(1).max(8e3).describe("Storyboard brief: scene, shots, mood, action."),
579
+ model: zod.z.string().optional().describe(`One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`),
580
+ aspect_ratio: zod.z.string().optional(),
581
+ duration: zod.z.number().int().positive().optional()
582
+ });
583
+ InputJsonSchema4 = {
584
+ type: "object",
585
+ properties: {
586
+ brief: {
587
+ type: "string",
588
+ description: "Storyboard brief: scene, shots, mood, action."
589
+ },
590
+ model: {
591
+ type: "string",
592
+ enum: ALLOWED2,
593
+ description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`
594
+ },
595
+ aspect_ratio: { type: "string" },
596
+ duration: { type: "integer" }
597
+ },
598
+ required: ["brief"]
599
+ };
600
+ storyboardSkill = {
601
+ name: "pixeldojo:storyboard",
602
+ description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
603
+ inputZod: InputSchema4,
604
+ inputJsonSchema: InputJsonSchema4,
605
+ allowedModels: [...ALLOWED2],
606
+ defaultModel: DEFAULT_MODEL4,
607
+ run: run4
608
+ };
609
+ }
306
610
  });
307
- var InputJsonSchema4 = {
308
- type: "object",
309
- properties: {
310
- image_url: { type: "string", format: "uri" },
311
- model: { type: "string", enum: ALLOWED3 },
312
- scale_factor: { type: "integer", minimum: 1, maximum: 4 },
313
- creativity: { type: "number", minimum: 0, maximum: 1 }
314
- },
315
- required: ["image_url"]
316
- };
317
- async function run4(input, ctx) {
318
- const requested = input.model ?? DEFAULT_MODEL4;
611
+ async function run5(input, ctx) {
612
+ const requested = input.model ?? DEFAULT_MODEL5;
319
613
  if (!ALLOWED3.includes(requested)) {
320
614
  return {
321
615
  kind: "text",
@@ -346,31 +640,41 @@ async function run4(input, ctx) {
346
640
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Upscaled. Asset: ${urls[0]}`
347
641
  );
348
642
  }
349
- var upscaleSkill = {
350
- name: "pixeldojo:upscale",
351
- description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
352
- inputZod: InputSchema4,
353
- inputJsonSchema: InputJsonSchema4,
354
- allowedModels: [...ALLOWED3],
355
- defaultModel: DEFAULT_MODEL4,
356
- run: run4
357
- };
358
- var InputSchema5 = zod.z.object({
359
- jobId: zod.z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
643
+ var ALLOWED3, DEFAULT_MODEL5, InputSchema5, InputJsonSchema5, upscaleSkill;
644
+ var init_upscale = __esm({
645
+ "src/skills/upscale.ts"() {
646
+ init_poll();
647
+ init_types();
648
+ ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance"];
649
+ DEFAULT_MODEL5 = "magnific-upscaler";
650
+ InputSchema5 = zod.z.object({
651
+ image_url: zod.z.string().url().describe("URL of the source image to upscale."),
652
+ model: zod.z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL5}.`),
653
+ scale_factor: zod.z.number().int().min(1).max(4).optional().describe("Magnific only: 1, 2, or 4. Defaults to 2."),
654
+ creativity: zod.z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
655
+ });
656
+ InputJsonSchema5 = {
657
+ type: "object",
658
+ properties: {
659
+ image_url: { type: "string", format: "uri" },
660
+ model: { type: "string", enum: ALLOWED3 },
661
+ scale_factor: { type: "integer", minimum: 1, maximum: 4 },
662
+ creativity: { type: "number", minimum: 0, maximum: 1 }
663
+ },
664
+ required: ["image_url"]
665
+ };
666
+ upscaleSkill = {
667
+ name: "pixeldojo:upscale",
668
+ description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
669
+ inputZod: InputSchema5,
670
+ inputJsonSchema: InputJsonSchema5,
671
+ allowedModels: [...ALLOWED3],
672
+ defaultModel: DEFAULT_MODEL5,
673
+ run: run5
674
+ };
675
+ }
360
676
  });
361
- var InputJsonSchema5 = {
362
- type: "object",
363
- properties: {
364
- jobId: {
365
- type: "string",
366
- description: "Job id returned from a prior pixeldojo:* skill handoff."
367
- }
368
- },
369
- required: ["jobId"]
370
- };
371
- var STUCK_HINT_MS = 5 * 60 * 1e3;
372
- var ABANDON_HINT_MS = 12 * 60 * 1e3;
373
- async function run5(input, ctx) {
677
+ async function run6(input, ctx) {
374
678
  const job = await ctx.client.getJob(input.jobId);
375
679
  if (job.status === "completed") {
376
680
  const urls = extractAssetUrls2(job);
@@ -434,30 +738,37 @@ function extractAssetUrls2(job) {
434
738
  if (output?.video) return [output.video];
435
739
  return [];
436
740
  }
437
- var statusSkill = {
438
- name: "pixeldojo:status",
439
- 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,
442
- // status is special — it doesn't dispatch to a model
443
- allowedModels: [],
444
- defaultModel: "",
445
- run: run5
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.")
741
+ var InputSchema6, InputJsonSchema6, STUCK_HINT_MS, ABANDON_HINT_MS, statusSkill;
742
+ var init_status = __esm({
743
+ "src/skills/status.ts"() {
744
+ InputSchema6 = zod.z.object({
745
+ jobId: zod.z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
746
+ });
747
+ InputJsonSchema6 = {
748
+ type: "object",
749
+ properties: {
750
+ jobId: {
751
+ type: "string",
752
+ description: "Job id returned from a prior pixeldojo:* skill handoff."
753
+ }
754
+ },
755
+ required: ["jobId"]
756
+ };
757
+ STUCK_HINT_MS = 5 * 60 * 1e3;
758
+ ABANDON_HINT_MS = 12 * 60 * 1e3;
759
+ statusSkill = {
760
+ name: "pixeldojo:status",
761
+ 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.",
762
+ inputZod: InputSchema6,
763
+ inputJsonSchema: InputJsonSchema6,
764
+ // status is special — it doesn't dispatch to a model
765
+ allowedModels: [],
766
+ defaultModel: "",
767
+ run: run6
768
+ };
769
+ }
449
770
  });
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) {
771
+ async function run7(input, ctx) {
461
772
  const product = await ctx.client.extractProduct(input.url);
462
773
  const summary = [
463
774
  `Product: ${product.name}`,
@@ -474,61 +785,35 @@ function truncate(input, max) {
474
785
  if (input.length <= max) return input;
475
786
  return input.slice(0, max - 3) + "...";
476
787
  }
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: {
788
+ var InputSchema7, InputJsonSchema7, fromUrlSkill;
789
+ var init_from_url = __esm({
790
+ "src/skills/from-url.ts"() {
791
+ InputSchema7 = zod.z.object({
792
+ url: zod.z.string().min(1).max(2048).describe("Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata.")
793
+ });
794
+ InputJsonSchema7 = {
512
795
  type: "object",
513
796
  properties: {
514
- name: { type: "string" },
515
- description: { type: "string" },
516
- images: { type: "array", items: { type: "string", format: "uri" } }
797
+ url: {
798
+ type: "string",
799
+ description: "Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata."
800
+ }
517
801
  },
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) {
802
+ required: ["url"]
803
+ };
804
+ fromUrlSkill = {
805
+ name: "pixeldojo:from_url",
806
+ 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.",
807
+ inputZod: InputSchema7,
808
+ inputJsonSchema: InputJsonSchema7,
809
+ // No model dispatch — this skill calls a server-side scrape, not a generation.
810
+ allowedModels: [],
811
+ defaultModel: "",
812
+ run: run7
813
+ };
814
+ }
815
+ });
816
+ async function run8(input, ctx) {
532
817
  const client = ctx.client;
533
818
  const result = await client.runCampaign(input);
534
819
  if (result.kind === "campaign") {
@@ -555,31 +840,67 @@ async function run7(input, ctx) {
555
840
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
556
841
  };
557
842
  }
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.")
843
+ var InputSchema8, InputJsonSchema8, campaignSkill;
844
+ var init_campaign = __esm({
845
+ "src/skills/campaign.ts"() {
846
+ InputSchema8 = zod.z.object({
847
+ productUrl: zod.z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
848
+ product: zod.z.object({
849
+ name: zod.z.string().min(1).max(200),
850
+ description: zod.z.string().max(2e3).optional(),
851
+ images: zod.z.array(zod.z.string().url()).max(5).optional()
852
+ }).optional().describe("Manual product fields, used when productUrl is omitted."),
853
+ tone: zod.z.string().max(120).optional().describe('Optional creative direction ("premium minimalist", "playful Y2K"). Threaded through every prompt.'),
854
+ variants: zod.z.number().int().min(1).max(8).optional().describe("Lifestyle shots in addition to the hero (default 4, max 8)."),
855
+ includeVideo: zod.z.boolean().optional().describe("Whether to also generate a vertical launch video (default true)."),
856
+ videoAspectRatio: zod.z.enum(["9:16", "16:9", "1:1"]).optional().describe("Aspect ratio of the launch video (default 9:16 for Reels/TikTok)."),
857
+ videoMode: zod.z.enum(["lifestyle", "cgi", "ugc", "skip"]).optional().describe("Video styling \u2014 lifestyle (default), cgi, ugc, or skip to omit video.")
858
+ }).refine((v) => v.productUrl || v.product, {
859
+ message: "productUrl or product is required",
860
+ path: ["productUrl"]
861
+ });
862
+ InputJsonSchema8 = {
863
+ type: "object",
864
+ properties: {
865
+ productUrl: {
866
+ type: "string",
867
+ format: "uri",
868
+ description: "Product page URL. We extract name + description + images automatically."
869
+ },
870
+ product: {
871
+ type: "object",
872
+ properties: {
873
+ name: { type: "string" },
874
+ description: { type: "string" },
875
+ images: { type: "array", items: { type: "string", format: "uri" } }
876
+ },
877
+ required: ["name"]
878
+ },
879
+ tone: {
880
+ type: "string",
881
+ description: "Creative direction threaded through every prompt."
882
+ },
883
+ variants: { type: "integer", minimum: 1, maximum: 8 },
884
+ includeVideo: { type: "boolean" },
885
+ videoAspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1"] },
886
+ videoMode: { type: "string", enum: ["lifestyle", "cgi", "ugc", "skip"] }
887
+ },
888
+ oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
889
+ };
890
+ campaignSkill = {
891
+ name: "pixeldojo:campaign",
892
+ 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.",
893
+ inputZod: InputSchema8,
894
+ inputJsonSchema: InputJsonSchema8,
895
+ // Composition skill — internal sub-jobs route to per-tool models, no
896
+ // single allowed-models list applies here.
897
+ allowedModels: [],
898
+ defaultModel: "",
899
+ run: run8
900
+ };
901
+ }
571
902
  });
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) {
903
+ async function run9(input, ctx) {
583
904
  const client = ctx.client;
584
905
  const result = await client.getCampaign(input.campaignId);
585
906
  if (result.kind === "campaign") {
@@ -609,31 +930,488 @@ async function run8(input, ctx) {
609
930
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
610
931
  };
611
932
  }
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
- };
933
+ var InputSchema9, InputJsonSchema9, campaignStatusSkill;
934
+ var init_campaign_status = __esm({
935
+ "src/skills/campaign-status.ts"() {
936
+ InputSchema9 = zod.z.object({
937
+ campaignId: zod.z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
938
+ });
939
+ InputJsonSchema9 = {
940
+ type: "object",
941
+ properties: {
942
+ campaignId: {
943
+ type: "string",
944
+ description: "Campaign id returned from a prior pixeldojo:campaign handoff."
945
+ }
946
+ },
947
+ required: ["campaignId"]
948
+ };
949
+ campaignStatusSkill = {
950
+ name: "pixeldojo:campaign_status",
951
+ 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.",
952
+ inputZod: InputSchema9,
953
+ inputJsonSchema: InputJsonSchema9,
954
+ allowedModels: [],
955
+ defaultModel: "",
956
+ run: run9
957
+ };
958
+ }
959
+ });
960
+ async function run10(input, ctx) {
961
+ let url;
962
+ try {
963
+ url = await uploadLocalFile(input.path, ctx.client);
964
+ } catch (err) {
965
+ if (err instanceof SkillUploadError) {
966
+ return { kind: "text", text: `pixeldojo:upload: ${err.userMessage}` };
967
+ }
968
+ throw err;
969
+ }
970
+ return {
971
+ kind: "text",
972
+ text: `Uploaded ${path.basename(input.path)} \u2192 ${url}
973
+ (temp storage; auto-deletes in ~24h \u2014 pass this URL as image_url to :generate or :edit)`
974
+ };
975
+ }
976
+ var InputSchema10, InputJsonSchema10, uploadSkill;
977
+ var init_upload = __esm({
978
+ "src/skills/upload.ts"() {
979
+ init_upload_helpers();
980
+ InputSchema10 = zod.z.object({
981
+ path: zod.z.string().min(1).max(4096).describe(
982
+ "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."
983
+ )
984
+ });
985
+ InputJsonSchema10 = {
986
+ type: "object",
987
+ properties: {
988
+ path: {
989
+ type: "string",
990
+ 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."
991
+ }
992
+ },
993
+ required: ["path"]
994
+ };
995
+ uploadSkill = {
996
+ name: "pixeldojo:upload",
997
+ 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.",
998
+ inputZod: InputSchema10,
999
+ inputJsonSchema: InputJsonSchema10,
1000
+ allowedModels: [],
1001
+ defaultModel: "",
1002
+ run: run10
1003
+ };
1004
+ }
1005
+ });
1006
+ async function run11(input, ctx) {
1007
+ const requested = input.model ?? DEFAULT_MODEL6;
1008
+ if (!ALLOWED4.includes(requested)) {
1009
+ return {
1010
+ kind: "text",
1011
+ text: `Unknown model "${requested}" for pixeldojo:audio. Allowed: ${ALLOWED4.join(", ")}.`
1012
+ };
1013
+ }
1014
+ const apiId = requested;
1015
+ let audioUrls = input.audio_urls ?? [];
1016
+ if (input.audio_paths?.length) {
1017
+ try {
1018
+ const uploaded = await uploadLocalFiles(input.audio_paths, ctx.client);
1019
+ audioUrls = [...audioUrls, ...uploaded].slice(0, 3);
1020
+ } catch (err) {
1021
+ if (err instanceof SkillUploadError) {
1022
+ return { kind: "text", text: `pixeldojo:audio (upload): ${err.userMessage}` };
1023
+ }
1024
+ throw err;
1025
+ }
1026
+ }
1027
+ const body = { prompt: input.prompt };
1028
+ if (apiId === "seed-audio") {
1029
+ if (input.voice) body.voice = input.voice;
1030
+ if (audioUrls.length) body.audio_urls = audioUrls;
1031
+ if (input.output_format) body.output_format = input.output_format;
1032
+ if (input.speed !== void 0) body.speed = input.speed;
1033
+ } else {
1034
+ body.text = input.prompt;
1035
+ delete body.prompt;
1036
+ if (input.voice) body.voice_id = input.voice;
1037
+ if (input.output_format) body.audio_format = input.output_format;
1038
+ if (input.speed !== void 0) body.speed = input.speed;
1039
+ }
1040
+ const submitted = await ctx.client.run(apiId, body);
1041
+ const result = await pollJobUntilDoneOrHandoff(
1042
+ submitted.jobId,
1043
+ (id) => ctx.client.getJob(id),
1044
+ ctx.buildStatusUrl,
1045
+ {
1046
+ now: ctx.pollOverrides?.now,
1047
+ sleep: ctx.pollOverrides?.sleep
1048
+ }
1049
+ );
1050
+ return pollResultToSkillResult(
1051
+ result,
1052
+ (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
1053
+ );
1054
+ }
1055
+ var ALLOWED4, DEFAULT_MODEL6, InputSchema11, InputJsonSchema11, audioSkill;
1056
+ var init_audio = __esm({
1057
+ "src/skills/audio.ts"() {
1058
+ init_poll();
1059
+ init_upload_helpers();
1060
+ init_types();
1061
+ ALLOWED4 = ["seed-audio", "text-to-speech"];
1062
+ DEFAULT_MODEL6 = "seed-audio";
1063
+ InputSchema11 = zod.z.object({
1064
+ prompt: zod.z.string().min(1).describe(
1065
+ "A script for speech, or a scene description for cinematic audio (dialogue + effects + music). Reference uploaded clips as @Audio1..@Audio3."
1066
+ ),
1067
+ model: zod.z.string().optional().describe(`One of: ${ALLOWED4.join(", ")}. Defaults to ${DEFAULT_MODEL6}.`),
1068
+ voice: zod.z.string().optional().describe("Optional voice preset name or cloned-voice id (seed-audio) / voice_id (text-to-speech)."),
1069
+ audio_urls: zod.z.array(zod.z.string().url()).max(3).optional().describe("seed-audio only: up to 3 reference audio URLs (30s / 10MB max each)."),
1070
+ audio_paths: zod.z.array(zod.z.string()).max(3).optional().describe("seed-audio only: absolute local paths to reference clips; uploaded automatically (24h temp storage)."),
1071
+ output_format: zod.z.string().optional().describe("seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3, wav, flac, pcm."),
1072
+ speed: zod.z.number().min(0.5).max(2).optional().describe("Speech speed multiplier (0.5\u20132).")
1073
+ });
1074
+ InputJsonSchema11 = {
1075
+ type: "object",
1076
+ properties: {
1077
+ prompt: { type: "string" },
1078
+ model: { type: "string", enum: ALLOWED4 },
1079
+ voice: { type: "string" },
1080
+ audio_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 3 },
1081
+ audio_paths: { type: "array", items: { type: "string" }, maxItems: 3 },
1082
+ output_format: { type: "string" },
1083
+ speed: { type: "number", minimum: 0.5, maximum: 2 }
1084
+ },
1085
+ required: ["prompt"]
1086
+ };
1087
+ audioSkill = {
1088
+ name: "pixeldojo:audio",
1089
+ description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain TTS available).",
1090
+ inputZod: InputSchema11,
1091
+ inputJsonSchema: InputJsonSchema11,
1092
+ allowedModels: [...ALLOWED4],
1093
+ defaultModel: DEFAULT_MODEL6,
1094
+ run: run11
1095
+ };
1096
+ }
1097
+ });
1098
+ async function run12(input, ctx) {
1099
+ const res = await ctx.client.listLibrary({
1100
+ q: input.q,
1101
+ model: input.model,
1102
+ modality: input.modality,
1103
+ limit: input.limit ?? 10
1104
+ });
1105
+ if (res.items.length === 0) {
1106
+ return {
1107
+ kind: "text",
1108
+ 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."
1109
+ };
1110
+ }
1111
+ const lines = res.items.map((it) => {
1112
+ const parts = [
1113
+ `- ${it.url}`,
1114
+ it.modality ? `[${it.modality}]` : null,
1115
+ it.model ? `model=${it.model}` : null,
1116
+ `(${it.source}, ${it.createdAt.slice(0, 10)})`,
1117
+ it.prompt ? `"${it.prompt.slice(0, 90)}${it.prompt.length > 90 ? "\u2026" : ""}"` : null
1118
+ ].filter(Boolean);
1119
+ return parts.join(" ");
1120
+ });
1121
+ return {
1122
+ kind: "text",
1123
+ text: `${res.items.length} library item(s):
1124
+ ${lines.join("\n")}
1125
+
1126
+ Pass any URL back into :generate, :edit, :character, or :upscale as a reference input.`
1127
+ };
1128
+ }
1129
+ var InputSchema12, InputJsonSchema12, librarySkill;
1130
+ var init_library = __esm({
1131
+ "src/skills/library.ts"() {
1132
+ InputSchema12 = zod.z.object({
1133
+ q: zod.z.string().optional().describe("Substring match against the prompt that produced the asset."),
1134
+ model: zod.z.string().optional().describe("Restrict to a single model (apiId)."),
1135
+ modality: zod.z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
1136
+ limit: zod.z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
1137
+ });
1138
+ InputJsonSchema12 = {
1139
+ type: "object",
1140
+ properties: {
1141
+ q: { type: "string" },
1142
+ model: { type: "string" },
1143
+ modality: { type: "string", enum: ["image", "video", "audio"] },
1144
+ limit: { type: "integer", minimum: 1, maximum: 50 }
1145
+ },
1146
+ required: []
1147
+ };
1148
+ librarySkill = {
1149
+ name: "pixeldojo:library",
1150
+ description: "Search your own generation history (API/MCP outputs + results kept in the web app). Returns asset URLs to reuse as reference inputs.",
1151
+ inputZod: InputSchema12,
1152
+ inputJsonSchema: InputJsonSchema12,
1153
+ allowedModels: [],
1154
+ defaultModel: "",
1155
+ run: run12
1156
+ };
1157
+ }
1158
+ });
1159
+ function substitutePlaceholders(args, inputs, prevUrl) {
1160
+ const missing = [];
1161
+ const fill = (value) => {
1162
+ if (typeof value === "string") {
1163
+ return value.replace(/\{\{(prev|input\.[a-zA-Z0-9_-]+)\}\}/g, (_, token) => {
1164
+ if (token === "prev") {
1165
+ if (!prevUrl) missing.push("{{prev}}");
1166
+ return prevUrl ?? "";
1167
+ }
1168
+ const key = token.slice("input.".length);
1169
+ if (!(key in inputs)) missing.push(`{{${token}}}`);
1170
+ return inputs[key] ?? "";
1171
+ });
1172
+ }
1173
+ if (Array.isArray(value)) return value.map(fill);
1174
+ if (value && typeof value === "object") {
1175
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, fill(v)]));
1176
+ }
1177
+ return value;
1178
+ };
1179
+ return { resolved: fill(args), missing };
1180
+ }
1181
+ function firstUrl(result) {
1182
+ if (result.kind === "assets" && result.assetUrls.length > 0) return result.assetUrls[0];
1183
+ if (result.kind === "text") {
1184
+ const m = result.text.match(/https?:\/\/\S+/);
1185
+ return m?.[0].replace(/[).,]+$/, "");
1186
+ }
1187
+ return void 0;
1188
+ }
1189
+ async function runSave(input, ctx) {
1190
+ const res = await ctx.client.saveWorkflow({
1191
+ name: input.name,
1192
+ description: input.description,
1193
+ steps: input.steps.map((s) => ({ skill: s.skill, args: s.args ?? {} }))
1194
+ });
1195
+ return {
1196
+ kind: "text",
1197
+ 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"}).`
1198
+ };
1199
+ }
1200
+ async function runList(_input, ctx) {
1201
+ const res = await ctx.client.listWorkflows();
1202
+ if (res.count === 0) {
1203
+ return {
1204
+ kind: "text",
1205
+ text: "No saved workflows yet. Save one with pixeldojo:save_workflow { name, steps: [{ skill, args }] }."
1206
+ };
1207
+ }
1208
+ const lines = res.workflows.map(
1209
+ (w) => `- ${w.name} (${w.steps.length} steps, ${w.runs} runs)${w.description ? `: ${w.description}` : ""}`
1210
+ );
1211
+ return { kind: "text", text: `${res.count} saved workflow(s):
1212
+ ${lines.join("\n")}` };
1213
+ }
1214
+ async function runWorkflow(input, ctx) {
1215
+ const workflow = await ctx.client.getWorkflow(input.name.toLowerCase(), { countRun: true });
1216
+ const { findSkill: findSkill2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
1217
+ const inputs = input.inputs ?? {};
1218
+ const outputs = [];
1219
+ let prevUrl;
1220
+ for (let i = 0; i < workflow.steps.length; i++) {
1221
+ const step = workflow.steps[i];
1222
+ const skill = findSkill2(step.skill);
1223
+ if (!skill) {
1224
+ return {
1225
+ kind: "text",
1226
+ text: `Step ${i + 1} names unknown skill "${step.skill}". Re-save the workflow.`
1227
+ };
1228
+ }
1229
+ const { resolved, missing } = substitutePlaceholders(step.args, inputs, prevUrl);
1230
+ if (missing.length > 0) {
1231
+ return {
1232
+ kind: "text",
1233
+ text: `Step ${i + 1} (${step.skill}) is missing ${Array.from(new Set(missing)).join(", ")}. ${missing.some((m) => m.includes("input.")) ? "Pass the value via run_workflow inputs." : "A previous step produced no asset URL."}`
1234
+ };
1235
+ }
1236
+ const parsed = skill.inputZod.safeParse(resolved);
1237
+ if (!parsed.success) {
1238
+ const issue = parsed.error.issues[0];
1239
+ return {
1240
+ kind: "text",
1241
+ text: `Step ${i + 1} (${step.skill}) has invalid args: ${issue?.path.join(".") || "input"} ${issue?.message || "invalid"}.`
1242
+ };
1243
+ }
1244
+ const result = await skill.run(parsed.data, ctx);
1245
+ const url = firstUrl(result);
1246
+ if (url) {
1247
+ prevUrl = url;
1248
+ outputs.push(`step ${i + 1} (${step.skill}): ${url}`);
1249
+ } else if (result.kind === "text" && /fail|error|invalid|unknown/i.test(result.text)) {
1250
+ return {
1251
+ kind: "text",
1252
+ text: `Workflow "${workflow.name}" stopped at step ${i + 1} (${step.skill}): ${result.text}`
1253
+ };
1254
+ } else {
1255
+ outputs.push(`step ${i + 1} (${step.skill}): done (no asset URL)`);
1256
+ }
1257
+ }
1258
+ return {
1259
+ kind: "text",
1260
+ text: `Workflow "${workflow.name}" completed ${workflow.steps.length} step(s):
1261
+ ${outputs.join("\n")}`
1262
+ };
1263
+ }
1264
+ var StepSchema, SaveInputSchema, RunInputSchema, ListInputSchema, stepJson, saveWorkflowSkill, runWorkflowSkill, listWorkflowsSkill;
1265
+ var init_workflows = __esm({
1266
+ "src/skills/workflows.ts"() {
1267
+ StepSchema = zod.z.object({
1268
+ skill: zod.z.string().regex(/^pixeldojo:(generate|edit|character|storyboard|upscale|audio|upload|from_url)$/, {
1269
+ message: "Runnable skills only: generate, edit, character, storyboard, upscale, audio, upload, from_url."
1270
+ }),
1271
+ args: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
1272
+ });
1273
+ SaveInputSchema = zod.z.object({
1274
+ name: zod.z.string().regex(/^[a-z0-9][a-z0-9-]{1,40}$/, {
1275
+ message: 'Kebab-case, 2-41 chars, e.g. "product-launch-pack".'
1276
+ }).describe("Workflow handle. Saving to an existing name overwrites it."),
1277
+ description: zod.z.string().max(500).optional(),
1278
+ steps: zod.z.array(StepSchema).min(1).max(8)
1279
+ });
1280
+ RunInputSchema = zod.z.object({
1281
+ name: zod.z.string().describe("Saved workflow name."),
1282
+ inputs: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Values for {{input.<key>}} placeholders in the workflow steps.")
1283
+ });
1284
+ ListInputSchema = zod.z.object({});
1285
+ stepJson = {
1286
+ type: "object",
1287
+ properties: {
1288
+ skill: { type: "string" },
1289
+ args: { type: "object" }
1290
+ },
1291
+ required: ["skill"]
1292
+ };
1293
+ saveWorkflowSkill = {
1294
+ name: "pixeldojo:save_workflow",
1295
+ 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.",
1296
+ inputZod: SaveInputSchema,
1297
+ inputJsonSchema: {
1298
+ type: "object",
1299
+ properties: {
1300
+ name: { type: "string", pattern: "^[a-z0-9][a-z0-9-]{1,40}$" },
1301
+ description: { type: "string" },
1302
+ steps: { type: "array", items: stepJson, minItems: 1, maxItems: 8 }
1303
+ },
1304
+ required: ["name", "steps"]
1305
+ },
1306
+ allowedModels: [],
1307
+ defaultModel: "",
1308
+ run: runSave
1309
+ };
1310
+ runWorkflowSkill = {
1311
+ name: "pixeldojo:run_workflow",
1312
+ description: "Run a saved workflow by name. Pass inputs for its {{input.<key>}} placeholders; each step feeds the next via {{prev}}.",
1313
+ inputZod: RunInputSchema,
1314
+ inputJsonSchema: {
1315
+ type: "object",
1316
+ properties: {
1317
+ name: { type: "string" },
1318
+ inputs: { type: "object", additionalProperties: { type: "string" } }
1319
+ },
1320
+ required: ["name"]
1321
+ },
1322
+ allowedModels: [],
1323
+ defaultModel: "",
1324
+ run: runWorkflow
1325
+ };
1326
+ listWorkflowsSkill = {
1327
+ name: "pixeldojo:list_workflows",
1328
+ description: "List your saved workflows with step counts and run totals.",
1329
+ inputZod: ListInputSchema,
1330
+ inputJsonSchema: { type: "object", properties: {}, required: [] },
1331
+ allowedModels: [],
1332
+ defaultModel: "",
1333
+ run: runList
1334
+ };
1335
+ }
1336
+ });
621
1337
 
622
1338
  // src/skills/index.ts
623
- var ALL_SKILLS = [
624
- generateSkill,
625
- characterSkill,
626
- storyboardSkill,
627
- upscaleSkill,
628
- statusSkill,
629
- fromUrlSkill,
630
- campaignSkill,
631
- campaignStatusSkill
632
- ];
1339
+ var skills_exports = {};
1340
+ __export(skills_exports, {
1341
+ ALL_SKILLS: () => exports.ALL_SKILLS,
1342
+ audioSkill: () => audioSkill,
1343
+ campaignSkill: () => campaignSkill,
1344
+ campaignStatusSkill: () => campaignStatusSkill,
1345
+ characterSkill: () => characterSkill,
1346
+ editSkill: () => editSkill,
1347
+ findSkill: () => findSkill,
1348
+ fromUrlSkill: () => fromUrlSkill,
1349
+ generateSkill: () => generateSkill,
1350
+ librarySkill: () => librarySkill,
1351
+ listWorkflowsSkill: () => listWorkflowsSkill,
1352
+ runWorkflowSkill: () => runWorkflowSkill,
1353
+ saveWorkflowSkill: () => saveWorkflowSkill,
1354
+ statusSkill: () => statusSkill,
1355
+ storyboardSkill: () => storyboardSkill,
1356
+ uploadSkill: () => uploadSkill,
1357
+ upscaleSkill: () => upscaleSkill
1358
+ });
633
1359
  function findSkill(name) {
634
- return ALL_SKILLS.find((s) => s.name === name);
1360
+ return exports.ALL_SKILLS.find((s) => s.name === name);
1361
+ }
1362
+ exports.ALL_SKILLS = void 0;
1363
+ var init_skills = __esm({
1364
+ "src/skills/index.ts"() {
1365
+ init_generate();
1366
+ init_edit();
1367
+ init_character();
1368
+ init_storyboard();
1369
+ init_upscale();
1370
+ init_status();
1371
+ init_from_url();
1372
+ init_campaign();
1373
+ init_campaign_status();
1374
+ init_upload();
1375
+ init_audio();
1376
+ init_library();
1377
+ init_workflows();
1378
+ exports.ALL_SKILLS = [
1379
+ generateSkill,
1380
+ editSkill,
1381
+ characterSkill,
1382
+ storyboardSkill,
1383
+ upscaleSkill,
1384
+ audioSkill,
1385
+ librarySkill,
1386
+ saveWorkflowSkill,
1387
+ runWorkflowSkill,
1388
+ listWorkflowsSkill,
1389
+ statusSkill,
1390
+ fromUrlSkill,
1391
+ uploadSkill,
1392
+ campaignSkill,
1393
+ campaignStatusSkill
1394
+ ];
1395
+ }
1396
+ });
1397
+ function makeAttributedClient(options) {
1398
+ const { mcpVersion, skillName, baseFetch, ...sdkOptions } = options;
1399
+ const real = baseFetch ?? globalThis.fetch.bind(globalThis);
1400
+ const wrappedFetch = (input, init) => {
1401
+ const headers = new Headers(init?.headers);
1402
+ headers.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
1403
+ if (skillName) headers.set("x-pixeldojo-skill", skillName);
1404
+ return real(input, { ...init, headers });
1405
+ };
1406
+ return new sdk.PixelDojoClient({
1407
+ ...sdkOptions,
1408
+ fetch: wrappedFetch
1409
+ });
635
1410
  }
636
1411
 
1412
+ // src/server.ts
1413
+ init_skills();
1414
+
637
1415
  // src/redact.ts
638
1416
  var PD_KEY_RE = /pd_[A-Za-z0-9_-]{8,}/g;
639
1417
  var REDACTED = "[redacted]";
@@ -690,7 +1468,7 @@ function buildServer(options) {
690
1468
  );
691
1469
  server.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
692
1470
  return {
693
- tools: ALL_SKILLS.map((skill) => ({
1471
+ tools: exports.ALL_SKILLS.map((skill) => ({
694
1472
  name: skill.name,
695
1473
  description: skill.description,
696
1474
  inputSchema: skill.inputJsonSchema
@@ -701,7 +1479,7 @@ function buildServer(options) {
701
1479
  const skillName = req.params.name;
702
1480
  const skill = findSkill(skillName);
703
1481
  if (!skill) {
704
- const available = ALL_SKILLS.map((s) => s.name).join(", ");
1482
+ const available = exports.ALL_SKILLS.map((s) => s.name).join(", ");
705
1483
  return {
706
1484
  isError: true,
707
1485
  content: [
@@ -802,9 +1580,13 @@ async function runStdio(options) {
802
1580
  await server.connect(transport);
803
1581
  }
804
1582
 
805
- exports.ALL_SKILLS = ALL_SKILLS;
1583
+ // src/index.ts
1584
+ init_skills();
1585
+ init_poll();
1586
+
806
1587
  exports.PACKAGE_VERSION = PACKAGE_VERSION;
807
1588
  exports.buildServer = buildServer;
1589
+ exports.errorToActionableMessage = errorToActionableMessage;
808
1590
  exports.findSkill = findSkill;
809
1591
  exports.makeAttributedClient = makeAttributedClient;
810
1592
  exports.makeRedactedLogger = makeRedactedLogger;
@@ -812,5 +1594,6 @@ exports.pollJobUntilDoneOrHandoff = pollJobUntilDoneOrHandoff;
812
1594
  exports.redactKeys = redactKeys;
813
1595
  exports.redactValue = redactValue;
814
1596
  exports.runStdio = runStdio;
1597
+ exports.skillResultToMcpResponse = skillResultToMcpResponse;
815
1598
  //# sourceMappingURL=index.cjs.map
816
1599
  //# sourceMappingURL=index.cjs.map