pixelkiln 0.82.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +733 -480
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +2 -0
- package/dist/client/gallery.js +3377 -3000
- package/dist/index.cjs +446 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1283 -79
- package/dist/index.d.ts +1283 -79
- package/dist/index.js +442 -189
- package/dist/index.js.map +1 -1
- package/docs/AGENTS.md +4 -2
- package/docs/CHARACTERS.md +4 -3
- package/docs/MANIFEST.md +10 -6
- package/docs/PIXELLAB.md +9 -1
- package/docs/REVISIONS.md +63 -9
- package/package.json +1 -1
- package/schema/manifest.schema.json +22 -0
- package/skills/pixelkiln/references/pixellab-roadmap.md +10 -0
- package/skills/pixelkiln/references/pixellab.md +17 -11
- package/skills/pixelkiln/references/revisions.md +19 -10
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ var RevisionModeSchema = z.enum([
|
|
|
9
9
|
"outpaint",
|
|
10
10
|
"reduce-colors",
|
|
11
11
|
"correct-pixelart",
|
|
12
|
+
"remove-background",
|
|
12
13
|
"animate",
|
|
13
14
|
"animate-pixminimax",
|
|
14
15
|
"animate-skeleton",
|
|
@@ -340,7 +341,22 @@ var RevisionSchema = z.object({
|
|
|
340
341
|
* `interpolate`: required, the ending keyframe; the parent is the start.
|
|
341
342
|
*/
|
|
342
343
|
lastFrame: z.string().min(1).optional(),
|
|
343
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* `animate`/`animate-pixminimax` only: colour de-flicker sensitivity.
|
|
346
|
+
* Frames whose foreground drifts from the first frame beyond this are
|
|
347
|
+
* corrected toward it; 0 corrects every frame and higher values correct
|
|
348
|
+
* fewer. Omit for PixelLab's default.
|
|
349
|
+
*/
|
|
350
|
+
driftThreshold: z.number().min(0).optional(),
|
|
351
|
+
/**
|
|
352
|
+
* `animate-pixminimax` only: one noun phrase for what the parent shows.
|
|
353
|
+
* PixelLab reads it off the image when omitted, and can decline some
|
|
354
|
+
* images, failing the job until it is sent.
|
|
355
|
+
*/
|
|
356
|
+
subjectDescription: z.string().min(1).max(300).optional(),
|
|
357
|
+
/** `animate-pixminimax` only: the pose the parent is in, read off the image by PixelLab when omitted. */
|
|
358
|
+
initialPose: z.string().min(1).max(300).optional(),
|
|
359
|
+
/** `animate-pixminimax` only: facing direction, one of the model's caption fields, so it also holds the sprite's facing. */
|
|
344
360
|
direction: CharacterDirectionSchema.optional(),
|
|
345
361
|
/** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
|
|
346
362
|
enhancePrompt: z.boolean().optional(),
|
|
@@ -360,12 +376,15 @@ var RevisionSchema = z.object({
|
|
|
360
376
|
* same `mannequin`/`bear`/`cat`/`dog`/`horse`/`lion` vocabulary.
|
|
361
377
|
*/
|
|
362
378
|
skeletonTemplate: z.string().min(1).optional(),
|
|
379
|
+
/** `remove-background` only: `simple` is faster and suits flat backgrounds; `complex` handles detailed edges. PixelLab defaults to `simple`. */
|
|
380
|
+
removalTask: z.enum(["simple", "complex"]).optional(),
|
|
363
381
|
/**
|
|
364
|
-
* `animate-skeleton
|
|
382
|
+
* `animate-skeleton`: what the character *looks like*, a noun
|
|
365
383
|
* phrase (colours, clothing, held items) — distinct from the asset's
|
|
366
384
|
* own `prompt`, which this mode sends as the motion's short label
|
|
367
385
|
* (PixelLab's `action`), the same `prompt`-as-motion convention
|
|
368
|
-
* `animate`/`animate-pixminimax` already use.
|
|
386
|
+
* `animate`/`animate-pixminimax` already use. `remove-background`: a
|
|
387
|
+
* short description of the foreground, which PixelLab uses as a hint.
|
|
369
388
|
*/
|
|
370
389
|
description: z.string().min(1).optional(),
|
|
371
390
|
/**
|
|
@@ -482,7 +501,44 @@ var RevisionSchema = z.object({
|
|
|
482
501
|
path: ["keypointsFile"]
|
|
483
502
|
});
|
|
484
503
|
}
|
|
485
|
-
|
|
504
|
+
if (revision.driftThreshold !== void 0 && revision.mode !== "animate" && revision.mode !== "animate-pixminimax") {
|
|
505
|
+
context.addIssue({
|
|
506
|
+
code: z.ZodIssueCode.custom,
|
|
507
|
+
message: "driftThreshold applies to animate/animate-pixminimax revisions only",
|
|
508
|
+
path: ["driftThreshold"]
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
for (const field of ["subjectDescription", "initialPose"]) {
|
|
512
|
+
if (revision[field] !== void 0 && revision.mode !== "animate-pixminimax") {
|
|
513
|
+
context.addIssue({
|
|
514
|
+
code: z.ZodIssueCode.custom,
|
|
515
|
+
message: `${field} applies to animate-pixminimax revisions only`,
|
|
516
|
+
path: [field]
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (revision.removalTask !== void 0 && revision.mode !== "remove-background") {
|
|
521
|
+
context.addIssue({
|
|
522
|
+
code: z.ZodIssueCode.custom,
|
|
523
|
+
message: "removalTask applies to remove-background revisions only",
|
|
524
|
+
path: ["removalTask"]
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (revision.strength !== void 0 && revision.mode === "remove-background") {
|
|
528
|
+
context.addIssue({
|
|
529
|
+
code: z.ZodIssueCode.custom,
|
|
530
|
+
message: "remove-background revisions do not take a strength",
|
|
531
|
+
path: ["strength"]
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (revision.description !== void 0 && revision.mode !== "animate-skeleton" && revision.mode !== "remove-background") {
|
|
535
|
+
context.addIssue({
|
|
536
|
+
code: z.ZodIssueCode.custom,
|
|
537
|
+
message: "description applies to animate-skeleton/remove-background revisions only",
|
|
538
|
+
path: ["description"]
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
for (const field of ["keypointsFile", "skeletonTemplate"]) {
|
|
486
542
|
if (revision[field] !== void 0 && revision.mode !== "animate-skeleton") {
|
|
487
543
|
context.addIssue({
|
|
488
544
|
code: z.ZodIssueCode.custom,
|
|
@@ -1136,6 +1192,10 @@ var LockEntrySchema = z.object({
|
|
|
1136
1192
|
keypointsSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1137
1193
|
skeletonTemplate: z.string().min(1).optional(),
|
|
1138
1194
|
description: z.string().min(1).optional(),
|
|
1195
|
+
removalTask: z.enum(["simple", "complex"]).optional(),
|
|
1196
|
+
driftThreshold: z.number().min(0).optional(),
|
|
1197
|
+
subjectDescription: z.string().min(1).max(300).optional(),
|
|
1198
|
+
initialPose: z.string().min(1).max(300).optional(),
|
|
1139
1199
|
engine: z.enum(["pro-flash"]).optional()
|
|
1140
1200
|
}).strict().nullable().default(null),
|
|
1141
1201
|
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
@@ -1564,6 +1624,7 @@ var CorrectPixelartResponseSchema = z3.object({
|
|
|
1564
1624
|
images: z3.array(z3.object({ base64: z3.string().min(1) }).passthrough()).min(1),
|
|
1565
1625
|
usage: z3.unknown().optional()
|
|
1566
1626
|
}).passthrough();
|
|
1627
|
+
var RemoveBackgroundResponseSchema = z3.object({ image: z3.object({ base64: z3.string().min(1) }).passthrough(), usage: z3.unknown().optional() }).passthrough();
|
|
1567
1628
|
var ProFlashImageSubmitSchema = z3.object({
|
|
1568
1629
|
background_job_id: z3.string().min(1),
|
|
1569
1630
|
image_id: z3.string().min(1),
|
|
@@ -1755,9 +1816,9 @@ var PixelLabClient = class {
|
|
|
1755
1816
|
apiKey;
|
|
1756
1817
|
http;
|
|
1757
1818
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1758
|
-
async request(
|
|
1819
|
+
async request(path37, init) {
|
|
1759
1820
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1760
|
-
const res = await this.http(`${BASE}${
|
|
1821
|
+
const res = await this.http(`${BASE}${path37}`, {
|
|
1761
1822
|
...init,
|
|
1762
1823
|
headers: {
|
|
1763
1824
|
Authorization: auth,
|
|
@@ -1767,13 +1828,13 @@ var PixelLabClient = class {
|
|
|
1767
1828
|
});
|
|
1768
1829
|
const text = await res.text();
|
|
1769
1830
|
if (!res.ok) {
|
|
1770
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1831
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path37} \u2192 ${res.status}`, res.status, text);
|
|
1771
1832
|
}
|
|
1772
1833
|
if (!text) return {};
|
|
1773
1834
|
try {
|
|
1774
1835
|
return JSON.parse(text);
|
|
1775
1836
|
} catch {
|
|
1776
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1837
|
+
throw new Error(`${init?.method ?? "GET"} ${path37} returned invalid JSON`);
|
|
1777
1838
|
}
|
|
1778
1839
|
}
|
|
1779
1840
|
async balance() {
|
|
@@ -2178,10 +2239,10 @@ var PixelLabClient = class {
|
|
|
2178
2239
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2179
2240
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2180
2241
|
const south = args.reference?.south;
|
|
2181
|
-
let
|
|
2242
|
+
let path37;
|
|
2182
2243
|
let body;
|
|
2183
2244
|
if (args.mode === "v3") {
|
|
2184
|
-
|
|
2245
|
+
path37 = "/create-character-v3";
|
|
2185
2246
|
body = {
|
|
2186
2247
|
description: args.description,
|
|
2187
2248
|
image_size: imageSize,
|
|
@@ -2195,7 +2256,7 @@ var PixelLabClient = class {
|
|
|
2195
2256
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2196
2257
|
};
|
|
2197
2258
|
} else if (args.mode === "pro-flash") {
|
|
2198
|
-
|
|
2259
|
+
path37 = "/create-character-pro-flash";
|
|
2199
2260
|
const traits = args.styleTraits;
|
|
2200
2261
|
body = {
|
|
2201
2262
|
description: args.description,
|
|
@@ -2219,7 +2280,7 @@ var PixelLabClient = class {
|
|
|
2219
2280
|
} : {}
|
|
2220
2281
|
};
|
|
2221
2282
|
} else if (args.mode === "pro") {
|
|
2222
|
-
|
|
2283
|
+
path37 = "/create-character-pro";
|
|
2223
2284
|
body = {
|
|
2224
2285
|
description: args.description,
|
|
2225
2286
|
image_size: imageSize,
|
|
@@ -2233,7 +2294,7 @@ var PixelLabClient = class {
|
|
|
2233
2294
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2234
2295
|
};
|
|
2235
2296
|
} else {
|
|
2236
|
-
|
|
2297
|
+
path37 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2237
2298
|
body = {
|
|
2238
2299
|
description: args.description,
|
|
2239
2300
|
image_size: imageSize,
|
|
@@ -2250,8 +2311,8 @@ var PixelLabClient = class {
|
|
|
2250
2311
|
...palette
|
|
2251
2312
|
};
|
|
2252
2313
|
}
|
|
2253
|
-
const raw = await this.request(
|
|
2254
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2314
|
+
const raw = await this.request(path37, { method: "POST", body: JSON.stringify(body) });
|
|
2315
|
+
return validateResponse(CharacterSubmitSchema, raw, path37.slice(1));
|
|
2255
2316
|
}
|
|
2256
2317
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2257
2318
|
async createCharacterState(args) {
|
|
@@ -2628,6 +2689,27 @@ var PixelLabClient = class {
|
|
|
2628
2689
|
const pngs = cleanupResults(res.images, images.length, "correct-pixelart");
|
|
2629
2690
|
return { png: pngs[0], pngs, usage: res.usage };
|
|
2630
2691
|
}
|
|
2692
|
+
/**
|
|
2693
|
+
* `/remove-background`: a transparent PNG from one pixel-art image,
|
|
2694
|
+
* synchronously, like the rest of the Cleanup tier. The API takes at most
|
|
2695
|
+
* 400x400 worth of area, one image per call. `simple` is faster and suits
|
|
2696
|
+
* flat backgrounds; `complex` is slower and handles detailed edges. The
|
|
2697
|
+
* schema's response example is dollar-denominated (`usd: 0.01`); the
|
|
2698
|
+
* subscription cost measured 1 generation (docs/ENDPOINTS.md), and the
|
|
2699
|
+
* default `simple` task is the one measured.
|
|
2700
|
+
*/
|
|
2701
|
+
async removeBackground(args) {
|
|
2702
|
+
const body = { image: args.image, image_size: { width: args.width, height: args.height } };
|
|
2703
|
+
if (args.task) body.background_removal_task = args.task === "complex" ? "remove_complex_background" : "remove_simple_background";
|
|
2704
|
+
if (args.text) body.text = args.text;
|
|
2705
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2706
|
+
const res = validateResponse(
|
|
2707
|
+
RemoveBackgroundResponseSchema,
|
|
2708
|
+
await this.request("/remove-background", { method: "POST", body: JSON.stringify(body) }),
|
|
2709
|
+
"remove-background"
|
|
2710
|
+
);
|
|
2711
|
+
return { png: Buffer.from(res.image.base64, "base64"), usage: res.usage };
|
|
2712
|
+
}
|
|
2631
2713
|
/**
|
|
2632
2714
|
* `/unzoom`: recover native-resolution pixel art from an upscaled image (a
|
|
2633
2715
|
* 32x32 sprite saved at 512x512). Synchronous like the Cleanup tier.
|
|
@@ -2736,6 +2818,7 @@ var PixelLabClient = class {
|
|
|
2736
2818
|
*/
|
|
2737
2819
|
async animateWithTextV3(args) {
|
|
2738
2820
|
const body = { first_frame: args.firstFrame, action: args.action };
|
|
2821
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
2739
2822
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
2740
2823
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
2741
2824
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -2755,6 +2838,10 @@ var PixelLabClient = class {
|
|
|
2755
2838
|
*/
|
|
2756
2839
|
async animatePixminimax(args) {
|
|
2757
2840
|
const body = { first_frame: args.firstFrame, description: args.description };
|
|
2841
|
+
if (args.view) body.view = args.view;
|
|
2842
|
+
if (args.subjectDescription) body.subject_description = args.subjectDescription;
|
|
2843
|
+
if (args.initialPose) body.initial_pose = args.initialPose;
|
|
2844
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
2758
2845
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
2759
2846
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
2760
2847
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -2790,6 +2877,7 @@ var PixelLabClient = class {
|
|
|
2790
2877
|
keypoints: args.keypoints,
|
|
2791
2878
|
direction: args.direction
|
|
2792
2879
|
};
|
|
2880
|
+
if (args.view) body.view = args.view;
|
|
2793
2881
|
if (args.templateId) body.template_id = args.templateId;
|
|
2794
2882
|
if (args.action) body.action = args.action;
|
|
2795
2883
|
if (args.description) body.description = args.description;
|
|
@@ -3437,10 +3525,10 @@ import { createReadStream } from "fs";
|
|
|
3437
3525
|
function sha256(data) {
|
|
3438
3526
|
return createHash("sha256").update(data).digest("hex");
|
|
3439
3527
|
}
|
|
3440
|
-
async function sha256File(
|
|
3528
|
+
async function sha256File(path37) {
|
|
3441
3529
|
return new Promise((resolve, reject) => {
|
|
3442
3530
|
const hash = createHash("sha256");
|
|
3443
|
-
const stream = createReadStream(
|
|
3531
|
+
const stream = createReadStream(path37);
|
|
3444
3532
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3445
3533
|
stream.on("error", reject);
|
|
3446
3534
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -3564,6 +3652,10 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3564
3652
|
keypointsSha256: spec.revision.keypointsSha256,
|
|
3565
3653
|
skeletonTemplate: spec.revision.skeletonTemplate,
|
|
3566
3654
|
description: spec.revision.description,
|
|
3655
|
+
removalTask: spec.revision.removalTask,
|
|
3656
|
+
driftThreshold: spec.revision.driftThreshold,
|
|
3657
|
+
subjectDescription: spec.revision.subjectDescription,
|
|
3658
|
+
initialPose: spec.revision.initialPose,
|
|
3567
3659
|
engine: spec.revision.engine
|
|
3568
3660
|
} : void 0
|
|
3569
3661
|
})
|
|
@@ -3739,7 +3831,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3739
3831
|
* outpaint path yet.
|
|
3740
3832
|
*/
|
|
3741
3833
|
supportsRevision(mode2) {
|
|
3742
|
-
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "animate-skeleton" || mode2 === "interpolate" || mode2 === "edit-animation";
|
|
3834
|
+
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "remove-background" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "animate-skeleton" || mode2 === "interpolate" || mode2 === "edit-animation";
|
|
3743
3835
|
}
|
|
3744
3836
|
/** PixelLab's own constraints: submissions must be >2s apart, and
|
|
3745
3837
|
* background jobs in flight are capped by subscription tier (Tier 1=8,
|
|
@@ -3767,6 +3859,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3767
3859
|
if (spec.batch?.role === "member") {
|
|
3768
3860
|
return { unit: "generations", amount: 0, candidates: 1 };
|
|
3769
3861
|
}
|
|
3862
|
+
if (spec.revision?.mode === "remove-background") {
|
|
3863
|
+
return { unit: "generations", amount: 1, candidates: 1 };
|
|
3864
|
+
}
|
|
3770
3865
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
3771
3866
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
3772
3867
|
}
|
|
@@ -3855,6 +3950,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3855
3950
|
throw new Error(`PixelLab correct-pixelart source is ${width}x${height}; the API takes at most 1024 pixels per side`);
|
|
3856
3951
|
}
|
|
3857
3952
|
}
|
|
3953
|
+
if (spec.revision?.mode === "remove-background") {
|
|
3954
|
+
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
3955
|
+
if (width != null && height != null && (width > 400 || height > 400)) {
|
|
3956
|
+
throw new Error(`PixelLab remove-background source is ${width}x${height}; the API takes at most 400x400`);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3858
3959
|
if (spec.revision?.mode === "animate-skeleton") {
|
|
3859
3960
|
const { sourceWidth: width, sourceHeight: height, frames, skeletonTemplate } = spec.revision;
|
|
3860
3961
|
if (width != null && height != null && (width > 256 || height > 256)) {
|
|
@@ -3875,6 +3976,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3875
3976
|
if (mode2 === "animate" && frames != null && frames > 16) {
|
|
3876
3977
|
throw new Error(`PixelLab animate takes 4 to 16 frames; ${frames} is too many (use animate-pixminimax for up to 40)`);
|
|
3877
3978
|
}
|
|
3979
|
+
if (mode2 === "animate-pixminimax" && frames != null && frames % 4 !== 0) {
|
|
3980
|
+
throw new Error(`PixelLab animate-pixminimax takes a multiple of 4 frames (4 to 40); ${frames} is not one`);
|
|
3981
|
+
}
|
|
3878
3982
|
if (lastFrameWidth != null && lastFrameHeight != null && width != null && height != null && (lastFrameWidth !== width || lastFrameHeight !== height)) {
|
|
3879
3983
|
throw new Error(
|
|
3880
3984
|
`PixelLab ${mode2} lastFrame is ${lastFrameWidth}x${lastFrameHeight}; source is ${width}x${height} \u2014 they must match`
|
|
@@ -4966,6 +5070,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4966
5070
|
const res2 = await this.client.correctPixelart({ images, strength: revision.strength });
|
|
4967
5071
|
return this.cacheCleanupResult(res2.pngs, res2.usage, roles, revision.sourceFps);
|
|
4968
5072
|
}
|
|
5073
|
+
if (revision.mode === "remove-background") {
|
|
5074
|
+
if (width == null || height == null) throw new Error(`${spec.styleId}/${spec.assetId}: remove-background needs the source's size`);
|
|
5075
|
+
const res2 = await this.client.removeBackground({
|
|
5076
|
+
image,
|
|
5077
|
+
width,
|
|
5078
|
+
height,
|
|
5079
|
+
task: revision.removalTask,
|
|
5080
|
+
text: revision.description,
|
|
5081
|
+
seed: spec.seed
|
|
5082
|
+
});
|
|
5083
|
+
return this.cacheCleanupResult([res2.png], res2.usage, roles, revision.sourceFps);
|
|
5084
|
+
}
|
|
4969
5085
|
if (revision.mode === "interpolate") {
|
|
4970
5086
|
const lastFrame = this.readLastFrame(spec);
|
|
4971
5087
|
if (!lastFrame) throw new Error(`${spec.styleId}/${spec.assetId}: interpolate needs lastFrame, the ending keyframe`);
|
|
@@ -5001,7 +5117,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5001
5117
|
frameCount: revision.frames,
|
|
5002
5118
|
seed: spec.seed,
|
|
5003
5119
|
noBackground: spec.noBackground,
|
|
5004
|
-
enhancePrompt: revision.enhancePrompt
|
|
5120
|
+
enhancePrompt: revision.enhancePrompt,
|
|
5121
|
+
driftThreshold: revision.driftThreshold
|
|
5005
5122
|
});
|
|
5006
5123
|
return { jobId: res3.background_job_id };
|
|
5007
5124
|
}
|
|
@@ -5013,7 +5130,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5013
5130
|
seed: spec.seed,
|
|
5014
5131
|
noBackground: spec.noBackground,
|
|
5015
5132
|
enhancePrompt: revision.enhancePrompt,
|
|
5016
|
-
direction: revision.direction
|
|
5133
|
+
direction: revision.direction,
|
|
5134
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5135
|
+
subjectDescription: revision.subjectDescription,
|
|
5136
|
+
initialPose: revision.initialPose,
|
|
5137
|
+
driftThreshold: revision.driftThreshold
|
|
5017
5138
|
});
|
|
5018
5139
|
return { jobId: res2.background_job_id };
|
|
5019
5140
|
}
|
|
@@ -5030,6 +5151,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5030
5151
|
firstFrameKeypoints: revision.skeleton.firstFrameKeypoints,
|
|
5031
5152
|
keypoints: revision.skeleton.frames,
|
|
5032
5153
|
direction: revision.direction,
|
|
5154
|
+
// The style's camera, when it is one this endpoint knows; its enum is
|
|
5155
|
+
// narrower than a style's free-string `view`.
|
|
5156
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5033
5157
|
templateId: revision.skeletonTemplate,
|
|
5034
5158
|
action: spec.prompt,
|
|
5035
5159
|
description: revision.description,
|
|
@@ -5100,7 +5224,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5100
5224
|
}
|
|
5101
5225
|
async poll(jobId, generator, context) {
|
|
5102
5226
|
const revisionMode = context?.spec?.revision?.mode;
|
|
5103
|
-
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart") {
|
|
5227
|
+
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart" || revisionMode === "remove-background") {
|
|
5104
5228
|
return this.pollCachedRevision(jobId, context);
|
|
5105
5229
|
}
|
|
5106
5230
|
if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "animate-skeleton" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
|
|
@@ -5808,9 +5932,9 @@ var RetroDiffusionClient = class {
|
|
|
5808
5932
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
5809
5933
|
return Buffer.from(await response.arrayBuffer());
|
|
5810
5934
|
}
|
|
5811
|
-
async call(
|
|
5935
|
+
async call(path37, init = {}) {
|
|
5812
5936
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
5813
|
-
const response = await this.request(`${this.baseUrl}${
|
|
5937
|
+
const response = await this.request(`${this.baseUrl}${path37}`, {
|
|
5814
5938
|
...init,
|
|
5815
5939
|
headers: {
|
|
5816
5940
|
"Content-Type": "application/json",
|
|
@@ -8816,6 +8940,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8816
8940
|
} : {},
|
|
8817
8941
|
...asset.revision.skeletonTemplate ? { skeletonTemplate: asset.revision.skeletonTemplate } : {},
|
|
8818
8942
|
...asset.revision.description ? { description: asset.revision.description } : {},
|
|
8943
|
+
...asset.revision.removalTask ? { removalTask: asset.revision.removalTask } : {},
|
|
8944
|
+
...asset.revision.driftThreshold == null ? {} : { driftThreshold: asset.revision.driftThreshold },
|
|
8945
|
+
...asset.revision.subjectDescription ? { subjectDescription: asset.revision.subjectDescription } : {},
|
|
8946
|
+
...asset.revision.initialPose ? { initialPose: asset.revision.initialPose } : {},
|
|
8819
8947
|
...asset.revision.engine ? { engine: asset.revision.engine } : {}
|
|
8820
8948
|
};
|
|
8821
8949
|
}
|
|
@@ -12099,10 +12227,10 @@ function isSha256Hash(value) {
|
|
|
12099
12227
|
function parseCache(value) {
|
|
12100
12228
|
return HashCacheSchema.parse(value);
|
|
12101
12229
|
}
|
|
12102
|
-
async function loadCache(
|
|
12103
|
-
if (!existsSync13(
|
|
12230
|
+
async function loadCache(path37) {
|
|
12231
|
+
if (!existsSync13(path37)) return { version: 1, hashes: {} };
|
|
12104
12232
|
try {
|
|
12105
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(
|
|
12233
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path37, "utf8")));
|
|
12106
12234
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12107
12235
|
return {
|
|
12108
12236
|
version: 1,
|
|
@@ -12114,18 +12242,18 @@ async function loadCache(path36) {
|
|
|
12114
12242
|
return { version: 1, hashes: {} };
|
|
12115
12243
|
}
|
|
12116
12244
|
}
|
|
12117
|
-
async function saveCache(
|
|
12245
|
+
async function saveCache(path37, cache) {
|
|
12118
12246
|
const sorted = {};
|
|
12119
12247
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12120
12248
|
const hash = cache.hashes[key];
|
|
12121
12249
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12122
12250
|
sorted[key] = hash;
|
|
12123
12251
|
}
|
|
12124
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
12125
|
-
const tmp = `${
|
|
12252
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path37)), { recursive: true });
|
|
12253
|
+
const tmp = `${path37}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12126
12254
|
try {
|
|
12127
12255
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12128
|
-
await rename4(tmp,
|
|
12256
|
+
await rename4(tmp, path37);
|
|
12129
12257
|
} finally {
|
|
12130
12258
|
await rm4(tmp, { force: true });
|
|
12131
12259
|
}
|
|
@@ -13641,6 +13769,10 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13641
13769
|
...spec.revision.keypointsSha256 ? { keypointsSha256: spec.revision.keypointsSha256 } : {},
|
|
13642
13770
|
...spec.revision.skeletonTemplate ? { skeletonTemplate: spec.revision.skeletonTemplate } : {},
|
|
13643
13771
|
...spec.revision.description ? { description: spec.revision.description } : {},
|
|
13772
|
+
...spec.revision.removalTask ? { removalTask: spec.revision.removalTask } : {},
|
|
13773
|
+
...spec.revision.driftThreshold == null ? {} : { driftThreshold: spec.revision.driftThreshold },
|
|
13774
|
+
...spec.revision.subjectDescription ? { subjectDescription: spec.revision.subjectDescription } : {},
|
|
13775
|
+
...spec.revision.initialPose ? { initialPose: spec.revision.initialPose } : {},
|
|
13644
13776
|
...spec.revision.engine ? { engine: spec.revision.engine } : {}
|
|
13645
13777
|
} : null,
|
|
13646
13778
|
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
@@ -14930,9 +15062,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
14930
15062
|
|
|
14931
15063
|
// src/gallery/snapshot.ts
|
|
14932
15064
|
import { createHash as createHash4 } from "crypto";
|
|
14933
|
-
import { existsSync as
|
|
14934
|
-
import { readFile as
|
|
14935
|
-
import
|
|
15065
|
+
import { existsSync as existsSync26 } from "fs";
|
|
15066
|
+
import { readFile as readFile24, stat as stat3 } from "fs/promises";
|
|
15067
|
+
import path32 from "path";
|
|
14936
15068
|
|
|
14937
15069
|
// src/workspace.ts
|
|
14938
15070
|
import { mkdir as mkdir7, readFile as readFile18, rename as rename6, rm as rm7, writeFile as writeFile9 } from "fs/promises";
|
|
@@ -15068,15 +15200,16 @@ function validateWorkspace(ws, dir) {
|
|
|
15068
15200
|
}
|
|
15069
15201
|
|
|
15070
15202
|
// src/gallery/edit.ts
|
|
15071
|
-
import { readFile as
|
|
15072
|
-
import
|
|
15073
|
-
import { z as
|
|
15203
|
+
import { readFile as readFile23, rename as rename9, writeFile as writeFile15 } from "fs/promises";
|
|
15204
|
+
import path31 from "path";
|
|
15205
|
+
import { z as z13 } from "zod";
|
|
15074
15206
|
|
|
15075
15207
|
// src/manifest-edit.ts
|
|
15076
15208
|
import { readFile as readFile19, rename as rename7, rm as rm8, writeFile as writeFile10 } from "fs/promises";
|
|
15077
15209
|
import path26 from "path";
|
|
15078
15210
|
import { z as z8 } from "zod";
|
|
15079
15211
|
var HexSha = z8.string().regex(/^[0-9a-f]{64}$/);
|
|
15212
|
+
var ProjectFileSchema = z8.string().min(1).refine((file) => !path26.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
15080
15213
|
var AssetPatchSchema = z8.object({
|
|
15081
15214
|
prompt: z8.string().optional(),
|
|
15082
15215
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -15125,10 +15258,11 @@ var NewAssetSchema = z8.object({
|
|
|
15125
15258
|
styles: z8.array(z8.string().min(1)).optional(),
|
|
15126
15259
|
/**
|
|
15127
15260
|
* A controlled regeneration of another asset in the same manifest,
|
|
15128
|
-
* instead of a fresh generation.
|
|
15129
|
-
*
|
|
15130
|
-
* `
|
|
15131
|
-
*
|
|
15261
|
+
* instead of a fresh generation. Every mode but `outpaint` can be
|
|
15262
|
+
* created here: `inpaint` names a mask file the gallery's mask editor
|
|
15263
|
+
* wrote first, `interpolate` names an existing image as its ending
|
|
15264
|
+
* keyframe, and `animate-skeleton` names the keypoints file the gallery
|
|
15265
|
+
* wrote first.
|
|
15132
15266
|
*/
|
|
15133
15267
|
revision: z8.discriminatedUnion("mode", [
|
|
15134
15268
|
z8.object({
|
|
@@ -15137,6 +15271,52 @@ var NewAssetSchema = z8.object({
|
|
|
15137
15271
|
from: z8.string().min(1),
|
|
15138
15272
|
strength: z8.number().min(0).max(1).optional()
|
|
15139
15273
|
}).strict(),
|
|
15274
|
+
z8.object({
|
|
15275
|
+
mode: z8.literal("reduce-colors"),
|
|
15276
|
+
from: z8.string().min(1),
|
|
15277
|
+
/** A target colour count; the gallery leaves PixelLab's default when this is omitted. */
|
|
15278
|
+
numColors: z8.number().int().min(2).max(256).optional(),
|
|
15279
|
+
dithering: RevisionDitheringSchema.optional(),
|
|
15280
|
+
ditheringStrength: z8.number().min(0).max(10).optional()
|
|
15281
|
+
}).strict(),
|
|
15282
|
+
z8.object({
|
|
15283
|
+
mode: z8.literal("inpaint"),
|
|
15284
|
+
from: z8.string().min(1),
|
|
15285
|
+
/** Black-and-white PNG the same size as the parent; white is repainted. */
|
|
15286
|
+
mask: ProjectFileSchema
|
|
15287
|
+
}).strict(),
|
|
15288
|
+
z8.object({
|
|
15289
|
+
mode: z8.literal("interpolate"),
|
|
15290
|
+
from: z8.string().min(1),
|
|
15291
|
+
/** The ending keyframe: an image already in the project, the parent's size. */
|
|
15292
|
+
lastFrame: ProjectFileSchema,
|
|
15293
|
+
fps: z8.number().int().min(1).max(60).optional()
|
|
15294
|
+
}).strict(),
|
|
15295
|
+
z8.object({ mode: z8.literal("correct-pixelart"), from: z8.string().min(1), strength: z8.number().min(0).max(1).optional() }).strict(),
|
|
15296
|
+
z8.object({
|
|
15297
|
+
mode: z8.literal("remove-background"),
|
|
15298
|
+
from: z8.string().min(1),
|
|
15299
|
+
removalTask: z8.enum(["simple", "complex"]).optional(),
|
|
15300
|
+
/** A short description of the foreground, sent as PixelLab's text hint. */
|
|
15301
|
+
description: z8.string().min(1).optional()
|
|
15302
|
+
}).strict(),
|
|
15303
|
+
z8.object({
|
|
15304
|
+
mode: z8.enum(["animate", "animate-pixminimax"]),
|
|
15305
|
+
from: z8.string().min(1),
|
|
15306
|
+
frames: z8.number().int().min(4).max(40).refine((n) => n % 2 === 0, "frames must be even").optional(),
|
|
15307
|
+
fps: z8.number().int().min(1).max(60).optional(),
|
|
15308
|
+
enhancePrompt: z8.boolean().optional(),
|
|
15309
|
+
driftThreshold: z8.number().min(0).optional(),
|
|
15310
|
+
/** `animate-pixminimax` only; the loader refuses these on `animate`. */
|
|
15311
|
+
direction: CharacterDirectionSchema.optional(),
|
|
15312
|
+
subjectDescription: z8.string().min(1).max(300).optional(),
|
|
15313
|
+
initialPose: z8.string().min(1).max(300).optional()
|
|
15314
|
+
}).strict(),
|
|
15315
|
+
z8.object({
|
|
15316
|
+
mode: z8.literal("edit-animation"),
|
|
15317
|
+
from: z8.string().min(1),
|
|
15318
|
+
fps: z8.number().int().min(1).max(60).optional()
|
|
15319
|
+
}).strict(),
|
|
15140
15320
|
z8.object({
|
|
15141
15321
|
mode: z8.literal("animate-skeleton"),
|
|
15142
15322
|
from: z8.string().min(1),
|
|
@@ -15781,62 +15961,118 @@ async function saveProjectImage(root, request) {
|
|
|
15781
15961
|
return relative.split(path28.sep).join("/");
|
|
15782
15962
|
}
|
|
15783
15963
|
|
|
15784
|
-
// src/gallery/
|
|
15964
|
+
// src/gallery/image-revision.ts
|
|
15785
15965
|
import { existsSync as existsSync24 } from "fs";
|
|
15786
|
-
import {
|
|
15966
|
+
import { readFile as readFile21, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
15787
15967
|
import path29 from "path";
|
|
15788
15968
|
import { z as z11 } from "zod";
|
|
15789
|
-
var
|
|
15790
|
-
|
|
15791
|
-
styleId: z11.string().min(1),
|
|
15792
|
-
assetId: z11.string().min(1),
|
|
15793
|
-
frames: z11.number().int().min(3).max(15).optional()
|
|
15794
|
-
}).strict();
|
|
15795
|
-
var SkeletonAnimationRequestSchema = z11.object({
|
|
15796
|
-
action: z11.literal("create-skeleton-animation"),
|
|
15969
|
+
var InpaintRevisionRequestSchema = z11.object({
|
|
15970
|
+
action: z11.literal("create-inpaint-revision"),
|
|
15797
15971
|
project: z11.string().min(1).optional(),
|
|
15798
15972
|
expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/),
|
|
15799
15973
|
styleId: z11.string().min(1),
|
|
15800
|
-
/** The sprite
|
|
15974
|
+
/** The sprite being repainted. */
|
|
15801
15975
|
from: z11.string().min(1),
|
|
15802
15976
|
assetId: z11.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
15803
|
-
/**
|
|
15977
|
+
/** What to paint into the masked area. */
|
|
15804
15978
|
prompt: z11.string().min(1),
|
|
15979
|
+
/** Manifest-relative `.png` destination for the mask. */
|
|
15980
|
+
maskPath: z11.string().min(1),
|
|
15981
|
+
maskBase64: z11.string().regex(/^[A-Za-z0-9+/]*={0,2}$/),
|
|
15982
|
+
/** Replace a file already at `maskPath`. */
|
|
15983
|
+
overwrite: z11.boolean().optional()
|
|
15984
|
+
}).strict();
|
|
15985
|
+
async function createInpaintRevision(manifestPath, request) {
|
|
15986
|
+
const root = path29.dirname(path29.resolve(manifestPath));
|
|
15987
|
+
if (path29.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
15988
|
+
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
15989
|
+
}
|
|
15990
|
+
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
15991
|
+
const metadata = imageMetadata(bytes);
|
|
15992
|
+
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
15993
|
+
const file = path29.resolve(root, request.maskPath);
|
|
15994
|
+
const previous = existsSync24(file) ? await readFile21(file) : null;
|
|
15995
|
+
const mask = await saveProjectImage(root, {
|
|
15996
|
+
action: "upload-image",
|
|
15997
|
+
path: request.maskPath,
|
|
15998
|
+
base64: request.maskBase64,
|
|
15999
|
+
...request.overwrite ? { overwrite: true } : {}
|
|
16000
|
+
});
|
|
16001
|
+
try {
|
|
16002
|
+
await applyManifestEdit(manifestPath, {
|
|
16003
|
+
action: "add-asset",
|
|
16004
|
+
assetId: request.assetId,
|
|
16005
|
+
expectedSha256: request.expectedSha256,
|
|
16006
|
+
asset: {
|
|
16007
|
+
prompt: request.prompt,
|
|
16008
|
+
styles: [request.styleId],
|
|
16009
|
+
revision: { mode: "inpaint", from: request.from, mask }
|
|
16010
|
+
}
|
|
16011
|
+
});
|
|
16012
|
+
} catch (error) {
|
|
16013
|
+
if (previous) await writeFile13(file, previous);
|
|
16014
|
+
else await rm10(file, { force: true });
|
|
16015
|
+
throw error;
|
|
16016
|
+
}
|
|
16017
|
+
return mask;
|
|
16018
|
+
}
|
|
16019
|
+
|
|
16020
|
+
// src/gallery/skeleton.ts
|
|
16021
|
+
import { existsSync as existsSync25 } from "fs";
|
|
16022
|
+
import { mkdir as mkdir10, readFile as readFile22, rm as rm11, writeFile as writeFile14 } from "fs/promises";
|
|
16023
|
+
import path30 from "path";
|
|
16024
|
+
import { z as z12 } from "zod";
|
|
16025
|
+
var SkeletonEstimateRequestSchema = z12.object({
|
|
16026
|
+
project: z12.string().min(1).optional(),
|
|
16027
|
+
styleId: z12.string().min(1),
|
|
16028
|
+
assetId: z12.string().min(1),
|
|
16029
|
+
frames: z12.number().int().min(3).max(15).optional()
|
|
16030
|
+
}).strict();
|
|
16031
|
+
var SkeletonAnimationRequestSchema = z12.object({
|
|
16032
|
+
action: z12.literal("create-skeleton-animation"),
|
|
16033
|
+
project: z12.string().min(1).optional(),
|
|
16034
|
+
expectedSha256: z12.string().regex(/^[0-9a-f]{64}$/),
|
|
16035
|
+
styleId: z12.string().min(1),
|
|
16036
|
+
/** The sprite the animation poses. */
|
|
16037
|
+
from: z12.string().min(1),
|
|
16038
|
+
assetId: z12.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
16039
|
+
/** The motion, sent as PixelLab's `action`. */
|
|
16040
|
+
prompt: z12.string().min(1),
|
|
15805
16041
|
direction: CharacterDirectionSchema,
|
|
15806
|
-
description:
|
|
15807
|
-
skeletonTemplate:
|
|
16042
|
+
description: z12.string().min(1).optional(),
|
|
16043
|
+
skeletonTemplate: z12.string().min(1).optional(),
|
|
15808
16044
|
/** Manifest-relative JSON path inside the project. */
|
|
15809
|
-
keypointsFile:
|
|
16045
|
+
keypointsFile: z12.string().min(1),
|
|
15810
16046
|
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
15811
16047
|
set: SkeletonSetSchema.optional(),
|
|
15812
16048
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
15813
|
-
overwrite:
|
|
16049
|
+
overwrite: z12.boolean().optional()
|
|
15814
16050
|
}).strict();
|
|
15815
|
-
var SkeletonKeypointsUpdateSchema =
|
|
15816
|
-
action:
|
|
15817
|
-
project:
|
|
15818
|
-
styleId:
|
|
15819
|
-
assetId:
|
|
16051
|
+
var SkeletonKeypointsUpdateSchema = z12.object({
|
|
16052
|
+
action: z12.literal("update-skeleton-keypoints"),
|
|
16053
|
+
project: z12.string().min(1).optional(),
|
|
16054
|
+
styleId: z12.string().min(1),
|
|
16055
|
+
assetId: z12.string().min(1),
|
|
15820
16056
|
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
15821
|
-
expectedSha256:
|
|
16057
|
+
expectedSha256: z12.string().regex(/^[0-9a-f]{64}$/),
|
|
15822
16058
|
set: SkeletonSetSchema
|
|
15823
16059
|
}).strict();
|
|
15824
16060
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
15825
|
-
const root =
|
|
15826
|
-
const file =
|
|
15827
|
-
const relative =
|
|
15828
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
16061
|
+
const root = path30.dirname(path30.resolve(manifestPath));
|
|
16062
|
+
const file = path30.resolve(root, request.keypointsFile);
|
|
16063
|
+
const relative = path30.relative(root, file);
|
|
16064
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path30.isAbsolute(relative)) {
|
|
15829
16065
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
15830
16066
|
}
|
|
15831
|
-
const existed =
|
|
16067
|
+
const existed = existsSync25(file);
|
|
15832
16068
|
let previous = null;
|
|
15833
16069
|
if (request.set) {
|
|
15834
16070
|
if (existed && !request.overwrite) {
|
|
15835
16071
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
15836
16072
|
}
|
|
15837
|
-
if (existed) previous = await
|
|
15838
|
-
await mkdir10(
|
|
15839
|
-
await
|
|
16073
|
+
if (existed) previous = await readFile22(file);
|
|
16074
|
+
await mkdir10(path30.dirname(file), { recursive: true });
|
|
16075
|
+
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15840
16076
|
} else if (!existed) {
|
|
15841
16077
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
15842
16078
|
}
|
|
@@ -15851,7 +16087,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15851
16087
|
revision: {
|
|
15852
16088
|
mode: "animate-skeleton",
|
|
15853
16089
|
from: request.from,
|
|
15854
|
-
keypointsFile: relative.split(
|
|
16090
|
+
keypointsFile: relative.split(path30.sep).join("/"),
|
|
15855
16091
|
direction: request.direction,
|
|
15856
16092
|
...request.description ? { description: request.description } : {},
|
|
15857
16093
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -15860,8 +16096,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15860
16096
|
});
|
|
15861
16097
|
} catch (error) {
|
|
15862
16098
|
if (request.set) {
|
|
15863
|
-
if (previous) await
|
|
15864
|
-
else await
|
|
16099
|
+
if (previous) await writeFile14(file, previous);
|
|
16100
|
+
else await rm11(file, { force: true });
|
|
15865
16101
|
}
|
|
15866
16102
|
throw error;
|
|
15867
16103
|
}
|
|
@@ -15872,64 +16108,64 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
15872
16108
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
15873
16109
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
15874
16110
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
15875
|
-
const relative =
|
|
15876
|
-
if (relative.startsWith("..") ||
|
|
16111
|
+
const relative = path30.relative(ctx.loaded.root, file);
|
|
16112
|
+
if (relative.startsWith("..") || path30.isAbsolute(relative)) {
|
|
15877
16113
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
15878
16114
|
}
|
|
15879
|
-
const current =
|
|
16115
|
+
const current = existsSync25(file) ? sha256(await readFile22(file)) : null;
|
|
15880
16116
|
if (current !== request.expectedSha256) {
|
|
15881
16117
|
throw Object.assign(
|
|
15882
|
-
new Error(`${relative.split(
|
|
16118
|
+
new Error(`${relative.split(path30.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
15883
16119
|
{ status: 409 }
|
|
15884
16120
|
);
|
|
15885
16121
|
}
|
|
15886
|
-
await
|
|
15887
|
-
return relative.split(
|
|
16122
|
+
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16123
|
+
return relative.split(path30.sep).join("/");
|
|
15888
16124
|
}
|
|
15889
16125
|
|
|
15890
16126
|
// src/gallery/edit.ts
|
|
15891
|
-
var Base64 =
|
|
15892
|
-
var HandEditRequestSchema =
|
|
15893
|
-
|
|
15894
|
-
action:
|
|
15895
|
-
project:
|
|
15896
|
-
assetId:
|
|
15897
|
-
styleId:
|
|
15898
|
-
expectedSha256:
|
|
16127
|
+
var Base64 = z13.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
16128
|
+
var HandEditRequestSchema = z13.discriminatedUnion("action", [
|
|
16129
|
+
z13.object({
|
|
16130
|
+
action: z13.literal("start-edit"),
|
|
16131
|
+
project: z13.string().min(1).optional(),
|
|
16132
|
+
assetId: z13.string().min(1),
|
|
16133
|
+
styleId: z13.string().min(1),
|
|
16134
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/),
|
|
15899
16135
|
/** Also launch the author's editor on the file. */
|
|
15900
|
-
open:
|
|
16136
|
+
open: z13.boolean().optional()
|
|
15901
16137
|
}).strict(),
|
|
15902
|
-
|
|
15903
|
-
action:
|
|
15904
|
-
project:
|
|
15905
|
-
assetId:
|
|
15906
|
-
styleId:
|
|
15907
|
-
expectedSha256:
|
|
16138
|
+
z13.object({
|
|
16139
|
+
action: z13.literal("detach-edit"),
|
|
16140
|
+
project: z13.string().min(1).optional(),
|
|
16141
|
+
assetId: z13.string().min(1),
|
|
16142
|
+
styleId: z13.string().min(1),
|
|
16143
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/)
|
|
15908
16144
|
}).strict(),
|
|
15909
16145
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
15910
|
-
|
|
15911
|
-
action:
|
|
15912
|
-
project:
|
|
15913
|
-
assetId:
|
|
15914
|
-
styleId:
|
|
15915
|
-
expectedSha256:
|
|
16146
|
+
z13.object({
|
|
16147
|
+
action: z13.literal("save-edit"),
|
|
16148
|
+
project: z13.string().min(1).optional(),
|
|
16149
|
+
assetId: z13.string().min(1),
|
|
16150
|
+
styleId: z13.string().min(1),
|
|
16151
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/),
|
|
15916
16152
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
15917
16153
|
png: Base64.min(1).optional(),
|
|
15918
16154
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
15919
|
-
frames:
|
|
16155
|
+
frames: z13.array(z13.object({ role: z13.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
15920
16156
|
/** The editor's layered project file, kept beside the edit. */
|
|
15921
16157
|
pxo: Base64.optional(),
|
|
15922
16158
|
/** Editor identity from the bridge's ready message. */
|
|
15923
|
-
editor:
|
|
15924
|
-
protocol:
|
|
16159
|
+
editor: z13.string().min(1).max(200),
|
|
16160
|
+
protocol: z13.number().int().positive()
|
|
15925
16161
|
}).strict()
|
|
15926
16162
|
]);
|
|
15927
16163
|
var UNDO_DEPTH = 20;
|
|
15928
|
-
var UndoRequestSchema =
|
|
15929
|
-
action:
|
|
15930
|
-
project:
|
|
16164
|
+
var UndoRequestSchema = z13.object({
|
|
16165
|
+
action: z13.literal("undo-edit"),
|
|
16166
|
+
project: z13.string().min(1).optional(),
|
|
15931
16167
|
/** The manifest as the page last saw it; undo refuses a manifest someone else has changed since. */
|
|
15932
|
-
expectedSha256:
|
|
16168
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/)
|
|
15933
16169
|
}).strict();
|
|
15934
16170
|
function createGalleryEditHandler(opts) {
|
|
15935
16171
|
const log = opts.onProgress ?? (() => {
|
|
@@ -15938,7 +16174,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15938
16174
|
const history = /* @__PURE__ */ new Map();
|
|
15939
16175
|
const manifestOf = async (project) => {
|
|
15940
16176
|
try {
|
|
15941
|
-
return
|
|
16177
|
+
return path31.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
15942
16178
|
} catch {
|
|
15943
16179
|
return null;
|
|
15944
16180
|
}
|
|
@@ -15948,7 +16184,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15948
16184
|
if (undo.success) {
|
|
15949
16185
|
const manifestPath2 = await manifestOf(undo.data.project);
|
|
15950
16186
|
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
15951
|
-
const current = await
|
|
16187
|
+
const current = await readFile23(manifestPath2, "utf8");
|
|
15952
16188
|
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
15953
16189
|
const stack = history.get(manifestPath2) ?? [];
|
|
15954
16190
|
const last = stack[stack.length - 1];
|
|
@@ -15958,17 +16194,17 @@ function createGalleryEditHandler(opts) {
|
|
|
15958
16194
|
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
15959
16195
|
}
|
|
15960
16196
|
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
15961
|
-
await
|
|
16197
|
+
await writeFile15(tmp, last.before);
|
|
15962
16198
|
await rename9(tmp, manifestPath2);
|
|
15963
16199
|
stack.pop();
|
|
15964
|
-
log(` manifest edit undone in ${
|
|
16200
|
+
log(` manifest edit undone in ${path31.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
15965
16201
|
return opts.reload();
|
|
15966
16202
|
}
|
|
15967
16203
|
const manifestPath = await manifestOf(body?.project);
|
|
15968
|
-
const before = manifestPath ? await
|
|
16204
|
+
const before = manifestPath ? await readFile23(manifestPath, "utf8").catch(() => null) : null;
|
|
15969
16205
|
const build = await apply(body);
|
|
15970
16206
|
if (manifestPath && before !== null) {
|
|
15971
|
-
const after = await
|
|
16207
|
+
const after = await readFile23(manifestPath, "utf8").catch(() => null);
|
|
15972
16208
|
if (after !== null && after !== before) {
|
|
15973
16209
|
const stack = history.get(manifestPath) ?? [];
|
|
15974
16210
|
stack.push({ before, afterSha: sha256(after) });
|
|
@@ -16023,7 +16259,7 @@ function createEditApplier(opts, log) {
|
|
|
16023
16259
|
} catch (error) {
|
|
16024
16260
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16025
16261
|
}
|
|
16026
|
-
const written = await saveProjectImage(
|
|
16262
|
+
const written = await saveProjectImage(path31.dirname(path31.resolve(manifestPath2)), upload.data);
|
|
16027
16263
|
log(` image added: ${written}`);
|
|
16028
16264
|
return opts.reload();
|
|
16029
16265
|
}
|
|
@@ -16059,6 +16295,23 @@ function createEditApplier(opts, log) {
|
|
|
16059
16295
|
"invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16060
16296
|
);
|
|
16061
16297
|
}
|
|
16298
|
+
const inpaint = InpaintRevisionRequestSchema.safeParse(body);
|
|
16299
|
+
if (inpaint.success) {
|
|
16300
|
+
let manifestPath2;
|
|
16301
|
+
try {
|
|
16302
|
+
manifestPath2 = await opts.manifestFor(inpaint.data.project);
|
|
16303
|
+
} catch (error) {
|
|
16304
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16305
|
+
}
|
|
16306
|
+
const mask = await createInpaintRevision(manifestPath2, inpaint.data);
|
|
16307
|
+
log(` manifest edited: added inpaint revision ${inpaint.data.assetId} (mask ${mask})`);
|
|
16308
|
+
return opts.reload();
|
|
16309
|
+
}
|
|
16310
|
+
if (body?.action === "create-inpaint-revision") {
|
|
16311
|
+
throw new ManifestEditError(
|
|
16312
|
+
"invalid inpaint revision: " + inpaint.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16313
|
+
);
|
|
16314
|
+
}
|
|
16062
16315
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
16063
16316
|
if (!parsed.success) {
|
|
16064
16317
|
throw new ManifestEditError(
|
|
@@ -16073,7 +16326,7 @@ function createEditApplier(opts, log) {
|
|
|
16073
16326
|
}
|
|
16074
16327
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16075
16328
|
if (result.changed) {
|
|
16076
|
-
const relative =
|
|
16329
|
+
const relative = path31.relative(process.cwd(), result.manifestPath);
|
|
16077
16330
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16078
16331
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16079
16332
|
}
|
|
@@ -16101,7 +16354,7 @@ function describeEdit(edit) {
|
|
|
16101
16354
|
// src/gallery/snapshot.ts
|
|
16102
16355
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16103
16356
|
function galleryMediaId(absolutePath) {
|
|
16104
|
-
return createHash4("sha256").update(
|
|
16357
|
+
return createHash4("sha256").update(path32.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16105
16358
|
}
|
|
16106
16359
|
function galleryMediaRoute(id) {
|
|
16107
16360
|
return `/media/${id}`;
|
|
@@ -16120,9 +16373,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16120
16373
|
return (entry.history ?? []).map((generation, i) => {
|
|
16121
16374
|
const outputs = generation.outputs.map((output) => {
|
|
16122
16375
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16123
|
-
const file =
|
|
16376
|
+
const file = path32.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16124
16377
|
let url = null;
|
|
16125
|
-
if (
|
|
16378
|
+
if (existsSync26(file)) {
|
|
16126
16379
|
const id = galleryMediaId(file);
|
|
16127
16380
|
media.set(id, { path: file, contentType: mediaType });
|
|
16128
16381
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16154,7 +16407,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16154
16407
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16155
16408
|
async function samePixels(a, b) {
|
|
16156
16409
|
try {
|
|
16157
|
-
const [left, right] = await Promise.all([
|
|
16410
|
+
const [left, right] = await Promise.all([readFile24(a), readFile24(b)]);
|
|
16158
16411
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16159
16412
|
const x = decodePng(left);
|
|
16160
16413
|
const y = decodePng(right);
|
|
@@ -16252,10 +16505,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16252
16505
|
async function describeQuality(media, root, inspection) {
|
|
16253
16506
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16254
16507
|
const outputs = await Promise.all(
|
|
16255
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16508
|
+
outputPaths.map((file) => describeOutput(media, root, path32.resolve(root, file)))
|
|
16256
16509
|
);
|
|
16257
|
-
const recordPath =
|
|
16258
|
-
const recordExists =
|
|
16510
|
+
const recordPath = path32.resolve(root, inspection.record);
|
|
16511
|
+
const recordExists = existsSync26(recordPath);
|
|
16259
16512
|
let options = null;
|
|
16260
16513
|
let check = null;
|
|
16261
16514
|
if (recordExists) {
|
|
@@ -16274,7 +16527,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16274
16527
|
return {
|
|
16275
16528
|
state: inspection.state,
|
|
16276
16529
|
reason: inspection.reason,
|
|
16277
|
-
output: portableOutputPath(
|
|
16530
|
+
output: portableOutputPath(path32.resolve(root, inspection.output), root),
|
|
16278
16531
|
record: portableOutputPath(recordPath, root),
|
|
16279
16532
|
recordExists,
|
|
16280
16533
|
outputs,
|
|
@@ -16312,9 +16565,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
16312
16565
|
const media = /* @__PURE__ */ new Map();
|
|
16313
16566
|
const plan = await buildPlan(specs, lock);
|
|
16314
16567
|
const items = [];
|
|
16315
|
-
const manifestText = await
|
|
16568
|
+
const manifestText = await readFile24(loaded.path, "utf8");
|
|
16316
16569
|
const manifestSha256 = sha256(manifestText);
|
|
16317
|
-
const cacheDir =
|
|
16570
|
+
const cacheDir = path32.resolve(path32.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16318
16571
|
let rawStyles = {};
|
|
16319
16572
|
try {
|
|
16320
16573
|
const raw = JSON.parse(manifestText);
|
|
@@ -16338,12 +16591,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
16338
16591
|
)
|
|
16339
16592
|
);
|
|
16340
16593
|
if (spec.source) {
|
|
16341
|
-
const editPath =
|
|
16594
|
+
const editPath = path32.resolve(root, spec.source);
|
|
16342
16595
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16343
16596
|
const editShas = await Promise.all(
|
|
16344
16597
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
16345
16598
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
16346
|
-
return { file, sha256:
|
|
16599
|
+
return { file, sha256: existsSync26(file) ? await sha256File(file) : null };
|
|
16347
16600
|
})
|
|
16348
16601
|
);
|
|
16349
16602
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -16351,7 +16604,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16351
16604
|
const companion = await readHandEditCompanion(editPath);
|
|
16352
16605
|
if (companion) {
|
|
16353
16606
|
const projectPath = handEditProjectPath(editPath);
|
|
16354
|
-
const project = companion.project &&
|
|
16607
|
+
const project = companion.project && existsSync26(projectPath) ? await fileInfo(projectPath) : null;
|
|
16355
16608
|
let projectUrl = null;
|
|
16356
16609
|
if (project) {
|
|
16357
16610
|
const id = galleryMediaId(projectPath);
|
|
@@ -16387,8 +16640,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16387
16640
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16388
16641
|
}
|
|
16389
16642
|
} else if (spec.source) {
|
|
16390
|
-
outputs = [await describeOutput(media, root,
|
|
16391
|
-
} else if (!entry &&
|
|
16643
|
+
outputs = [await describeOutput(media, root, path32.resolve(root, spec.source))];
|
|
16644
|
+
} else if (!entry && existsSync26(spec.outFile)) {
|
|
16392
16645
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16393
16646
|
} else {
|
|
16394
16647
|
outputs = [];
|
|
@@ -16591,7 +16844,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16591
16844
|
id: loaded.manifest.name,
|
|
16592
16845
|
name: loaded.manifest.name,
|
|
16593
16846
|
manifest: loaded.path,
|
|
16594
|
-
lock:
|
|
16847
|
+
lock: path32.resolve(opts.lockPath),
|
|
16595
16848
|
root,
|
|
16596
16849
|
provider: loaded.manifest.provider,
|
|
16597
16850
|
account: null,
|
|
@@ -16617,7 +16870,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16617
16870
|
return { snapshot, media };
|
|
16618
16871
|
}
|
|
16619
16872
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16620
|
-
const dir =
|
|
16873
|
+
const dir = path32.dirname(path32.resolve(opts.workspacePath));
|
|
16621
16874
|
const media = /* @__PURE__ */ new Map();
|
|
16622
16875
|
const projects = [];
|
|
16623
16876
|
const items = [];
|
|
@@ -16660,11 +16913,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16660
16913
|
id: project.id,
|
|
16661
16914
|
name: loaded.manifest.name,
|
|
16662
16915
|
manifest: manifestPath,
|
|
16663
|
-
lock:
|
|
16916
|
+
lock: path32.resolve(lockPath),
|
|
16664
16917
|
root: loaded.root,
|
|
16665
16918
|
provider: loaded.manifest.provider,
|
|
16666
16919
|
account: project.account ?? null,
|
|
16667
|
-
manifestSha256: manifestSha256 ?? sha256(await
|
|
16920
|
+
manifestSha256: manifestSha256 ?? sha256(await readFile24(manifestPath, "utf8")),
|
|
16668
16921
|
historyLimit: historyLimit(loaded.manifest),
|
|
16669
16922
|
entries: Object.keys(lock.entries).length,
|
|
16670
16923
|
items: projectItems.length,
|
|
@@ -16676,8 +16929,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16676
16929
|
id: project.id,
|
|
16677
16930
|
name: project.id,
|
|
16678
16931
|
manifest: manifestPath,
|
|
16679
|
-
lock:
|
|
16680
|
-
root:
|
|
16932
|
+
lock: path32.resolve(lockPath),
|
|
16933
|
+
root: path32.dirname(manifestPath),
|
|
16681
16934
|
provider: project.provider,
|
|
16682
16935
|
account: project.account ?? null,
|
|
16683
16936
|
manifestSha256: null,
|
|
@@ -16704,7 +16957,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16704
16957
|
version: 1,
|
|
16705
16958
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16706
16959
|
project: null,
|
|
16707
|
-
workspace: { path:
|
|
16960
|
+
workspace: { path: path32.resolve(opts.workspacePath), dir, projects },
|
|
16708
16961
|
filter,
|
|
16709
16962
|
totals: {
|
|
16710
16963
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16721,7 +16974,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16721
16974
|
}
|
|
16722
16975
|
|
|
16723
16976
|
// src/gallery/page.ts
|
|
16724
|
-
import { existsSync as
|
|
16977
|
+
import { existsSync as existsSync27, readFileSync as readFileSync5 } from "fs";
|
|
16725
16978
|
|
|
16726
16979
|
// src/gallery/client-bundle.ts
|
|
16727
16980
|
import { createRequire } from "module";
|
|
@@ -16762,7 +17015,7 @@ function clientAssets() {
|
|
|
16762
17015
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
16763
17016
|
client ??= {
|
|
16764
17017
|
css: readFileSync5(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
16765
|
-
js:
|
|
17018
|
+
js: existsSync27(prebuilt) ? readFileSync5(prebuilt, "utf8") : bundleGalleryClient()
|
|
16766
17019
|
};
|
|
16767
17020
|
return client;
|
|
16768
17021
|
}
|
|
@@ -16905,10 +17158,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
16905
17158
|
|
|
16906
17159
|
// src/editor/install.ts
|
|
16907
17160
|
import { createHash as createHash5 } from "crypto";
|
|
16908
|
-
import { existsSync as
|
|
16909
|
-
import { mkdir as mkdir11, readFile as
|
|
17161
|
+
import { existsSync as existsSync28 } from "fs";
|
|
17162
|
+
import { mkdir as mkdir11, readFile as readFile25, rename as rename10, rm as rm12, stat as stat4, writeFile as writeFile16 } from "fs/promises";
|
|
16910
17163
|
import os2 from "os";
|
|
16911
|
-
import
|
|
17164
|
+
import path33 from "path";
|
|
16912
17165
|
var EditorInstallError = class extends Error {
|
|
16913
17166
|
constructor(message6, file) {
|
|
16914
17167
|
super(message6);
|
|
@@ -16919,15 +17172,15 @@ var EditorInstallError = class extends Error {
|
|
|
16919
17172
|
};
|
|
16920
17173
|
function editorToolsRoot() {
|
|
16921
17174
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
16922
|
-
if (override) return
|
|
16923
|
-
if (process.platform === "darwin") return
|
|
17175
|
+
if (override) return path33.resolve(override);
|
|
17176
|
+
if (process.platform === "darwin") return path33.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
16924
17177
|
if (process.platform === "win32") {
|
|
16925
|
-
return
|
|
17178
|
+
return path33.join(process.env.LOCALAPPDATA ?? path33.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
16926
17179
|
}
|
|
16927
|
-
return
|
|
17180
|
+
return path33.join(process.env.XDG_CACHE_HOME ?? path33.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
|
|
16928
17181
|
}
|
|
16929
17182
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
16930
|
-
return
|
|
17183
|
+
return path33.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
16931
17184
|
}
|
|
16932
17185
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
16933
17186
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -16937,7 +17190,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
16937
17190
|
}
|
|
16938
17191
|
async function sha256Of(file) {
|
|
16939
17192
|
try {
|
|
16940
|
-
return createHash5("sha256").update(await
|
|
17193
|
+
return createHash5("sha256").update(await readFile25(file)).digest("hex");
|
|
16941
17194
|
} catch {
|
|
16942
17195
|
return null;
|
|
16943
17196
|
}
|
|
@@ -16950,8 +17203,8 @@ async function editorStatus(opts = {}) {
|
|
|
16950
17203
|
let totalBytes = 0;
|
|
16951
17204
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
16952
17205
|
totalBytes += expected.bytes;
|
|
16953
|
-
const file =
|
|
16954
|
-
const info =
|
|
17206
|
+
const file = path33.join(dir, name);
|
|
17207
|
+
const info = existsSync28(file) ? await stat4(file).catch(() => null) : null;
|
|
16955
17208
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
16956
17209
|
installedBytes += expected.bytes;
|
|
16957
17210
|
} else {
|
|
@@ -17008,13 +17261,13 @@ async function installEditor(opts = {}) {
|
|
|
17008
17261
|
if (digest2 !== expected.sha256) {
|
|
17009
17262
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17010
17263
|
}
|
|
17011
|
-
const final =
|
|
17264
|
+
const final = path33.join(dir, name);
|
|
17012
17265
|
const tmp = `${final}.${process.pid}.part`;
|
|
17013
17266
|
try {
|
|
17014
|
-
await
|
|
17267
|
+
await writeFile16(tmp, bytes);
|
|
17015
17268
|
await rename10(tmp, final);
|
|
17016
17269
|
} finally {
|
|
17017
|
-
await
|
|
17270
|
+
await rm12(tmp, { force: true });
|
|
17018
17271
|
}
|
|
17019
17272
|
fetchedBytes += bytes.length;
|
|
17020
17273
|
downloaded.push(name);
|
|
@@ -17054,12 +17307,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17054
17307
|
".json": "application/json; charset=utf-8"
|
|
17055
17308
|
};
|
|
17056
17309
|
function editorContentType(name) {
|
|
17057
|
-
return EDITOR_CONTENT_TYPES[
|
|
17310
|
+
return EDITOR_CONTENT_TYPES[path33.extname(name).toLowerCase()] ?? null;
|
|
17058
17311
|
}
|
|
17059
17312
|
|
|
17060
17313
|
// src/gallery/editor.ts
|
|
17061
|
-
import { readFile as
|
|
17062
|
-
import
|
|
17314
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
17315
|
+
import path34 from "path";
|
|
17063
17316
|
function editorRoute(release, name = "index.html") {
|
|
17064
17317
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17065
17318
|
}
|
|
@@ -17132,7 +17385,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17132
17385
|
if (!status.installed) return null;
|
|
17133
17386
|
let bytes;
|
|
17134
17387
|
try {
|
|
17135
|
-
bytes = await
|
|
17388
|
+
bytes = await readFile26(path34.join(dir, name));
|
|
17136
17389
|
} catch {
|
|
17137
17390
|
verified = null;
|
|
17138
17391
|
return null;
|
|
@@ -17149,7 +17402,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17149
17402
|
// src/gallery/server.ts
|
|
17150
17403
|
import { randomBytes } from "crypto";
|
|
17151
17404
|
import { createServer as createServer2 } from "http";
|
|
17152
|
-
import { readFile as
|
|
17405
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
17153
17406
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17154
17407
|
var EDITOR_CSP = [
|
|
17155
17408
|
"default-src 'self'",
|
|
@@ -17330,7 +17583,7 @@ async function serveGallery(opts) {
|
|
|
17330
17583
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17331
17584
|
if (!asset) return fail(404, "no such review asset");
|
|
17332
17585
|
try {
|
|
17333
|
-
const bytes = await
|
|
17586
|
+
const bytes = await readFile27(asset.path);
|
|
17334
17587
|
res.writeHead(200, {
|
|
17335
17588
|
"Content-Type": asset.contentType,
|
|
17336
17589
|
"Cache-Control": "no-store",
|
|
@@ -17413,7 +17666,7 @@ async function serveGallery(opts) {
|
|
|
17413
17666
|
if (!asset) return fail(404, "no such gallery media");
|
|
17414
17667
|
let bytes;
|
|
17415
17668
|
try {
|
|
17416
|
-
bytes = await
|
|
17669
|
+
bytes = await readFile27(asset.path);
|
|
17417
17670
|
} catch {
|
|
17418
17671
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17419
17672
|
}
|
|
@@ -17457,27 +17710,27 @@ async function serveGallery(opts) {
|
|
|
17457
17710
|
|
|
17458
17711
|
// src/gallery/generate.ts
|
|
17459
17712
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
17460
|
-
import { z as
|
|
17461
|
-
var GenerateRequestSchema =
|
|
17462
|
-
project:
|
|
17463
|
-
keys:
|
|
17713
|
+
import { z as z14 } from "zod";
|
|
17714
|
+
var GenerateRequestSchema = z14.object({
|
|
17715
|
+
project: z14.string().min(1).optional(),
|
|
17716
|
+
keys: z14.array(z14.string().min(1)).min(1).max(500),
|
|
17464
17717
|
/**
|
|
17465
17718
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
17466
17719
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
17467
17720
|
* the recorded bytes back over a modified file.
|
|
17468
17721
|
*/
|
|
17469
|
-
force:
|
|
17722
|
+
force: z14.boolean().optional(),
|
|
17470
17723
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
17471
|
-
resume:
|
|
17724
|
+
resume: z14.boolean().optional(),
|
|
17472
17725
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
17473
|
-
refresh:
|
|
17726
|
+
refresh: z14.boolean().optional(),
|
|
17474
17727
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
17475
|
-
restore:
|
|
17728
|
+
restore: z14.boolean().optional(),
|
|
17476
17729
|
/**
|
|
17477
17730
|
* Bring a replaced generation back: 1-based position in the record's
|
|
17478
17731
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
17479
17732
|
*/
|
|
17480
|
-
revert:
|
|
17733
|
+
revert: z14.string().min(1).max(64).optional()
|
|
17481
17734
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
17482
17735
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
17483
17736
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -17823,19 +18076,19 @@ function createGenerateHandlers(opts) {
|
|
|
17823
18076
|
}
|
|
17824
18077
|
|
|
17825
18078
|
// src/pipeline/salvage.ts
|
|
17826
|
-
import { readFile as
|
|
17827
|
-
import { existsSync as
|
|
17828
|
-
import
|
|
18079
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
18080
|
+
import { existsSync as existsSync29 } from "fs";
|
|
18081
|
+
import path35 from "path";
|
|
17829
18082
|
function providerClaimId(provider, objectId) {
|
|
17830
18083
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
17831
18084
|
}
|
|
17832
18085
|
async function loadClaims(lockPaths, opts = {}) {
|
|
17833
18086
|
const claimed = /* @__PURE__ */ new Set();
|
|
17834
18087
|
for (const p of lockPaths) {
|
|
17835
|
-
if (!
|
|
18088
|
+
if (!existsSync29(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
17836
18089
|
let parsed;
|
|
17837
18090
|
try {
|
|
17838
|
-
parsed = parseLock(JSON.parse(await
|
|
18091
|
+
parsed = parseLock(JSON.parse(await readFile28(p, "utf8")));
|
|
17839
18092
|
} catch {
|
|
17840
18093
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
17841
18094
|
}
|
|
@@ -17939,20 +18192,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
17939
18192
|
return matchStyleByPattern(prompt, manifest);
|
|
17940
18193
|
}
|
|
17941
18194
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
17942
|
-
const own =
|
|
18195
|
+
const own = path35.resolve(ownManifestPath);
|
|
17943
18196
|
const siblingManifestPaths = [
|
|
17944
18197
|
.../* @__PURE__ */ new Set([
|
|
17945
18198
|
...workspaceManifestPaths,
|
|
17946
|
-
...claimPaths.map((c) =>
|
|
18199
|
+
...claimPaths.map((c) => path35.join(path35.dirname(path35.resolve(c)), "pixelkiln.manifest.json"))
|
|
17947
18200
|
])
|
|
17948
18201
|
];
|
|
17949
18202
|
const siblings = [];
|
|
17950
18203
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
17951
|
-
if (
|
|
17952
|
-
if (!
|
|
18204
|
+
if (path35.resolve(siblingManifestPath) === own) continue;
|
|
18205
|
+
if (!existsSync29(siblingManifestPath)) continue;
|
|
17953
18206
|
try {
|
|
17954
18207
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
17955
|
-
siblings.push({ label:
|
|
18208
|
+
siblings.push({ label: path35.basename(path35.dirname(siblingManifestPath)), manifest });
|
|
17956
18209
|
} catch {
|
|
17957
18210
|
}
|
|
17958
18211
|
}
|
|
@@ -18072,8 +18325,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18072
18325
|
}
|
|
18073
18326
|
|
|
18074
18327
|
// src/pick/salvage-server.ts
|
|
18075
|
-
import { mkdir as mkdir12, writeFile as
|
|
18076
|
-
import
|
|
18328
|
+
import { mkdir as mkdir12, writeFile as writeFile17, readFile as readFile29 } from "fs/promises";
|
|
18329
|
+
import path36 from "path";
|
|
18077
18330
|
|
|
18078
18331
|
// src/pick/salvage-sheet.ts
|
|
18079
18332
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18239,7 +18492,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18239
18492
|
});
|
|
18240
18493
|
const html = renderSalvageSheet(orphans, {
|
|
18241
18494
|
styleId: ctx.styleId,
|
|
18242
|
-
importDir:
|
|
18495
|
+
importDir: path36.relative(process.cwd(), ctx.importDir) || "."
|
|
18243
18496
|
});
|
|
18244
18497
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18245
18498
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18295,10 +18548,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18295
18548
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18296
18549
|
decodePng(buf);
|
|
18297
18550
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18298
|
-
const rel =
|
|
18299
|
-
const outFile =
|
|
18300
|
-
await mkdir12(
|
|
18301
|
-
await
|
|
18551
|
+
const rel = path36.join("_salvaged", `${assetId}.png`);
|
|
18552
|
+
const outFile = path36.resolve(ctx.importDir, rel);
|
|
18553
|
+
await mkdir12(path36.dirname(outFile), { recursive: true });
|
|
18554
|
+
await writeFile17(outFile, buf);
|
|
18302
18555
|
ctx.manifest.assets[assetId] = {
|
|
18303
18556
|
prompt: orphan.prompt,
|
|
18304
18557
|
promptByStyle: {},
|
|
@@ -18330,7 +18583,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18330
18583
|
sourceUrl: durableSource,
|
|
18331
18584
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18332
18585
|
outputs: [{
|
|
18333
|
-
path: portableOutputPath(outFile,
|
|
18586
|
+
path: portableOutputPath(outFile, path36.dirname(ctx.manifestPath)),
|
|
18334
18587
|
sha256: sha256(buf)
|
|
18335
18588
|
}],
|
|
18336
18589
|
submittedAt: orphan.createdAt,
|
|
@@ -18354,9 +18607,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18354
18607
|
}
|
|
18355
18608
|
}
|
|
18356
18609
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18357
|
-
const raw = JSON.parse(await
|
|
18610
|
+
const raw = JSON.parse(await readFile29(ctx.manifestPath, "utf8"));
|
|
18358
18611
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18359
|
-
await
|
|
18612
|
+
await writeFile17(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18360
18613
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18361
18614
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18362
18615
|
return result;
|