@pixeldojo/mcp 0.3.0 → 0.5.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.js CHANGED
@@ -1,31 +1,22 @@
1
+ import { stat, readFile } from 'fs/promises';
2
+ import { basename, extname } from 'path';
3
+ import { z } from 'zod';
1
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
6
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
7
  import { PixelDojoClient } from '@pixeldojo/sdk';
5
- import { z } from 'zod';
6
- import { stat, readFile } from 'fs/promises';
7
- import { basename, extname } from 'path';
8
8
 
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 PixelDojoClient({
20
- ...sdkOptions,
21
- fetch: wrappedFetch
22
- });
23
- }
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __esm = (fn, res) => function __init() {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
24
18
 
25
19
  // 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
20
  function defaultSleep(ms, signal) {
30
21
  return new Promise((resolve, reject) => {
31
22
  if (signal?.aborted) {
@@ -74,6 +65,14 @@ async function pollJobUntilDoneOrHandoff(jobId, fetchJob, buildStatusUrl, option
74
65
  attempt++;
75
66
  }
76
67
  }
68
+ var DEFAULT_BACKOFF, DEFAULT_MAX_INTERVAL, DEFAULT_SYNC_BUDGET_MS;
69
+ var init_poll = __esm({
70
+ "src/poll.ts"() {
71
+ DEFAULT_BACKOFF = [1e3, 2e3, 4e3, 8e3];
72
+ DEFAULT_MAX_INTERVAL = 8e3;
73
+ DEFAULT_SYNC_BUDGET_MS = 3e4;
74
+ }
75
+ });
77
76
 
78
77
  // src/skills/types.ts
79
78
  function pollResultToSkillResult(result, successText) {
@@ -108,26 +107,10 @@ function extractAssetUrls(job) {
108
107
  if (output?.video) return [output.video];
109
108
  return [];
110
109
  }
111
- var MAX_IMAGE_BYTES = 50 * 1024 * 1024;
112
- var MAX_VIDEO_BYTES = 200 * 1024 * 1024;
113
- var EXT_TO_MIME = {
114
- ".jpg": "image/jpeg",
115
- ".jpeg": "image/jpeg",
116
- ".png": "image/png",
117
- ".webp": "image/webp",
118
- ".gif": "image/gif",
119
- ".mp4": "video/mp4",
120
- ".webm": "video/webm",
121
- ".mov": "video/quicktime"
122
- };
123
- var SkillUploadError = class extends Error {
124
- constructor(userMessage) {
125
- super(userMessage);
126
- this.userMessage = userMessage;
127
- this.name = "SkillUploadError";
110
+ var init_types = __esm({
111
+ "src/skills/types.ts"() {
128
112
  }
129
- userMessage;
130
- };
113
+ });
131
114
  async function uploadLocalFile(path, client) {
132
115
  const ext = extname(path).toLowerCase();
133
116
  const mime = EXT_TO_MIME[ext];
@@ -173,46 +156,31 @@ async function uploadLocalFile(path, client) {
173
156
  async function uploadLocalFiles(paths, client) {
174
157
  return Promise.all(paths.map((p) => uploadLocalFile(p, client)));
175
158
  }
176
-
177
- // src/skills/generate.ts
178
- var InputSchema = z.object({
179
- prompt: z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
180
- model: z.string().optional().describe(
181
- '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.'
182
- ),
183
- preset: z.string().optional().describe(
184
- '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.'
185
- ),
186
- aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
187
- duration: z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
188
- image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
189
- image_path: z.string().min(1).optional().describe(
190
- "Absolute local file path to a reference image/video. The skill uploads it to PixelDojo first (24h temp storage) and then dispatches. Prefer this over image_url when the user has the file on disk."
191
- )
159
+ var MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, EXT_TO_MIME, SkillUploadError;
160
+ var init_upload_helpers = __esm({
161
+ "src/skills/upload-helpers.ts"() {
162
+ MAX_IMAGE_BYTES = 50 * 1024 * 1024;
163
+ MAX_VIDEO_BYTES = 200 * 1024 * 1024;
164
+ EXT_TO_MIME = {
165
+ ".jpg": "image/jpeg",
166
+ ".jpeg": "image/jpeg",
167
+ ".png": "image/png",
168
+ ".webp": "image/webp",
169
+ ".gif": "image/gif",
170
+ ".mp4": "video/mp4",
171
+ ".webm": "video/webm",
172
+ ".mov": "video/quicktime"
173
+ };
174
+ SkillUploadError = class extends Error {
175
+ constructor(userMessage) {
176
+ super(userMessage);
177
+ this.userMessage = userMessage;
178
+ this.name = "SkillUploadError";
179
+ }
180
+ userMessage;
181
+ };
182
+ }
192
183
  });
193
- var DEFAULT_MODEL = "google-nano-banana";
194
- var InputJsonSchema = {
195
- type: "object",
196
- properties: {
197
- prompt: { type: "string", description: "Prompt describing what to generate." },
198
- model: {
199
- type: "string",
200
- 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.'
201
- },
202
- preset: {
203
- type: "string",
204
- description: 'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean"). Explicit fields override preset values.'
205
- },
206
- aspect_ratio: { type: "string" },
207
- duration: { type: "integer" },
208
- image_url: { type: "string", format: "uri" },
209
- image_path: {
210
- type: "string",
211
- description: "Absolute local file path. The skill uploads it to PixelDojo (24h temp storage) and uses the resulting URL as the reference image. Prefer this over image_url when the user has a local file."
212
- }
213
- },
214
- required: ["prompt"]
215
- };
216
184
  function buildPayload(apiId, input) {
217
185
  const body = { prompt: input.prompt };
218
186
  if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
@@ -281,77 +249,61 @@ async function run(input, ctx) {
281
249
  (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(", ")}`
282
250
  );
283
251
  }
284
- var generateSkill = {
285
- name: "pixeldojo:generate",
286
- description: "Generate any image or video from a prompt. Auto-routes to a sensible default model; pass `model` to pick a specific apiId from the catalog. For reference-image / image-to-video flows: pass `image_url` if you already have a URL, or `image_path` (absolute local path) and the skill uploads it to 24h temp storage automatically.",
287
- inputZod: InputSchema,
288
- inputJsonSchema: InputJsonSchema,
289
- allowedModels: null,
290
- defaultModel: DEFAULT_MODEL,
291
- run
292
- };
293
- var DEFAULT_MODEL2 = "wan-2.7-image-edit";
294
- var ALLOWED_MODELS = [
295
- "wan-2.7-image-edit",
296
- "wan-2.6-image-edit",
297
- "flux-edit",
298
- "xai-image-edit",
299
- "p-image-edit",
300
- "gpt-image-2-edit",
301
- "hidream-edit",
302
- "google-nano-banana"
303
- ];
304
- var InputSchema2 = z.object({
305
- prompt: z.string().min(1).max(5e3).describe("Edit instruction."),
306
- image_url: z.string().url().optional().describe("Single reference image to edit. Pass image_urls for multi-image edits."),
307
- image_urls: z.array(z.string().url()).min(1).max(9).optional().describe("Multiple reference images (1-9). Used for composition/multi-ref edits."),
308
- image_path: z.string().min(1).optional().describe(
309
- "Absolute local file path. Skill uploads it (24h temp storage) and edits it. Prefer over image_url when the user has the file on disk."
310
- ),
311
- image_paths: z.array(z.string().min(1)).min(1).max(9).optional().describe(
312
- "Multiple absolute local file paths (1-9) for multi-image edits. Each uploaded to 24h temp storage before dispatch."
313
- ),
314
- model: z.enum(ALLOWED_MODELS).optional().describe(
315
- "Override the default edit model. Defaults to wan-2.7-image-edit (most popular)."
316
- ),
317
- aspect_ratio: z.string().optional().describe('Aspect ratio when supported (e.g. "1:1", "16:9", "match_input_image").')
252
+ var InputSchema, DEFAULT_MODEL, InputJsonSchema, generateSkill;
253
+ var init_generate = __esm({
254
+ "src/skills/generate.ts"() {
255
+ init_poll();
256
+ init_types();
257
+ init_upload_helpers();
258
+ InputSchema = z.object({
259
+ prompt: z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
260
+ model: z.string().optional().describe(
261
+ '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.'
262
+ ),
263
+ preset: z.string().optional().describe(
264
+ '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.'
265
+ ),
266
+ aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
267
+ duration: z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
268
+ image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools."),
269
+ image_path: z.string().min(1).optional().describe(
270
+ "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."
271
+ )
272
+ });
273
+ DEFAULT_MODEL = "google-nano-banana";
274
+ InputJsonSchema = {
275
+ type: "object",
276
+ properties: {
277
+ prompt: { type: "string", description: "Prompt describing what to generate." },
278
+ model: {
279
+ type: "string",
280
+ 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.'
281
+ },
282
+ preset: {
283
+ type: "string",
284
+ description: 'Preset slug to seed the params (e.g. "cinematic-portrait", "product-floating-clean"). Explicit fields override preset values.'
285
+ },
286
+ aspect_ratio: { type: "string" },
287
+ duration: { type: "integer" },
288
+ image_url: { type: "string", format: "uri" },
289
+ image_path: {
290
+ type: "string",
291
+ 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."
292
+ }
293
+ },
294
+ required: ["prompt"]
295
+ };
296
+ generateSkill = {
297
+ name: "pixeldojo:generate",
298
+ 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.",
299
+ inputZod: InputSchema,
300
+ inputJsonSchema: InputJsonSchema,
301
+ allowedModels: null,
302
+ defaultModel: DEFAULT_MODEL,
303
+ run
304
+ };
305
+ }
318
306
  });
319
- var InputJsonSchema2 = {
320
- type: "object",
321
- properties: {
322
- prompt: { type: "string", description: "Edit instruction." },
323
- image_url: {
324
- type: "string",
325
- format: "uri",
326
- description: "Single reference image. Pass image_urls for multi-image edits."
327
- },
328
- image_urls: {
329
- type: "array",
330
- items: { type: "string", format: "uri" },
331
- minItems: 1,
332
- maxItems: 9,
333
- description: "Multiple reference images (1-9)."
334
- },
335
- image_path: {
336
- type: "string",
337
- description: "Absolute local file path. Skill uploads it (24h temp storage) and edits the resulting URL. Prefer over image_url when the user has a local file."
338
- },
339
- image_paths: {
340
- type: "array",
341
- items: { type: "string" },
342
- minItems: 1,
343
- maxItems: 9,
344
- description: "Multiple absolute local file paths (1-9). Each uploaded before dispatch."
345
- },
346
- model: {
347
- type: "string",
348
- enum: [...ALLOWED_MODELS],
349
- description: "Override edit model. Default: wan-2.7-image-edit."
350
- },
351
- aspect_ratio: { type: "string" }
352
- },
353
- required: ["prompt"]
354
- };
355
307
  function buildPayload2(apiId, input) {
356
308
  const urls = input.image_urls && input.image_urls.length > 0 ? input.image_urls : input.image_url ? [input.image_url] : [];
357
309
  switch (apiId) {
@@ -371,7 +323,6 @@ function buildPayload2(apiId, input) {
371
323
  case "flux-edit":
372
324
  case "xai-image-edit":
373
325
  case "gpt-image-2-edit":
374
- case "hidream-edit":
375
326
  return {
376
327
  prompt: input.prompt,
377
328
  image_url: urls[0],
@@ -433,39 +384,84 @@ async function run2(input, ctx) {
433
384
  (urls) => urls.length === 0 ? `Edit job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Edit complete. Asset: ${urls[0]}` : `Edit produced ${urls.length} assets. URLs: ${urls.join(", ")}`
434
385
  );
435
386
  }
436
- var editSkill = {
437
- name: "pixeldojo:edit",
438
- description: "Edit an existing image with a text instruction. Defaults to wan-2.7-image-edit; pass `model` to pick another edit-capable apiId. Supports up to 9 reference images for multi-image composition. For reference images: pass image_url(s) for hosted URLs or image_path(s) for absolute local file paths (skill uploads to 24h temp storage automatically).",
439
- inputZod: InputSchema2,
440
- inputJsonSchema: InputJsonSchema2,
441
- allowedModels: [...ALLOWED_MODELS],
442
- defaultModel: DEFAULT_MODEL2,
443
- run: run2
444
- };
445
- var ALLOWED = ["ideogram-character", "flux-edit", "gpt-image-2-edit"];
446
- var DEFAULT_MODEL3 = "ideogram-character";
447
- var InputSchema3 = z.object({
448
- prompt: z.string().min(1).max(8e3),
449
- model: z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`),
450
- reference_image_url: z.string().url().optional().describe(
451
- "Reference image of the character. Required by flux-edit and gpt-image-2-edit; optional for ideogram-character."
452
- ),
453
- aspect_ratio: z.string().optional()
387
+ var DEFAULT_MODEL2, ALLOWED_MODELS, InputSchema2, InputJsonSchema2, editSkill;
388
+ var init_edit = __esm({
389
+ "src/skills/edit.ts"() {
390
+ init_poll();
391
+ init_types();
392
+ init_upload_helpers();
393
+ DEFAULT_MODEL2 = "wan-2.7-image-edit";
394
+ ALLOWED_MODELS = [
395
+ "wan-2.7-image-edit",
396
+ "wan-2.6-image-edit",
397
+ "flux-edit",
398
+ "xai-image-edit",
399
+ "p-image-edit",
400
+ "gpt-image-2-edit",
401
+ "google-nano-banana"
402
+ ];
403
+ InputSchema2 = z.object({
404
+ prompt: z.string().min(1).max(5e3).describe("Edit instruction."),
405
+ image_url: z.string().url().optional().describe("Single reference image to edit. Pass image_urls for multi-image edits."),
406
+ image_urls: z.array(z.string().url()).min(1).max(9).optional().describe("Multiple reference images (1-9). Used for composition/multi-ref edits."),
407
+ image_path: z.string().min(1).optional().describe(
408
+ "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."
409
+ ),
410
+ image_paths: z.array(z.string().min(1)).min(1).max(9).optional().describe(
411
+ "Multiple absolute local file paths (1-9) for multi-image edits. Each uploaded to 24h temp storage before dispatch."
412
+ ),
413
+ model: z.enum(ALLOWED_MODELS).optional().describe(
414
+ "Override the default edit model. Defaults to wan-2.7-image-edit (most popular)."
415
+ ),
416
+ aspect_ratio: z.string().optional().describe('Aspect ratio when supported (e.g. "1:1", "16:9", "match_input_image").')
417
+ });
418
+ InputJsonSchema2 = {
419
+ type: "object",
420
+ properties: {
421
+ prompt: { type: "string", description: "Edit instruction." },
422
+ image_url: {
423
+ type: "string",
424
+ format: "uri",
425
+ description: "Single reference image. Pass image_urls for multi-image edits."
426
+ },
427
+ image_urls: {
428
+ type: "array",
429
+ items: { type: "string", format: "uri" },
430
+ minItems: 1,
431
+ maxItems: 9,
432
+ description: "Multiple reference images (1-9)."
433
+ },
434
+ image_path: {
435
+ type: "string",
436
+ 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."
437
+ },
438
+ image_paths: {
439
+ type: "array",
440
+ items: { type: "string" },
441
+ minItems: 1,
442
+ maxItems: 9,
443
+ description: "Multiple absolute local file paths (1-9). Each uploaded before dispatch."
444
+ },
445
+ model: {
446
+ type: "string",
447
+ enum: [...ALLOWED_MODELS],
448
+ description: "Override edit model. Default: wan-2.7-image-edit."
449
+ },
450
+ aspect_ratio: { type: "string" }
451
+ },
452
+ required: ["prompt"]
453
+ };
454
+ editSkill = {
455
+ name: "pixeldojo:edit",
456
+ 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).",
457
+ inputZod: InputSchema2,
458
+ inputJsonSchema: InputJsonSchema2,
459
+ allowedModels: [...ALLOWED_MODELS],
460
+ defaultModel: DEFAULT_MODEL2,
461
+ run: run2
462
+ };
463
+ }
454
464
  });
455
- var InputJsonSchema3 = {
456
- type: "object",
457
- properties: {
458
- prompt: { type: "string" },
459
- model: {
460
- type: "string",
461
- enum: ALLOWED,
462
- description: `One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`
463
- },
464
- reference_image_url: { type: "string", format: "uri" },
465
- aspect_ratio: { type: "string" }
466
- },
467
- required: ["prompt"]
468
- };
469
465
  async function run3(input, ctx) {
470
466
  const requested = input.model ?? DEFAULT_MODEL3;
471
467
  if (!ALLOWED.includes(requested)) {
@@ -502,40 +498,46 @@ async function run3(input, ctx) {
502
498
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Character shot ready. Asset: ${urls[0]}`
503
499
  );
504
500
  }
505
- var characterSkill = {
506
- name: "pixeldojo:character",
507
- description: "Generate or edit consistent characters across shots. Pass a reference_image_url for edit-style models.",
508
- inputZod: InputSchema3,
509
- inputJsonSchema: InputJsonSchema3,
510
- allowedModels: [...ALLOWED],
511
- defaultModel: DEFAULT_MODEL3,
512
- run: run3
513
- };
514
- var ALLOWED2 = ["veo-3.1", "kling-video", "seedance-2"];
515
- var DEFAULT_MODEL4 = "veo-3.1";
516
- var InputSchema4 = z.object({
517
- brief: z.string().min(1).max(8e3).describe("Storyboard brief: scene, shots, mood, action."),
518
- model: z.string().optional().describe(`One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`),
519
- aspect_ratio: z.string().optional(),
520
- duration: z.number().int().positive().optional()
501
+ var ALLOWED, DEFAULT_MODEL3, InputSchema3, InputJsonSchema3, characterSkill;
502
+ var init_character = __esm({
503
+ "src/skills/character.ts"() {
504
+ init_poll();
505
+ init_types();
506
+ ALLOWED = ["ideogram-character", "flux-edit", "gpt-image-2-edit"];
507
+ DEFAULT_MODEL3 = "ideogram-character";
508
+ InputSchema3 = z.object({
509
+ prompt: z.string().min(1).max(8e3),
510
+ model: z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`),
511
+ reference_image_url: z.string().url().optional().describe(
512
+ "Reference image of the character. Required by flux-edit and gpt-image-2-edit; optional for ideogram-character."
513
+ ),
514
+ aspect_ratio: z.string().optional()
515
+ });
516
+ InputJsonSchema3 = {
517
+ type: "object",
518
+ properties: {
519
+ prompt: { type: "string" },
520
+ model: {
521
+ type: "string",
522
+ enum: ALLOWED,
523
+ description: `One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`
524
+ },
525
+ reference_image_url: { type: "string", format: "uri" },
526
+ aspect_ratio: { type: "string" }
527
+ },
528
+ required: ["prompt"]
529
+ };
530
+ characterSkill = {
531
+ name: "pixeldojo:character",
532
+ description: "Generate or edit consistent characters across shots. Pass a reference_image_url for edit-style models.",
533
+ inputZod: InputSchema3,
534
+ inputJsonSchema: InputJsonSchema3,
535
+ allowedModels: [...ALLOWED],
536
+ defaultModel: DEFAULT_MODEL3,
537
+ run: run3
538
+ };
539
+ }
521
540
  });
522
- var InputJsonSchema4 = {
523
- type: "object",
524
- properties: {
525
- brief: {
526
- type: "string",
527
- description: "Storyboard brief: scene, shots, mood, action."
528
- },
529
- model: {
530
- type: "string",
531
- enum: ALLOWED2,
532
- description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`
533
- },
534
- aspect_ratio: { type: "string" },
535
- duration: { type: "integer" }
536
- },
537
- required: ["brief"]
538
- };
539
541
  async function run4(input, ctx) {
540
542
  const requested = input.model ?? DEFAULT_MODEL4;
541
543
  if (!ALLOWED2.includes(requested)) {
@@ -563,33 +565,47 @@ async function run4(input, ctx) {
563
565
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Storyboard preview ready. Asset: ${urls[0]}`
564
566
  );
565
567
  }
566
- var storyboardSkill = {
567
- name: "pixeldojo:storyboard",
568
- description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
569
- inputZod: InputSchema4,
570
- inputJsonSchema: InputJsonSchema4,
571
- allowedModels: [...ALLOWED2],
572
- defaultModel: DEFAULT_MODEL4,
573
- run: run4
574
- };
575
- var ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance"];
576
- var DEFAULT_MODEL5 = "magnific-upscaler";
577
- var InputSchema5 = z.object({
578
- image_url: z.string().url().describe("URL of the source image to upscale."),
579
- model: z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL5}.`),
580
- scale_factor: z.number().int().min(1).max(4).optional().describe("Magnific only: 1, 2, or 4. Defaults to 2."),
581
- creativity: z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
568
+ var ALLOWED2, DEFAULT_MODEL4, InputSchema4, InputJsonSchema4, storyboardSkill;
569
+ var init_storyboard = __esm({
570
+ "src/skills/storyboard.ts"() {
571
+ init_poll();
572
+ init_types();
573
+ ALLOWED2 = ["veo-3.1", "kling-video", "seedance-2"];
574
+ DEFAULT_MODEL4 = "veo-3.1";
575
+ InputSchema4 = z.object({
576
+ brief: z.string().min(1).max(8e3).describe("Storyboard brief: scene, shots, mood, action."),
577
+ model: z.string().optional().describe(`One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`),
578
+ aspect_ratio: z.string().optional(),
579
+ duration: z.number().int().positive().optional()
580
+ });
581
+ InputJsonSchema4 = {
582
+ type: "object",
583
+ properties: {
584
+ brief: {
585
+ type: "string",
586
+ description: "Storyboard brief: scene, shots, mood, action."
587
+ },
588
+ model: {
589
+ type: "string",
590
+ enum: ALLOWED2,
591
+ description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`
592
+ },
593
+ aspect_ratio: { type: "string" },
594
+ duration: { type: "integer" }
595
+ },
596
+ required: ["brief"]
597
+ };
598
+ storyboardSkill = {
599
+ name: "pixeldojo:storyboard",
600
+ description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
601
+ inputZod: InputSchema4,
602
+ inputJsonSchema: InputJsonSchema4,
603
+ allowedModels: [...ALLOWED2],
604
+ defaultModel: DEFAULT_MODEL4,
605
+ run: run4
606
+ };
607
+ }
582
608
  });
583
- var InputJsonSchema5 = {
584
- type: "object",
585
- properties: {
586
- image_url: { type: "string", format: "uri" },
587
- model: { type: "string", enum: ALLOWED3 },
588
- scale_factor: { type: "integer", minimum: 1, maximum: 4 },
589
- creativity: { type: "number", minimum: 0, maximum: 1 }
590
- },
591
- required: ["image_url"]
592
- };
593
609
  async function run5(input, ctx) {
594
610
  const requested = input.model ?? DEFAULT_MODEL5;
595
611
  if (!ALLOWED3.includes(requested)) {
@@ -622,30 +638,40 @@ async function run5(input, ctx) {
622
638
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Upscaled. Asset: ${urls[0]}`
623
639
  );
624
640
  }
625
- var upscaleSkill = {
626
- name: "pixeldojo:upscale",
627
- description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
628
- inputZod: InputSchema5,
629
- inputJsonSchema: InputJsonSchema5,
630
- allowedModels: [...ALLOWED3],
631
- defaultModel: DEFAULT_MODEL5,
632
- run: run5
633
- };
634
- var InputSchema6 = z.object({
635
- jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
641
+ var ALLOWED3, DEFAULT_MODEL5, InputSchema5, InputJsonSchema5, upscaleSkill;
642
+ var init_upscale = __esm({
643
+ "src/skills/upscale.ts"() {
644
+ init_poll();
645
+ init_types();
646
+ ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance"];
647
+ DEFAULT_MODEL5 = "magnific-upscaler";
648
+ InputSchema5 = z.object({
649
+ image_url: z.string().url().describe("URL of the source image to upscale."),
650
+ model: z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL5}.`),
651
+ scale_factor: z.number().int().min(1).max(4).optional().describe("Magnific only: 1, 2, or 4. Defaults to 2."),
652
+ creativity: z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
653
+ });
654
+ InputJsonSchema5 = {
655
+ type: "object",
656
+ properties: {
657
+ image_url: { type: "string", format: "uri" },
658
+ model: { type: "string", enum: ALLOWED3 },
659
+ scale_factor: { type: "integer", minimum: 1, maximum: 4 },
660
+ creativity: { type: "number", minimum: 0, maximum: 1 }
661
+ },
662
+ required: ["image_url"]
663
+ };
664
+ upscaleSkill = {
665
+ name: "pixeldojo:upscale",
666
+ description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
667
+ inputZod: InputSchema5,
668
+ inputJsonSchema: InputJsonSchema5,
669
+ allowedModels: [...ALLOWED3],
670
+ defaultModel: DEFAULT_MODEL5,
671
+ run: run5
672
+ };
673
+ }
636
674
  });
637
- var InputJsonSchema6 = {
638
- type: "object",
639
- properties: {
640
- jobId: {
641
- type: "string",
642
- description: "Job id returned from a prior pixeldojo:* skill handoff."
643
- }
644
- },
645
- required: ["jobId"]
646
- };
647
- var STUCK_HINT_MS = 5 * 60 * 1e3;
648
- var ABANDON_HINT_MS = 12 * 60 * 1e3;
649
675
  async function run6(input, ctx) {
650
676
  const job = await ctx.client.getJob(input.jobId);
651
677
  if (job.status === "completed") {
@@ -710,29 +736,36 @@ function extractAssetUrls2(job) {
710
736
  if (output?.video) return [output.video];
711
737
  return [];
712
738
  }
713
- var statusSkill = {
714
- name: "pixeldojo:status",
715
- description: "Check the status of a long-running job after a 30s sync handoff. Returns assets when ready, failure reason if failed, or current state if still running.",
716
- inputZod: InputSchema6,
717
- inputJsonSchema: InputJsonSchema6,
718
- // status is special — it doesn't dispatch to a model
719
- allowedModels: [],
720
- defaultModel: "",
721
- run: run6
722
- };
723
- var InputSchema7 = z.object({
724
- url: z.string().min(1).max(2048).describe("Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata.")
739
+ var InputSchema6, InputJsonSchema6, STUCK_HINT_MS, ABANDON_HINT_MS, statusSkill;
740
+ var init_status = __esm({
741
+ "src/skills/status.ts"() {
742
+ InputSchema6 = z.object({
743
+ jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
744
+ });
745
+ InputJsonSchema6 = {
746
+ type: "object",
747
+ properties: {
748
+ jobId: {
749
+ type: "string",
750
+ description: "Job id returned from a prior pixeldojo:* skill handoff."
751
+ }
752
+ },
753
+ required: ["jobId"]
754
+ };
755
+ STUCK_HINT_MS = 5 * 60 * 1e3;
756
+ ABANDON_HINT_MS = 12 * 60 * 1e3;
757
+ statusSkill = {
758
+ name: "pixeldojo:status",
759
+ 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.",
760
+ inputZod: InputSchema6,
761
+ inputJsonSchema: InputJsonSchema6,
762
+ // status is special — it doesn't dispatch to a model
763
+ allowedModels: [],
764
+ defaultModel: "",
765
+ run: run6
766
+ };
767
+ }
725
768
  });
726
- var InputJsonSchema7 = {
727
- type: "object",
728
- properties: {
729
- url: {
730
- type: "string",
731
- description: "Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata."
732
- }
733
- },
734
- required: ["url"]
735
- };
736
769
  async function run7(input, ctx) {
737
770
  const product = await ctx.client.extractProduct(input.url);
738
771
  const summary = [
@@ -750,60 +783,34 @@ function truncate(input, max) {
750
783
  if (input.length <= max) return input;
751
784
  return input.slice(0, max - 3) + "...";
752
785
  }
753
- var fromUrlSkill = {
754
- name: "pixeldojo:from_url",
755
- description: "Extract product info (name, description, images) from any product page URL. Use this as the first step before pixeldojo:campaign / :ad / :lookbook so the agent has structured product data instead of a free-text description.",
756
- inputZod: InputSchema7,
757
- inputJsonSchema: InputJsonSchema7,
758
- // No model dispatch — this skill calls a server-side scrape, not a generation.
759
- allowedModels: [],
760
- defaultModel: "",
761
- run: run7
762
- };
763
- var InputSchema8 = z.object({
764
- productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
765
- product: z.object({
766
- name: z.string().min(1).max(200),
767
- description: z.string().max(2e3).optional(),
768
- images: z.array(z.string().url()).max(5).optional()
769
- }).optional().describe("Manual product fields, used when productUrl is omitted."),
770
- tone: z.string().max(120).optional().describe('Optional creative direction ("premium minimalist", "playful Y2K"). Threaded through every prompt.'),
771
- variants: z.number().int().min(1).max(8).optional().describe("Lifestyle shots in addition to the hero (default 4, max 8)."),
772
- includeVideo: z.boolean().optional().describe("Whether to also generate a vertical launch video (default true)."),
773
- videoAspectRatio: z.enum(["9:16", "16:9", "1:1"]).optional().describe("Aspect ratio of the launch video (default 9:16 for Reels/TikTok)."),
774
- videoMode: z.enum(["lifestyle", "cgi", "ugc", "skip"]).optional().describe("Video styling \u2014 lifestyle (default), cgi, ugc, or skip to omit video.")
775
- }).refine((v) => v.productUrl || v.product, {
776
- message: "productUrl or product is required",
777
- path: ["productUrl"]
778
- });
779
- var InputJsonSchema8 = {
780
- type: "object",
781
- properties: {
782
- productUrl: {
783
- type: "string",
784
- format: "uri",
785
- description: "Product page URL. We extract name + description + images automatically."
786
- },
787
- product: {
786
+ var InputSchema7, InputJsonSchema7, fromUrlSkill;
787
+ var init_from_url = __esm({
788
+ "src/skills/from-url.ts"() {
789
+ InputSchema7 = z.object({
790
+ url: z.string().min(1).max(2048).describe("Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata.")
791
+ });
792
+ InputJsonSchema7 = {
788
793
  type: "object",
789
794
  properties: {
790
- name: { type: "string" },
791
- description: { type: "string" },
792
- images: { type: "array", items: { type: "string", format: "uri" } }
795
+ url: {
796
+ type: "string",
797
+ description: "Product page URL. Shopify, BigCommerce, WooCommerce, or any page with JSON-LD / OpenGraph product metadata."
798
+ }
793
799
  },
794
- required: ["name"]
795
- },
796
- tone: {
797
- type: "string",
798
- description: "Creative direction threaded through every prompt."
799
- },
800
- variants: { type: "integer", minimum: 1, maximum: 8 },
801
- includeVideo: { type: "boolean" },
802
- videoAspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1"] },
803
- videoMode: { type: "string", enum: ["lifestyle", "cgi", "ugc", "skip"] }
804
- },
805
- oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
806
- };
800
+ required: ["url"]
801
+ };
802
+ fromUrlSkill = {
803
+ name: "pixeldojo:from_url",
804
+ 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.",
805
+ inputZod: InputSchema7,
806
+ inputJsonSchema: InputJsonSchema7,
807
+ // No model dispatch — this skill calls a server-side scrape, not a generation.
808
+ allowedModels: [],
809
+ defaultModel: "",
810
+ run: run7
811
+ };
812
+ }
813
+ });
807
814
  async function run8(input, ctx) {
808
815
  const client = ctx.client;
809
816
  const result = await client.runCampaign(input);
@@ -831,30 +838,66 @@ async function run8(input, ctx) {
831
838
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
832
839
  };
833
840
  }
834
- var campaignSkill = {
835
- name: "pixeldojo:campaign",
836
- description: "Generate a coherent product launch package (hero image + N lifestyle variants + optional vertical video) from one URL or product profile. Composes hero / lifestyle / video models server-side; returns assets in one call or a handoff with campaignId to poll via pixeldojo:campaign_status.",
837
- inputZod: InputSchema8,
838
- inputJsonSchema: InputJsonSchema8,
839
- // Composition skill — internal sub-jobs route to per-tool models, no
840
- // single allowed-models list applies here.
841
- allowedModels: [],
842
- defaultModel: "",
843
- run: run8
844
- };
845
- var InputSchema9 = z.object({
846
- campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
841
+ var InputSchema8, InputJsonSchema8, campaignSkill;
842
+ var init_campaign = __esm({
843
+ "src/skills/campaign.ts"() {
844
+ InputSchema8 = z.object({
845
+ productUrl: z.string().url().optional().describe("Product page URL. We extract name + description + images automatically."),
846
+ product: z.object({
847
+ name: z.string().min(1).max(200),
848
+ description: z.string().max(2e3).optional(),
849
+ images: z.array(z.string().url()).max(5).optional()
850
+ }).optional().describe("Manual product fields, used when productUrl is omitted."),
851
+ tone: z.string().max(120).optional().describe('Optional creative direction ("premium minimalist", "playful Y2K"). Threaded through every prompt.'),
852
+ variants: z.number().int().min(1).max(8).optional().describe("Lifestyle shots in addition to the hero (default 4, max 8)."),
853
+ includeVideo: z.boolean().optional().describe("Whether to also generate a vertical launch video (default true)."),
854
+ videoAspectRatio: z.enum(["9:16", "16:9", "1:1"]).optional().describe("Aspect ratio of the launch video (default 9:16 for Reels/TikTok)."),
855
+ videoMode: z.enum(["lifestyle", "cgi", "ugc", "skip"]).optional().describe("Video styling \u2014 lifestyle (default), cgi, ugc, or skip to omit video.")
856
+ }).refine((v) => v.productUrl || v.product, {
857
+ message: "productUrl or product is required",
858
+ path: ["productUrl"]
859
+ });
860
+ InputJsonSchema8 = {
861
+ type: "object",
862
+ properties: {
863
+ productUrl: {
864
+ type: "string",
865
+ format: "uri",
866
+ description: "Product page URL. We extract name + description + images automatically."
867
+ },
868
+ product: {
869
+ type: "object",
870
+ properties: {
871
+ name: { type: "string" },
872
+ description: { type: "string" },
873
+ images: { type: "array", items: { type: "string", format: "uri" } }
874
+ },
875
+ required: ["name"]
876
+ },
877
+ tone: {
878
+ type: "string",
879
+ description: "Creative direction threaded through every prompt."
880
+ },
881
+ variants: { type: "integer", minimum: 1, maximum: 8 },
882
+ includeVideo: { type: "boolean" },
883
+ videoAspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1"] },
884
+ videoMode: { type: "string", enum: ["lifestyle", "cgi", "ugc", "skip"] }
885
+ },
886
+ oneOf: [{ required: ["productUrl"] }, { required: ["product"] }]
887
+ };
888
+ campaignSkill = {
889
+ name: "pixeldojo:campaign",
890
+ 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.",
891
+ inputZod: InputSchema8,
892
+ inputJsonSchema: InputJsonSchema8,
893
+ // Composition skill — internal sub-jobs route to per-tool models, no
894
+ // single allowed-models list applies here.
895
+ allowedModels: [],
896
+ defaultModel: "",
897
+ run: run8
898
+ };
899
+ }
847
900
  });
848
- var InputJsonSchema9 = {
849
- type: "object",
850
- properties: {
851
- campaignId: {
852
- type: "string",
853
- description: "Campaign id returned from a prior pixeldojo:campaign handoff."
854
- }
855
- },
856
- required: ["campaignId"]
857
- };
858
901
  async function run9(input, ctx) {
859
902
  const client = ctx.client;
860
903
  const result = await client.getCampaign(input.campaignId);
@@ -885,30 +928,33 @@ async function run9(input, ctx) {
885
928
  statusUrl: result.statusUrl ?? ctx.buildStatusUrl(result.campaignId)
886
929
  };
887
930
  }
888
- var campaignStatusSkill = {
889
- name: "pixeldojo:campaign_status",
890
- description: "Poll a campaign by campaignId returned from pixeldojo:campaign. Returns assets when every sub-job is done, or a handoff describing what is still in flight.",
891
- inputZod: InputSchema9,
892
- inputJsonSchema: InputJsonSchema9,
893
- allowedModels: [],
894
- defaultModel: "",
895
- run: run9
896
- };
897
- var InputSchema10 = z.object({
898
- path: z.string().min(1).max(4096).describe(
899
- "Absolute local file path to upload. JPEG, PNG, WebP, GIF (\u226450MB) or MP4, WebM, MOV (\u2264200MB). Returns a public URL on the temp CDN; storage auto-expires after 24 hours."
900
- )
931
+ var InputSchema9, InputJsonSchema9, campaignStatusSkill;
932
+ var init_campaign_status = __esm({
933
+ "src/skills/campaign-status.ts"() {
934
+ InputSchema9 = z.object({
935
+ campaignId: z.string().min(1).max(200).describe("Campaign id returned from a prior pixeldojo:campaign handoff.")
936
+ });
937
+ InputJsonSchema9 = {
938
+ type: "object",
939
+ properties: {
940
+ campaignId: {
941
+ type: "string",
942
+ description: "Campaign id returned from a prior pixeldojo:campaign handoff."
943
+ }
944
+ },
945
+ required: ["campaignId"]
946
+ };
947
+ campaignStatusSkill = {
948
+ name: "pixeldojo:campaign_status",
949
+ 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.",
950
+ inputZod: InputSchema9,
951
+ inputJsonSchema: InputJsonSchema9,
952
+ allowedModels: [],
953
+ defaultModel: "",
954
+ run: run9
955
+ };
956
+ }
901
957
  });
902
- var InputJsonSchema10 = {
903
- type: "object",
904
- properties: {
905
- path: {
906
- type: "string",
907
- description: "Absolute local file path. Allowed: JPEG, PNG, WebP, GIF (\u226450MB) or MP4, WebM, MOV (\u2264200MB). Returns a public URL on the temp CDN; auto-expires after 24h."
908
- }
909
- },
910
- required: ["path"]
911
- };
912
958
  async function run10(input, ctx) {
913
959
  let url;
914
960
  try {
@@ -925,32 +971,493 @@ async function run10(input, ctx) {
925
971
  (temp storage; auto-deletes in ~24h \u2014 pass this URL as image_url to :generate or :edit)`
926
972
  };
927
973
  }
928
- var uploadSkill = {
929
- name: "pixeldojo:upload",
930
- description: "Upload a local image or video file and get back a public URL you can pass to :generate or :edit as image_url. Use when the user has a reference image on disk (drag-dropped into Claude Code, screenshot, etc.) and you need a URL. Storage auto-expires after 24 hours.",
931
- inputZod: InputSchema10,
932
- inputJsonSchema: InputJsonSchema10,
933
- allowedModels: [],
934
- defaultModel: "",
935
- run: run10
936
- };
974
+ var InputSchema10, InputJsonSchema10, uploadSkill;
975
+ var init_upload = __esm({
976
+ "src/skills/upload.ts"() {
977
+ init_upload_helpers();
978
+ InputSchema10 = z.object({
979
+ path: z.string().min(1).max(4096).describe(
980
+ "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."
981
+ )
982
+ });
983
+ InputJsonSchema10 = {
984
+ type: "object",
985
+ properties: {
986
+ path: {
987
+ type: "string",
988
+ 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."
989
+ }
990
+ },
991
+ required: ["path"]
992
+ };
993
+ uploadSkill = {
994
+ name: "pixeldojo:upload",
995
+ 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.",
996
+ inputZod: InputSchema10,
997
+ inputJsonSchema: InputJsonSchema10,
998
+ allowedModels: [],
999
+ defaultModel: "",
1000
+ run: run10
1001
+ };
1002
+ }
1003
+ });
1004
+ async function run11(input, ctx) {
1005
+ const requested = input.model ?? DEFAULT_MODEL6;
1006
+ if (!ALLOWED4.includes(requested)) {
1007
+ return {
1008
+ kind: "text",
1009
+ text: `Unknown model "${requested}" for pixeldojo:audio. Allowed: ${ALLOWED4.join(", ")}.`
1010
+ };
1011
+ }
1012
+ const apiId = requested;
1013
+ let audioUrls = input.audio_urls ?? [];
1014
+ if (input.audio_paths?.length) {
1015
+ try {
1016
+ const uploaded = await uploadLocalFiles(input.audio_paths, ctx.client);
1017
+ audioUrls = [...audioUrls, ...uploaded].slice(0, 3);
1018
+ } catch (err) {
1019
+ if (err instanceof SkillUploadError) {
1020
+ return { kind: "text", text: `pixeldojo:audio (upload): ${err.userMessage}` };
1021
+ }
1022
+ throw err;
1023
+ }
1024
+ }
1025
+ const body = { prompt: input.prompt };
1026
+ if (apiId === "seed-audio") {
1027
+ if (input.voice) body.voice = input.voice;
1028
+ if (audioUrls.length) body.audio_urls = audioUrls;
1029
+ if (input.output_format) body.output_format = input.output_format;
1030
+ if (input.speed !== void 0) body.speed = input.speed;
1031
+ } else {
1032
+ body.text = input.prompt;
1033
+ delete body.prompt;
1034
+ if (input.voice) body.voice_id = input.voice;
1035
+ if (input.output_format) body.audio_format = input.output_format;
1036
+ if (input.speed !== void 0) body.speed = input.speed;
1037
+ }
1038
+ const submitted = await ctx.client.run(apiId, body);
1039
+ const result = await pollJobUntilDoneOrHandoff(
1040
+ submitted.jobId,
1041
+ (id) => ctx.client.getJob(id),
1042
+ ctx.buildStatusUrl,
1043
+ {
1044
+ now: ctx.pollOverrides?.now,
1045
+ sleep: ctx.pollOverrides?.sleep
1046
+ }
1047
+ );
1048
+ return pollResultToSkillResult(
1049
+ result,
1050
+ (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Audio ready. Asset: ${urls[0]}`
1051
+ );
1052
+ }
1053
+ var ALLOWED4, DEFAULT_MODEL6, InputSchema11, InputJsonSchema11, audioSkill;
1054
+ var init_audio = __esm({
1055
+ "src/skills/audio.ts"() {
1056
+ init_poll();
1057
+ init_upload_helpers();
1058
+ init_types();
1059
+ ALLOWED4 = ["seed-audio", "text-to-speech"];
1060
+ DEFAULT_MODEL6 = "seed-audio";
1061
+ InputSchema11 = z.object({
1062
+ prompt: z.string().min(1).describe(
1063
+ "A script for speech, or a scene description for cinematic audio (dialogue + effects + music). Reference uploaded clips as @Audio1..@Audio3."
1064
+ ),
1065
+ model: z.string().optional().describe(`One of: ${ALLOWED4.join(", ")}. Defaults to ${DEFAULT_MODEL6}.`),
1066
+ voice: z.string().optional().describe("Optional voice preset name or cloned-voice id (seed-audio) / voice_id (text-to-speech)."),
1067
+ audio_urls: z.array(z.string().url()).max(3).optional().describe("seed-audio only: up to 3 reference audio URLs (30s / 10MB max each)."),
1068
+ audio_paths: z.array(z.string()).max(3).optional().describe("seed-audio only: absolute local paths to reference clips; uploaded automatically (24h temp storage)."),
1069
+ output_format: z.string().optional().describe("seed-audio: mp3 (default), wav, pcm, ogg_opus. text-to-speech: mp3, wav, flac, pcm."),
1070
+ speed: z.number().min(0.5).max(2).optional().describe("Speech speed multiplier (0.5\u20132).")
1071
+ });
1072
+ InputJsonSchema11 = {
1073
+ type: "object",
1074
+ properties: {
1075
+ prompt: { type: "string" },
1076
+ model: { type: "string", enum: ALLOWED4 },
1077
+ voice: { type: "string" },
1078
+ audio_urls: { type: "array", items: { type: "string", format: "uri" }, maxItems: 3 },
1079
+ audio_paths: { type: "array", items: { type: "string" }, maxItems: 3 },
1080
+ output_format: { type: "string" },
1081
+ speed: { type: "number", minimum: 0.5, maximum: 2 }
1082
+ },
1083
+ required: ["prompt"]
1084
+ };
1085
+ audioSkill = {
1086
+ name: "pixeldojo:audio",
1087
+ description: "Generate audio: speech, multi-character dialogue, sound effects, and background music from one prompt (Seed Audio 1.0 by default; plain TTS available).",
1088
+ inputZod: InputSchema11,
1089
+ inputJsonSchema: InputJsonSchema11,
1090
+ allowedModels: [...ALLOWED4],
1091
+ defaultModel: DEFAULT_MODEL6,
1092
+ run: run11
1093
+ };
1094
+ }
1095
+ });
1096
+ async function run12(input, ctx) {
1097
+ const res = await ctx.client.listLibrary({
1098
+ q: input.q,
1099
+ model: input.model,
1100
+ modality: input.modality,
1101
+ limit: input.limit ?? 10
1102
+ });
1103
+ if (res.items.length === 0) {
1104
+ return {
1105
+ kind: "text",
1106
+ 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."
1107
+ };
1108
+ }
1109
+ const lines = res.items.map((it) => {
1110
+ const parts = [
1111
+ `- ${it.url}`,
1112
+ it.modality ? `[${it.modality}]` : null,
1113
+ it.model ? `model=${it.model}` : null,
1114
+ `(${it.source}, ${it.createdAt.slice(0, 10)})`,
1115
+ it.prompt ? `"${it.prompt.slice(0, 90)}${it.prompt.length > 90 ? "\u2026" : ""}"` : null
1116
+ ].filter(Boolean);
1117
+ return parts.join(" ");
1118
+ });
1119
+ return {
1120
+ kind: "text",
1121
+ text: `${res.items.length} library item(s):
1122
+ ${lines.join("\n")}
1123
+
1124
+ Pass any URL back into :generate, :edit, :character, or :upscale as a reference input.`
1125
+ };
1126
+ }
1127
+ var InputSchema12, InputJsonSchema12, librarySkill;
1128
+ var init_library = __esm({
1129
+ "src/skills/library.ts"() {
1130
+ InputSchema12 = z.object({
1131
+ q: z.string().optional().describe("Substring match against the prompt that produced the asset."),
1132
+ model: z.string().optional().describe("Restrict to a single model (apiId)."),
1133
+ modality: z.enum(["image", "video", "audio"]).optional().describe("Restrict to one output type."),
1134
+ limit: z.number().int().min(1).max(50).optional().describe("Max results (default 10).")
1135
+ });
1136
+ InputJsonSchema12 = {
1137
+ type: "object",
1138
+ properties: {
1139
+ q: { type: "string" },
1140
+ model: { type: "string" },
1141
+ modality: { type: "string", enum: ["image", "video", "audio"] },
1142
+ limit: { type: "integer", minimum: 1, maximum: 50 }
1143
+ },
1144
+ required: []
1145
+ };
1146
+ librarySkill = {
1147
+ name: "pixeldojo:library",
1148
+ description: "Search your media library: saved My Media items (permanent) and recent API/MCP generations (expire after 24h), newest first. Returns asset URLs to reuse as reference inputs. Keep a result forever with pixeldojo:save.",
1149
+ inputZod: InputSchema12,
1150
+ inputJsonSchema: InputJsonSchema12,
1151
+ allowedModels: [],
1152
+ defaultModel: "",
1153
+ run: run12
1154
+ };
1155
+ }
1156
+ });
1157
+ async function run13(input, ctx) {
1158
+ const res = await ctx.client.saveMedia({
1159
+ url: input.url,
1160
+ prompt: input.prompt,
1161
+ tool_name: input.tool_name,
1162
+ modality: input.modality,
1163
+ job_id: input.job_id
1164
+ });
1165
+ return {
1166
+ kind: "text",
1167
+ text: `Saved to My Media (permanent): ${res.url}
1168
+ It will appear at https://pixeldojo.ai/my-images and in pixeldojo:library searches.`
1169
+ };
1170
+ }
1171
+ var InputSchema13, InputJsonSchema13, saveSkill;
1172
+ var init_save = __esm({
1173
+ "src/skills/save.ts"() {
1174
+ InputSchema13 = z.object({
1175
+ url: z.string().url().describe("The asset URL to save (image or video)."),
1176
+ prompt: z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
1177
+ tool_name: z.string().optional().describe("Tool/model slug that produced it."),
1178
+ modality: z.enum(["image", "video", "audio"]).optional(),
1179
+ job_id: z.string().optional().describe("The jobId that produced this asset, when known.")
1180
+ });
1181
+ InputJsonSchema13 = {
1182
+ type: "object",
1183
+ properties: {
1184
+ url: { type: "string", format: "uri" },
1185
+ prompt: { type: "string" },
1186
+ tool_name: { type: "string" },
1187
+ modality: { type: "string", enum: ["image", "video", "audio"] },
1188
+ job_id: { type: "string" }
1189
+ },
1190
+ required: ["url"]
1191
+ };
1192
+ saveSkill = {
1193
+ name: "pixeldojo:save",
1194
+ description: "Save a generated image or video into the user's My Media library permanently. Generation outputs otherwise expire after 24 hours \u2014 call this when the user wants to keep a result.",
1195
+ inputZod: InputSchema13,
1196
+ inputJsonSchema: InputJsonSchema13,
1197
+ allowedModels: [],
1198
+ defaultModel: "",
1199
+ run: run13
1200
+ };
1201
+ }
1202
+ });
1203
+ function substitutePlaceholders(args, inputs, prevUrl) {
1204
+ const missing = [];
1205
+ const fill = (value) => {
1206
+ if (typeof value === "string") {
1207
+ return value.replace(/\{\{(prev|input\.[a-zA-Z0-9_-]+)\}\}/g, (_, token) => {
1208
+ if (token === "prev") {
1209
+ if (!prevUrl) missing.push("{{prev}}");
1210
+ return prevUrl ?? "";
1211
+ }
1212
+ const key = token.slice("input.".length);
1213
+ if (!(key in inputs)) missing.push(`{{${token}}}`);
1214
+ return inputs[key] ?? "";
1215
+ });
1216
+ }
1217
+ if (Array.isArray(value)) return value.map(fill);
1218
+ if (value && typeof value === "object") {
1219
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, fill(v)]));
1220
+ }
1221
+ return value;
1222
+ };
1223
+ return { resolved: fill(args), missing };
1224
+ }
1225
+ function firstUrl(result) {
1226
+ if (result.kind === "assets" && result.assetUrls.length > 0) return result.assetUrls[0];
1227
+ if (result.kind === "text") {
1228
+ const m = result.text.match(/https?:\/\/\S+/);
1229
+ return m?.[0].replace(/[).,]+$/, "");
1230
+ }
1231
+ return void 0;
1232
+ }
1233
+ async function runSave(input, ctx) {
1234
+ const res = await ctx.client.saveWorkflow({
1235
+ name: input.name,
1236
+ description: input.description,
1237
+ steps: input.steps.map((s) => ({ skill: s.skill, args: s.args ?? {} }))
1238
+ });
1239
+ return {
1240
+ kind: "text",
1241
+ 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"}).`
1242
+ };
1243
+ }
1244
+ async function runList(_input, ctx) {
1245
+ const res = await ctx.client.listWorkflows();
1246
+ if (res.count === 0) {
1247
+ return {
1248
+ kind: "text",
1249
+ text: "No saved workflows yet. Save one with pixeldojo:save_workflow { name, steps: [{ skill, args }] }."
1250
+ };
1251
+ }
1252
+ const lines = res.workflows.map(
1253
+ (w) => `- ${w.name} (${w.steps.length} steps, ${w.runs} runs)${w.description ? `: ${w.description}` : ""}`
1254
+ );
1255
+ return { kind: "text", text: `${res.count} saved workflow(s):
1256
+ ${lines.join("\n")}` };
1257
+ }
1258
+ async function runWorkflow(input, ctx) {
1259
+ const workflow = await ctx.client.getWorkflow(input.name.toLowerCase(), { countRun: true });
1260
+ const { findSkill: findSkill2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
1261
+ const inputs = input.inputs ?? {};
1262
+ const outputs = [];
1263
+ let prevUrl;
1264
+ for (let i = 0; i < workflow.steps.length; i++) {
1265
+ const step = workflow.steps[i];
1266
+ const skill = findSkill2(step.skill);
1267
+ if (!skill) {
1268
+ return {
1269
+ kind: "text",
1270
+ text: `Step ${i + 1} names unknown skill "${step.skill}". Re-save the workflow.`
1271
+ };
1272
+ }
1273
+ const { resolved, missing } = substitutePlaceholders(step.args, inputs, prevUrl);
1274
+ if (missing.length > 0) {
1275
+ return {
1276
+ kind: "text",
1277
+ 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."}`
1278
+ };
1279
+ }
1280
+ const parsed = skill.inputZod.safeParse(resolved);
1281
+ if (!parsed.success) {
1282
+ const issue = parsed.error.issues[0];
1283
+ return {
1284
+ kind: "text",
1285
+ text: `Step ${i + 1} (${step.skill}) has invalid args: ${issue?.path.join(".") || "input"} ${issue?.message || "invalid"}.`
1286
+ };
1287
+ }
1288
+ const result = await skill.run(parsed.data, ctx);
1289
+ const url = firstUrl(result);
1290
+ if (url) {
1291
+ prevUrl = url;
1292
+ outputs.push(`step ${i + 1} (${step.skill}): ${url}`);
1293
+ } else if (result.kind === "text" && /fail|error|invalid|unknown/i.test(result.text)) {
1294
+ return {
1295
+ kind: "text",
1296
+ text: `Workflow "${workflow.name}" stopped at step ${i + 1} (${step.skill}): ${result.text}`
1297
+ };
1298
+ } else {
1299
+ outputs.push(`step ${i + 1} (${step.skill}): done (no asset URL)`);
1300
+ }
1301
+ }
1302
+ return {
1303
+ kind: "text",
1304
+ text: `Workflow "${workflow.name}" completed ${workflow.steps.length} step(s):
1305
+ ${outputs.join("\n")}`
1306
+ };
1307
+ }
1308
+ var StepSchema, SaveInputSchema, RunInputSchema, ListInputSchema, stepJson, saveWorkflowSkill, runWorkflowSkill, listWorkflowsSkill;
1309
+ var init_workflows = __esm({
1310
+ "src/skills/workflows.ts"() {
1311
+ StepSchema = z.object({
1312
+ skill: z.string().regex(/^pixeldojo:(generate|edit|character|storyboard|upscale|audio|upload|from_url)$/, {
1313
+ message: "Runnable skills only: generate, edit, character, storyboard, upscale, audio, upload, from_url."
1314
+ }),
1315
+ args: z.record(z.string(), z.unknown()).optional()
1316
+ });
1317
+ SaveInputSchema = z.object({
1318
+ name: z.string().regex(/^[a-z0-9][a-z0-9-]{1,40}$/, {
1319
+ message: 'Kebab-case, 2-41 chars, e.g. "product-launch-pack".'
1320
+ }).describe("Workflow handle. Saving to an existing name overwrites it."),
1321
+ description: z.string().max(500).optional(),
1322
+ steps: z.array(StepSchema).min(1).max(8)
1323
+ });
1324
+ RunInputSchema = z.object({
1325
+ name: z.string().describe("Saved workflow name."),
1326
+ inputs: z.record(z.string(), z.string()).optional().describe("Values for {{input.<key>}} placeholders in the workflow steps.")
1327
+ });
1328
+ ListInputSchema = z.object({});
1329
+ stepJson = {
1330
+ type: "object",
1331
+ properties: {
1332
+ skill: { type: "string" },
1333
+ args: { type: "object" }
1334
+ },
1335
+ required: ["skill"]
1336
+ };
1337
+ saveWorkflowSkill = {
1338
+ name: "pixeldojo:save_workflow",
1339
+ 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.",
1340
+ inputZod: SaveInputSchema,
1341
+ inputJsonSchema: {
1342
+ type: "object",
1343
+ properties: {
1344
+ name: { type: "string", pattern: "^[a-z0-9][a-z0-9-]{1,40}$" },
1345
+ description: { type: "string" },
1346
+ steps: { type: "array", items: stepJson, minItems: 1, maxItems: 8 }
1347
+ },
1348
+ required: ["name", "steps"]
1349
+ },
1350
+ allowedModels: [],
1351
+ defaultModel: "",
1352
+ run: runSave
1353
+ };
1354
+ runWorkflowSkill = {
1355
+ name: "pixeldojo:run_workflow",
1356
+ description: "Run a saved workflow by name. Pass inputs for its {{input.<key>}} placeholders; each step feeds the next via {{prev}}.",
1357
+ inputZod: RunInputSchema,
1358
+ inputJsonSchema: {
1359
+ type: "object",
1360
+ properties: {
1361
+ name: { type: "string" },
1362
+ inputs: { type: "object", additionalProperties: { type: "string" } }
1363
+ },
1364
+ required: ["name"]
1365
+ },
1366
+ allowedModels: [],
1367
+ defaultModel: "",
1368
+ run: runWorkflow
1369
+ };
1370
+ listWorkflowsSkill = {
1371
+ name: "pixeldojo:list_workflows",
1372
+ description: "List your saved workflows with step counts and run totals.",
1373
+ inputZod: ListInputSchema,
1374
+ inputJsonSchema: { type: "object", properties: {}, required: [] },
1375
+ allowedModels: [],
1376
+ defaultModel: "",
1377
+ run: runList
1378
+ };
1379
+ }
1380
+ });
937
1381
 
938
1382
  // src/skills/index.ts
939
- var ALL_SKILLS = [
940
- generateSkill,
941
- editSkill,
942
- characterSkill,
943
- storyboardSkill,
944
- upscaleSkill,
945
- statusSkill,
946
- fromUrlSkill,
947
- uploadSkill,
948
- campaignSkill,
949
- campaignStatusSkill
950
- ];
1383
+ var skills_exports = {};
1384
+ __export(skills_exports, {
1385
+ ALL_SKILLS: () => ALL_SKILLS,
1386
+ audioSkill: () => audioSkill,
1387
+ campaignSkill: () => campaignSkill,
1388
+ campaignStatusSkill: () => campaignStatusSkill,
1389
+ characterSkill: () => characterSkill,
1390
+ editSkill: () => editSkill,
1391
+ findSkill: () => findSkill,
1392
+ fromUrlSkill: () => fromUrlSkill,
1393
+ generateSkill: () => generateSkill,
1394
+ librarySkill: () => librarySkill,
1395
+ listWorkflowsSkill: () => listWorkflowsSkill,
1396
+ runWorkflowSkill: () => runWorkflowSkill,
1397
+ saveSkill: () => saveSkill,
1398
+ saveWorkflowSkill: () => saveWorkflowSkill,
1399
+ statusSkill: () => statusSkill,
1400
+ storyboardSkill: () => storyboardSkill,
1401
+ uploadSkill: () => uploadSkill,
1402
+ upscaleSkill: () => upscaleSkill
1403
+ });
951
1404
  function findSkill(name) {
952
1405
  return ALL_SKILLS.find((s) => s.name === name);
953
1406
  }
1407
+ var ALL_SKILLS;
1408
+ var init_skills = __esm({
1409
+ "src/skills/index.ts"() {
1410
+ init_generate();
1411
+ init_edit();
1412
+ init_character();
1413
+ init_storyboard();
1414
+ init_upscale();
1415
+ init_status();
1416
+ init_from_url();
1417
+ init_campaign();
1418
+ init_campaign_status();
1419
+ init_upload();
1420
+ init_audio();
1421
+ init_library();
1422
+ init_save();
1423
+ init_workflows();
1424
+ ALL_SKILLS = [
1425
+ generateSkill,
1426
+ editSkill,
1427
+ characterSkill,
1428
+ storyboardSkill,
1429
+ upscaleSkill,
1430
+ audioSkill,
1431
+ librarySkill,
1432
+ saveSkill,
1433
+ saveWorkflowSkill,
1434
+ runWorkflowSkill,
1435
+ listWorkflowsSkill,
1436
+ statusSkill,
1437
+ fromUrlSkill,
1438
+ uploadSkill,
1439
+ campaignSkill,
1440
+ campaignStatusSkill
1441
+ ];
1442
+ }
1443
+ });
1444
+ function makeAttributedClient(options) {
1445
+ const { mcpVersion, skillName, baseFetch, ...sdkOptions } = options;
1446
+ const real = baseFetch ?? globalThis.fetch.bind(globalThis);
1447
+ const wrappedFetch = (input, init) => {
1448
+ const headers = new Headers(init?.headers);
1449
+ headers.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
1450
+ if (skillName) headers.set("x-pixeldojo-skill", skillName);
1451
+ return real(input, { ...init, headers });
1452
+ };
1453
+ return new PixelDojoClient({
1454
+ ...sdkOptions,
1455
+ fetch: wrappedFetch
1456
+ });
1457
+ }
1458
+
1459
+ // src/server.ts
1460
+ init_skills();
954
1461
 
955
1462
  // src/redact.ts
956
1463
  var PD_KEY_RE = /pd_[A-Za-z0-9_-]{8,}/g;
@@ -1120,6 +1627,10 @@ async function runStdio(options) {
1120
1627
  await server.connect(transport);
1121
1628
  }
1122
1629
 
1123
- export { ALL_SKILLS, PACKAGE_VERSION, buildServer, findSkill, makeAttributedClient, makeRedactedLogger, pollJobUntilDoneOrHandoff, redactKeys, redactValue, runStdio };
1630
+ // src/index.ts
1631
+ init_skills();
1632
+ init_poll();
1633
+
1634
+ export { ALL_SKILLS, PACKAGE_VERSION, buildServer, errorToActionableMessage, findSkill, makeAttributedClient, makeRedactedLogger, pollJobUntilDoneOrHandoff, redactKeys, redactValue, runStdio, skillResultToMcpResponse };
1124
1635
  //# sourceMappingURL=index.js.map
1125
1636
  //# sourceMappingURL=index.js.map