@pixeldojo/mcp 0.10.0 → 0.12.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
@@ -350,7 +350,7 @@ var init_schema_snap = __esm({
350
350
  ];
351
351
  FALLBACK_KEYS = {
352
352
  image: REFERENCE_FIELD_PRIORITY,
353
- video: ["reference_videos"],
353
+ video: ["reference_videos", "video_url", "video"],
354
354
  audio: ["reference_audios", "audio_url"]
355
355
  };
356
356
  REF_KEYS = [
@@ -1074,7 +1074,8 @@ var init_storyboard = __esm({
1074
1074
  }
1075
1075
  });
1076
1076
  async function run5(input, ctx) {
1077
- const requested = input.model ?? DEFAULT_MODEL5;
1077
+ const clipUrl = input.video_url ?? (input.image_url && isVideoUrl(input.image_url) ? input.image_url : void 0);
1078
+ const requested = input.model ?? (clipUrl ? VIDEO_MODEL : DEFAULT_MODEL5);
1078
1079
  if (!ALLOWED3.includes(requested)) {
1079
1080
  return {
1080
1081
  kind: "text",
@@ -1082,11 +1083,33 @@ async function run5(input, ctx) {
1082
1083
  };
1083
1084
  }
1084
1085
  const apiId = requested;
1086
+ if (apiId === VIDEO_MODEL) {
1087
+ if (!clipUrl) {
1088
+ return {
1089
+ kind: "text",
1090
+ text: 'pixeldojo_upscale with model="video-upscaler" requires video_url (the source clip).'
1091
+ };
1092
+ }
1093
+ if (input.video_duration === void 0) {
1094
+ return {
1095
+ kind: "text",
1096
+ text: 'pixeldojo_upscale with model="video-upscaler" requires video_duration (source clip length in seconds \u2014 it sets the credit cost, 1 credit/second).'
1097
+ };
1098
+ }
1099
+ } else if (!input.image_url || input.image_url === clipUrl) {
1100
+ return {
1101
+ kind: "text",
1102
+ text: input.image_url ? `${apiId} upscales still images, but the source is a video clip. Use model="video-upscaler" with video_url and video_duration (seconds).` : `pixeldojo_upscale with model="${apiId}" requires image_url (the source image).`
1103
+ };
1104
+ }
1085
1105
  const body = {
1086
1106
  prompt: "",
1087
- image: input.image_url
1107
+ ...apiId === VIDEO_MODEL ? { video: clipUrl } : { image: input.image_url }
1088
1108
  };
1089
- if (apiId === "magnific-upscaler") {
1109
+ if (apiId === VIDEO_MODEL) {
1110
+ body.video_duration = input.video_duration;
1111
+ if (input.target_resolution) body.target_resolution = input.target_resolution;
1112
+ } else if (apiId === "magnific-upscaler") {
1090
1113
  const allowed = [2, 4, 8, 16];
1091
1114
  const requestedScale = input.scale_factor ?? 2;
1092
1115
  const nearest = allowed.reduce(
@@ -1114,34 +1137,49 @@ async function run5(input, ctx) {
1114
1137
  (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Upscaled. Asset: ${urls[0]}`
1115
1138
  );
1116
1139
  }
1117
- var ALLOWED3, DEFAULT_MODEL5, InputSchema5, InputJsonSchema5, upscaleSkill;
1140
+ var ALLOWED3, DEFAULT_MODEL5, VIDEO_MODEL, VIDEO_RESOLUTIONS, InputSchema5, InputJsonSchema5, upscaleSkill;
1118
1141
  var init_upscale = __esm({
1119
1142
  "src/skills/upscale.ts"() {
1120
1143
  init_poll();
1121
1144
  init_types();
1122
- ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance"];
1145
+ init_schema_snap();
1146
+ ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance", "video-upscaler"];
1123
1147
  DEFAULT_MODEL5 = "magnific-upscaler";
1148
+ VIDEO_MODEL = "video-upscaler";
1149
+ VIDEO_RESOLUTIONS = ["720p", "1080p", "2k", "4k"];
1124
1150
  InputSchema5 = zod.z.object({
1125
- image_url: zod.z.string().url().describe("URL of the source image to upscale."),
1126
- model: zod.z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL5}.`),
1151
+ image_url: zod.z.string().url().optional().describe("URL of the source image to upscale."),
1152
+ video_url: zod.z.string().url().optional().describe("URL of a source video clip \u2014 routes to video-upscaler. MP4 only, max 500 MB."),
1153
+ model: zod.z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL5} (a clip defaults to ${VIDEO_MODEL}).`),
1127
1154
  scale_factor: zod.z.number().min(1).max(16).optional().describe(
1128
- "Upscale multiplier. Magnific snaps to the nearest of 2/4/8/16 (default 2); creative-upscale accepts 1-10 (default 2); face-enhance accepts 1-8 (default 4)."
1155
+ "Image models only. Magnific snaps to the nearest of 2/4/8/16 (default 2); creative-upscale accepts 1-10 (default 2); face-enhance accepts 1-8 (default 4)."
1129
1156
  ),
1130
- creativity: zod.z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
1157
+ creativity: zod.z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0."),
1158
+ video_duration: zod.z.number().positive().optional().describe("video-upscaler only, REQUIRED there: source clip length in seconds (its credit meter)."),
1159
+ target_resolution: zod.z.enum(VIDEO_RESOLUTIONS).optional().describe("video-upscaler only: target output resolution. Defaults to 1080p.")
1131
1160
  });
1132
1161
  InputJsonSchema5 = {
1133
1162
  type: "object",
1134
1163
  properties: {
1135
1164
  image_url: { type: "string", format: "uri" },
1165
+ video_url: {
1166
+ type: "string",
1167
+ format: "uri",
1168
+ description: "Source video clip \u2014 routes to video-upscaler."
1169
+ },
1136
1170
  model: { type: "string", enum: ALLOWED3 },
1137
1171
  scale_factor: { type: "number", minimum: 1, maximum: 16 },
1138
- creativity: { type: "number", minimum: 0, maximum: 1 }
1139
- },
1140
- required: ["image_url"]
1172
+ creativity: { type: "number", minimum: 0, maximum: 1 },
1173
+ video_duration: {
1174
+ type: "number",
1175
+ description: "video-upscaler only, required there: source clip length in seconds."
1176
+ },
1177
+ target_resolution: { type: "string", enum: VIDEO_RESOLUTIONS }
1178
+ }
1141
1179
  };
1142
1180
  upscaleSkill = {
1143
1181
  name: "pixeldojo_upscale",
1144
- description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
1182
+ description: "Enhance an image or video. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits; video-upscaler (FlashVSR, up to 4K) for clips \u2014 pass video_url plus video_duration in seconds.",
1145
1183
  inputZod: InputSchema5,
1146
1184
  inputJsonSchema: InputJsonSchema5,
1147
1185
  allowedModels: [...ALLOWED3],
@@ -1289,6 +1327,9 @@ var init_from_url = __esm({
1289
1327
  };
1290
1328
  }
1291
1329
  });
1330
+ function needsSave(url) {
1331
+ return Boolean(url && url.startsWith("https://temp.pixeldojo.ai/"));
1332
+ }
1292
1333
  async function filmApi(ctx, method, path, body) {
1293
1334
  const res = await fetch(`${ctx.baseUrl}${path}`, {
1294
1335
  method,
@@ -1385,7 +1426,7 @@ function buildScorePlanBody(film) {
1385
1426
  }
1386
1427
  function buildFlatMusicBody(film, all) {
1387
1428
  const runtimeSec = all.reduce((n, sh) => n + (sh.durationSec || 0), 0);
1388
- const ms = Math.min(6e5, Math.max(5e3, Math.ceil(runtimeSec * 1e3 / 5e3) * 5e3));
1429
+ const ms = Math.min(3e5, Math.max(3e4, Math.ceil(runtimeSec * 1e3 / 5e3) * 5e3));
1389
1430
  return {
1390
1431
  prompt: `${LOOK_MUSIC[film.look] ?? LOOK_MUSIC["live-action"]}. Instrumental only, no vocals. One consistent theme, loopable as a background bed under dialogue.`,
1391
1432
  music_length_ms: ms,
@@ -1415,24 +1456,8 @@ It is saved in the Library folder "Film: ${film.title}".`
1415
1456
  await filmApi(ctx, "POST", `/api/films/${filmId}/shots/${sh.id}/generate`, { kind: "frame" });
1416
1457
  }
1417
1458
  const framesDone = all.every((sh) => ["frame-ready", "clip-generating", "clip-ready"].includes(sh.status));
1418
- if (framesDone && all.some((sh) => sh.status === "frame-ready" && !sh.approved)) {
1419
- await filmApi(ctx, "PATCH", `/api/films/${filmId}`, {
1420
- scenes: film.scenes.map((s) => ({
1421
- id: s.id,
1422
- title: s.title,
1423
- location: s.location,
1424
- shots: s.shots.map((sh) => ({
1425
- id: sh.id,
1426
- prompt: sh.prompt,
1427
- characterIds: sh.characterIds ?? [],
1428
- elementIds: sh.elementIds ?? [],
1429
- engine: sh.engine,
1430
- cameraPreset: sh.cameraPreset,
1431
- approved: true,
1432
- durationSec: sh.durationSec
1433
- }))
1434
- }))
1435
- });
1459
+ if (framesDone && all.some((sh) => sh.status === "frame-ready" && needsSave(sh.frameUrl))) {
1460
+ await filmApi(ctx, "POST", `/api/films/${filmId}/save`, { kind: "frame" });
1436
1461
  }
1437
1462
  const inFlight = count(film, (sh) => sh.status === "clip-generating");
1438
1463
  const clipQueue = all.filter((sh) => (sh.status === "frame-ready" || sh.status === "failed" && Boolean(sh.frameUrl)) && framesDone);
@@ -1441,6 +1466,9 @@ It is saved in the Library folder "Film: ${film.title}".`
1441
1466
  }
1442
1467
  const clipsDone = all.length > 0 && all.every((sh) => sh.status === "clip-ready");
1443
1468
  if (clipsDone) {
1469
+ if (all.some((sh) => needsSave(sh.videoUrl))) {
1470
+ await filmApi(ctx, "POST", `/api/films/${filmId}/save`, { kind: "clip" });
1471
+ }
1444
1472
  const hasMusic = film.audioStems.some((st) => st.kind === "music");
1445
1473
  if (soundtrack !== "none" && !hasMusic) {
1446
1474
  if (musicJobId) {
@@ -2015,7 +2043,7 @@ var init_library = __esm({
2015
2043
  };
2016
2044
  librarySkill = {
2017
2045
  name: "pixeldojo_library",
2018
- 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.",
2046
+ description: "Search your media library: saved My Media items (permanent) and recent API/MCP generations (deleted 1 hour after they finish), newest first. Returns asset URLs to reuse as reference inputs.",
2019
2047
  inputZod: InputSchema14,
2020
2048
  inputJsonSchema: InputJsonSchema14,
2021
2049
  allowedModels: [],
@@ -2024,52 +2052,6 @@ var init_library = __esm({
2024
2052
  };
2025
2053
  }
2026
2054
  });
2027
- async function run15(input, ctx) {
2028
- const res = await ctx.client.saveMedia({
2029
- url: input.url,
2030
- prompt: input.prompt,
2031
- tool_name: input.tool_name,
2032
- modality: input.modality,
2033
- job_id: input.job_id
2034
- });
2035
- return {
2036
- kind: "text",
2037
- text: `Saved to My Media (permanent): ${res.url}
2038
- It will appear at https://pixeldojo.ai/my-images and in pixeldojo_library searches.`
2039
- };
2040
- }
2041
- var InputSchema15, InputJsonSchema15, saveSkill;
2042
- var init_save = __esm({
2043
- "src/skills/save.ts"() {
2044
- InputSchema15 = zod.z.object({
2045
- url: zod.z.string().url().describe("The asset URL to save (image or video)."),
2046
- prompt: zod.z.string().optional().describe("The prompt that produced it \u2014 makes it findable in the library later."),
2047
- tool_name: zod.z.string().optional().describe("Tool/model slug that produced it."),
2048
- modality: zod.z.enum(["image", "video", "audio"]).optional(),
2049
- job_id: zod.z.string().optional().describe("The jobId that produced this asset, when known.")
2050
- });
2051
- InputJsonSchema15 = {
2052
- type: "object",
2053
- properties: {
2054
- url: { type: "string", format: "uri" },
2055
- prompt: { type: "string" },
2056
- tool_name: { type: "string" },
2057
- modality: { type: "string", enum: ["image", "video", "audio"] },
2058
- job_id: { type: "string" }
2059
- },
2060
- required: ["url"]
2061
- };
2062
- saveSkill = {
2063
- name: "pixeldojo_save",
2064
- 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.",
2065
- inputZod: InputSchema15,
2066
- inputJsonSchema: InputJsonSchema15,
2067
- allowedModels: [],
2068
- defaultModel: "",
2069
- run: run15
2070
- };
2071
- }
2072
- });
2073
2055
  function substitutePlaceholders(args, inputs, prevUrl) {
2074
2056
  const missing = [];
2075
2057
  const fill = (value) => {
@@ -2291,7 +2273,6 @@ __export(skills_exports, {
2291
2273
  librarySkill: () => librarySkill,
2292
2274
  listWorkflowsSkill: () => listWorkflowsSkill,
2293
2275
  runWorkflowSkill: () => runWorkflowSkill,
2294
- saveSkill: () => saveSkill,
2295
2276
  saveWorkflowSkill: () => saveWorkflowSkill,
2296
2277
  statusSkill: () => statusSkill,
2297
2278
  storyboardSkill: () => storyboardSkill,
@@ -2319,7 +2300,6 @@ var init_skills = __esm({
2319
2300
  init_upload();
2320
2301
  init_audio();
2321
2302
  init_library();
2322
- init_save();
2323
2303
  init_workflows();
2324
2304
  exports.ALL_SKILLS = [
2325
2305
  generateSkill,
@@ -2329,7 +2309,6 @@ var init_skills = __esm({
2329
2309
  upscaleSkill,
2330
2310
  audioSkill,
2331
2311
  librarySkill,
2332
- saveSkill,
2333
2312
  saveWorkflowSkill,
2334
2313
  runWorkflowSkill,
2335
2314
  listWorkflowsSkill,
@@ -2394,7 +2373,7 @@ function makeRedactedLogger(target = console) {
2394
2373
  }
2395
2374
 
2396
2375
  // src/server.ts
2397
- var PACKAGE_VERSION = "0.10.0" ;
2376
+ var PACKAGE_VERSION = "0.12.0" ;
2398
2377
  function buildServer(options) {
2399
2378
  if (!options.apiKey) {
2400
2379
  throw new Error(