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.cjs
CHANGED
|
@@ -334,6 +334,7 @@ var RevisionModeSchema = import_zod.z.enum([
|
|
|
334
334
|
"outpaint",
|
|
335
335
|
"reduce-colors",
|
|
336
336
|
"correct-pixelart",
|
|
337
|
+
"remove-background",
|
|
337
338
|
"animate",
|
|
338
339
|
"animate-pixminimax",
|
|
339
340
|
"animate-skeleton",
|
|
@@ -665,7 +666,22 @@ var RevisionSchema = import_zod.z.object({
|
|
|
665
666
|
* `interpolate`: required, the ending keyframe; the parent is the start.
|
|
666
667
|
*/
|
|
667
668
|
lastFrame: import_zod.z.string().min(1).optional(),
|
|
668
|
-
/**
|
|
669
|
+
/**
|
|
670
|
+
* `animate`/`animate-pixminimax` only: colour de-flicker sensitivity.
|
|
671
|
+
* Frames whose foreground drifts from the first frame beyond this are
|
|
672
|
+
* corrected toward it; 0 corrects every frame and higher values correct
|
|
673
|
+
* fewer. Omit for PixelLab's default.
|
|
674
|
+
*/
|
|
675
|
+
driftThreshold: import_zod.z.number().min(0).optional(),
|
|
676
|
+
/**
|
|
677
|
+
* `animate-pixminimax` only: one noun phrase for what the parent shows.
|
|
678
|
+
* PixelLab reads it off the image when omitted, and can decline some
|
|
679
|
+
* images, failing the job until it is sent.
|
|
680
|
+
*/
|
|
681
|
+
subjectDescription: import_zod.z.string().min(1).max(300).optional(),
|
|
682
|
+
/** `animate-pixminimax` only: the pose the parent is in, read off the image by PixelLab when omitted. */
|
|
683
|
+
initialPose: import_zod.z.string().min(1).max(300).optional(),
|
|
684
|
+
/** `animate-pixminimax` only: facing direction, one of the model's caption fields, so it also holds the sprite's facing. */
|
|
669
685
|
direction: CharacterDirectionSchema.optional(),
|
|
670
686
|
/** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
|
|
671
687
|
enhancePrompt: import_zod.z.boolean().optional(),
|
|
@@ -685,12 +701,15 @@ var RevisionSchema = import_zod.z.object({
|
|
|
685
701
|
* same `mannequin`/`bear`/`cat`/`dog`/`horse`/`lion` vocabulary.
|
|
686
702
|
*/
|
|
687
703
|
skeletonTemplate: import_zod.z.string().min(1).optional(),
|
|
704
|
+
/** `remove-background` only: `simple` is faster and suits flat backgrounds; `complex` handles detailed edges. PixelLab defaults to `simple`. */
|
|
705
|
+
removalTask: import_zod.z.enum(["simple", "complex"]).optional(),
|
|
688
706
|
/**
|
|
689
|
-
* `animate-skeleton
|
|
707
|
+
* `animate-skeleton`: what the character *looks like*, a noun
|
|
690
708
|
* phrase (colours, clothing, held items) — distinct from the asset's
|
|
691
709
|
* own `prompt`, which this mode sends as the motion's short label
|
|
692
710
|
* (PixelLab's `action`), the same `prompt`-as-motion convention
|
|
693
|
-
* `animate`/`animate-pixminimax` already use.
|
|
711
|
+
* `animate`/`animate-pixminimax` already use. `remove-background`: a
|
|
712
|
+
* short description of the foreground, which PixelLab uses as a hint.
|
|
694
713
|
*/
|
|
695
714
|
description: import_zod.z.string().min(1).optional(),
|
|
696
715
|
/**
|
|
@@ -807,7 +826,44 @@ var RevisionSchema = import_zod.z.object({
|
|
|
807
826
|
path: ["keypointsFile"]
|
|
808
827
|
});
|
|
809
828
|
}
|
|
810
|
-
|
|
829
|
+
if (revision.driftThreshold !== void 0 && revision.mode !== "animate" && revision.mode !== "animate-pixminimax") {
|
|
830
|
+
context.addIssue({
|
|
831
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
832
|
+
message: "driftThreshold applies to animate/animate-pixminimax revisions only",
|
|
833
|
+
path: ["driftThreshold"]
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
for (const field of ["subjectDescription", "initialPose"]) {
|
|
837
|
+
if (revision[field] !== void 0 && revision.mode !== "animate-pixminimax") {
|
|
838
|
+
context.addIssue({
|
|
839
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
840
|
+
message: `${field} applies to animate-pixminimax revisions only`,
|
|
841
|
+
path: [field]
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (revision.removalTask !== void 0 && revision.mode !== "remove-background") {
|
|
846
|
+
context.addIssue({
|
|
847
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
848
|
+
message: "removalTask applies to remove-background revisions only",
|
|
849
|
+
path: ["removalTask"]
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
if (revision.strength !== void 0 && revision.mode === "remove-background") {
|
|
853
|
+
context.addIssue({
|
|
854
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
855
|
+
message: "remove-background revisions do not take a strength",
|
|
856
|
+
path: ["strength"]
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
if (revision.description !== void 0 && revision.mode !== "animate-skeleton" && revision.mode !== "remove-background") {
|
|
860
|
+
context.addIssue({
|
|
861
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
862
|
+
message: "description applies to animate-skeleton/remove-background revisions only",
|
|
863
|
+
path: ["description"]
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
for (const field of ["keypointsFile", "skeletonTemplate"]) {
|
|
811
867
|
if (revision[field] !== void 0 && revision.mode !== "animate-skeleton") {
|
|
812
868
|
context.addIssue({
|
|
813
869
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -1461,6 +1517,10 @@ var LockEntrySchema = import_zod.z.object({
|
|
|
1461
1517
|
keypointsSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1462
1518
|
skeletonTemplate: import_zod.z.string().min(1).optional(),
|
|
1463
1519
|
description: import_zod.z.string().min(1).optional(),
|
|
1520
|
+
removalTask: import_zod.z.enum(["simple", "complex"]).optional(),
|
|
1521
|
+
driftThreshold: import_zod.z.number().min(0).optional(),
|
|
1522
|
+
subjectDescription: import_zod.z.string().min(1).max(300).optional(),
|
|
1523
|
+
initialPose: import_zod.z.string().min(1).max(300).optional(),
|
|
1464
1524
|
engine: import_zod.z.enum(["pro-flash"]).optional()
|
|
1465
1525
|
}).strict().nullable().default(null),
|
|
1466
1526
|
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
@@ -1889,6 +1949,7 @@ var CorrectPixelartResponseSchema = import_zod3.z.object({
|
|
|
1889
1949
|
images: import_zod3.z.array(import_zod3.z.object({ base64: import_zod3.z.string().min(1) }).passthrough()).min(1),
|
|
1890
1950
|
usage: import_zod3.z.unknown().optional()
|
|
1891
1951
|
}).passthrough();
|
|
1952
|
+
var RemoveBackgroundResponseSchema = import_zod3.z.object({ image: import_zod3.z.object({ base64: import_zod3.z.string().min(1) }).passthrough(), usage: import_zod3.z.unknown().optional() }).passthrough();
|
|
1892
1953
|
var ProFlashImageSubmitSchema = import_zod3.z.object({
|
|
1893
1954
|
background_job_id: import_zod3.z.string().min(1),
|
|
1894
1955
|
image_id: import_zod3.z.string().min(1),
|
|
@@ -2080,9 +2141,9 @@ var PixelLabClient = class {
|
|
|
2080
2141
|
apiKey;
|
|
2081
2142
|
http;
|
|
2082
2143
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
2083
|
-
async request(
|
|
2144
|
+
async request(path37, init) {
|
|
2084
2145
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
2085
|
-
const res = await this.http(`${BASE}${
|
|
2146
|
+
const res = await this.http(`${BASE}${path37}`, {
|
|
2086
2147
|
...init,
|
|
2087
2148
|
headers: {
|
|
2088
2149
|
Authorization: auth,
|
|
@@ -2092,13 +2153,13 @@ var PixelLabClient = class {
|
|
|
2092
2153
|
});
|
|
2093
2154
|
const text = await res.text();
|
|
2094
2155
|
if (!res.ok) {
|
|
2095
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
2156
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path37} \u2192 ${res.status}`, res.status, text);
|
|
2096
2157
|
}
|
|
2097
2158
|
if (!text) return {};
|
|
2098
2159
|
try {
|
|
2099
2160
|
return JSON.parse(text);
|
|
2100
2161
|
} catch {
|
|
2101
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
2162
|
+
throw new Error(`${init?.method ?? "GET"} ${path37} returned invalid JSON`);
|
|
2102
2163
|
}
|
|
2103
2164
|
}
|
|
2104
2165
|
async balance() {
|
|
@@ -2503,10 +2564,10 @@ var PixelLabClient = class {
|
|
|
2503
2564
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2504
2565
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2505
2566
|
const south = args.reference?.south;
|
|
2506
|
-
let
|
|
2567
|
+
let path37;
|
|
2507
2568
|
let body;
|
|
2508
2569
|
if (args.mode === "v3") {
|
|
2509
|
-
|
|
2570
|
+
path37 = "/create-character-v3";
|
|
2510
2571
|
body = {
|
|
2511
2572
|
description: args.description,
|
|
2512
2573
|
image_size: imageSize,
|
|
@@ -2520,7 +2581,7 @@ var PixelLabClient = class {
|
|
|
2520
2581
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2521
2582
|
};
|
|
2522
2583
|
} else if (args.mode === "pro-flash") {
|
|
2523
|
-
|
|
2584
|
+
path37 = "/create-character-pro-flash";
|
|
2524
2585
|
const traits = args.styleTraits;
|
|
2525
2586
|
body = {
|
|
2526
2587
|
description: args.description,
|
|
@@ -2544,7 +2605,7 @@ var PixelLabClient = class {
|
|
|
2544
2605
|
} : {}
|
|
2545
2606
|
};
|
|
2546
2607
|
} else if (args.mode === "pro") {
|
|
2547
|
-
|
|
2608
|
+
path37 = "/create-character-pro";
|
|
2548
2609
|
body = {
|
|
2549
2610
|
description: args.description,
|
|
2550
2611
|
image_size: imageSize,
|
|
@@ -2558,7 +2619,7 @@ var PixelLabClient = class {
|
|
|
2558
2619
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2559
2620
|
};
|
|
2560
2621
|
} else {
|
|
2561
|
-
|
|
2622
|
+
path37 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2562
2623
|
body = {
|
|
2563
2624
|
description: args.description,
|
|
2564
2625
|
image_size: imageSize,
|
|
@@ -2575,8 +2636,8 @@ var PixelLabClient = class {
|
|
|
2575
2636
|
...palette
|
|
2576
2637
|
};
|
|
2577
2638
|
}
|
|
2578
|
-
const raw = await this.request(
|
|
2579
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2639
|
+
const raw = await this.request(path37, { method: "POST", body: JSON.stringify(body) });
|
|
2640
|
+
return validateResponse(CharacterSubmitSchema, raw, path37.slice(1));
|
|
2580
2641
|
}
|
|
2581
2642
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2582
2643
|
async createCharacterState(args) {
|
|
@@ -2953,6 +3014,27 @@ var PixelLabClient = class {
|
|
|
2953
3014
|
const pngs = cleanupResults(res.images, images.length, "correct-pixelart");
|
|
2954
3015
|
return { png: pngs[0], pngs, usage: res.usage };
|
|
2955
3016
|
}
|
|
3017
|
+
/**
|
|
3018
|
+
* `/remove-background`: a transparent PNG from one pixel-art image,
|
|
3019
|
+
* synchronously, like the rest of the Cleanup tier. The API takes at most
|
|
3020
|
+
* 400x400 worth of area, one image per call. `simple` is faster and suits
|
|
3021
|
+
* flat backgrounds; `complex` is slower and handles detailed edges. The
|
|
3022
|
+
* schema's response example is dollar-denominated (`usd: 0.01`); the
|
|
3023
|
+
* subscription cost measured 1 generation (docs/ENDPOINTS.md), and the
|
|
3024
|
+
* default `simple` task is the one measured.
|
|
3025
|
+
*/
|
|
3026
|
+
async removeBackground(args) {
|
|
3027
|
+
const body = { image: args.image, image_size: { width: args.width, height: args.height } };
|
|
3028
|
+
if (args.task) body.background_removal_task = args.task === "complex" ? "remove_complex_background" : "remove_simple_background";
|
|
3029
|
+
if (args.text) body.text = args.text;
|
|
3030
|
+
if (args.seed != null) body.seed = args.seed;
|
|
3031
|
+
const res = validateResponse(
|
|
3032
|
+
RemoveBackgroundResponseSchema,
|
|
3033
|
+
await this.request("/remove-background", { method: "POST", body: JSON.stringify(body) }),
|
|
3034
|
+
"remove-background"
|
|
3035
|
+
);
|
|
3036
|
+
return { png: Buffer.from(res.image.base64, "base64"), usage: res.usage };
|
|
3037
|
+
}
|
|
2956
3038
|
/**
|
|
2957
3039
|
* `/unzoom`: recover native-resolution pixel art from an upscaled image (a
|
|
2958
3040
|
* 32x32 sprite saved at 512x512). Synchronous like the Cleanup tier.
|
|
@@ -3061,6 +3143,7 @@ var PixelLabClient = class {
|
|
|
3061
3143
|
*/
|
|
3062
3144
|
async animateWithTextV3(args) {
|
|
3063
3145
|
const body = { first_frame: args.firstFrame, action: args.action };
|
|
3146
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
3064
3147
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
3065
3148
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
3066
3149
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -3080,6 +3163,10 @@ var PixelLabClient = class {
|
|
|
3080
3163
|
*/
|
|
3081
3164
|
async animatePixminimax(args) {
|
|
3082
3165
|
const body = { first_frame: args.firstFrame, description: args.description };
|
|
3166
|
+
if (args.view) body.view = args.view;
|
|
3167
|
+
if (args.subjectDescription) body.subject_description = args.subjectDescription;
|
|
3168
|
+
if (args.initialPose) body.initial_pose = args.initialPose;
|
|
3169
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
3083
3170
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
3084
3171
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
3085
3172
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -3115,6 +3202,7 @@ var PixelLabClient = class {
|
|
|
3115
3202
|
keypoints: args.keypoints,
|
|
3116
3203
|
direction: args.direction
|
|
3117
3204
|
};
|
|
3205
|
+
if (args.view) body.view = args.view;
|
|
3118
3206
|
if (args.templateId) body.template_id = args.templateId;
|
|
3119
3207
|
if (args.action) body.action = args.action;
|
|
3120
3208
|
if (args.description) body.description = args.description;
|
|
@@ -3762,10 +3850,10 @@ var import_node_fs = require("fs");
|
|
|
3762
3850
|
function sha256(data) {
|
|
3763
3851
|
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
3764
3852
|
}
|
|
3765
|
-
async function sha256File(
|
|
3853
|
+
async function sha256File(path37) {
|
|
3766
3854
|
return new Promise((resolve, reject) => {
|
|
3767
3855
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
3768
|
-
const stream = (0, import_node_fs.createReadStream)(
|
|
3856
|
+
const stream = (0, import_node_fs.createReadStream)(path37);
|
|
3769
3857
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3770
3858
|
stream.on("error", reject);
|
|
3771
3859
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -3889,6 +3977,10 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3889
3977
|
keypointsSha256: spec.revision.keypointsSha256,
|
|
3890
3978
|
skeletonTemplate: spec.revision.skeletonTemplate,
|
|
3891
3979
|
description: spec.revision.description,
|
|
3980
|
+
removalTask: spec.revision.removalTask,
|
|
3981
|
+
driftThreshold: spec.revision.driftThreshold,
|
|
3982
|
+
subjectDescription: spec.revision.subjectDescription,
|
|
3983
|
+
initialPose: spec.revision.initialPose,
|
|
3892
3984
|
engine: spec.revision.engine
|
|
3893
3985
|
} : void 0
|
|
3894
3986
|
})
|
|
@@ -4064,7 +4156,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4064
4156
|
* outpaint path yet.
|
|
4065
4157
|
*/
|
|
4066
4158
|
supportsRevision(mode2) {
|
|
4067
|
-
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";
|
|
4159
|
+
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";
|
|
4068
4160
|
}
|
|
4069
4161
|
/** PixelLab's own constraints: submissions must be >2s apart, and
|
|
4070
4162
|
* background jobs in flight are capped by subscription tier (Tier 1=8,
|
|
@@ -4092,6 +4184,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4092
4184
|
if (spec.batch?.role === "member") {
|
|
4093
4185
|
return { unit: "generations", amount: 0, candidates: 1 };
|
|
4094
4186
|
}
|
|
4187
|
+
if (spec.revision?.mode === "remove-background") {
|
|
4188
|
+
return { unit: "generations", amount: 1, candidates: 1 };
|
|
4189
|
+
}
|
|
4095
4190
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
4096
4191
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
4097
4192
|
}
|
|
@@ -4180,6 +4275,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4180
4275
|
throw new Error(`PixelLab correct-pixelart source is ${width}x${height}; the API takes at most 1024 pixels per side`);
|
|
4181
4276
|
}
|
|
4182
4277
|
}
|
|
4278
|
+
if (spec.revision?.mode === "remove-background") {
|
|
4279
|
+
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
4280
|
+
if (width != null && height != null && (width > 400 || height > 400)) {
|
|
4281
|
+
throw new Error(`PixelLab remove-background source is ${width}x${height}; the API takes at most 400x400`);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4183
4284
|
if (spec.revision?.mode === "animate-skeleton") {
|
|
4184
4285
|
const { sourceWidth: width, sourceHeight: height, frames, skeletonTemplate } = spec.revision;
|
|
4185
4286
|
if (width != null && height != null && (width > 256 || height > 256)) {
|
|
@@ -4200,6 +4301,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4200
4301
|
if (mode2 === "animate" && frames != null && frames > 16) {
|
|
4201
4302
|
throw new Error(`PixelLab animate takes 4 to 16 frames; ${frames} is too many (use animate-pixminimax for up to 40)`);
|
|
4202
4303
|
}
|
|
4304
|
+
if (mode2 === "animate-pixminimax" && frames != null && frames % 4 !== 0) {
|
|
4305
|
+
throw new Error(`PixelLab animate-pixminimax takes a multiple of 4 frames (4 to 40); ${frames} is not one`);
|
|
4306
|
+
}
|
|
4203
4307
|
if (lastFrameWidth != null && lastFrameHeight != null && width != null && height != null && (lastFrameWidth !== width || lastFrameHeight !== height)) {
|
|
4204
4308
|
throw new Error(
|
|
4205
4309
|
`PixelLab ${mode2} lastFrame is ${lastFrameWidth}x${lastFrameHeight}; source is ${width}x${height} \u2014 they must match`
|
|
@@ -5291,6 +5395,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5291
5395
|
const res2 = await this.client.correctPixelart({ images, strength: revision.strength });
|
|
5292
5396
|
return this.cacheCleanupResult(res2.pngs, res2.usage, roles, revision.sourceFps);
|
|
5293
5397
|
}
|
|
5398
|
+
if (revision.mode === "remove-background") {
|
|
5399
|
+
if (width == null || height == null) throw new Error(`${spec.styleId}/${spec.assetId}: remove-background needs the source's size`);
|
|
5400
|
+
const res2 = await this.client.removeBackground({
|
|
5401
|
+
image,
|
|
5402
|
+
width,
|
|
5403
|
+
height,
|
|
5404
|
+
task: revision.removalTask,
|
|
5405
|
+
text: revision.description,
|
|
5406
|
+
seed: spec.seed
|
|
5407
|
+
});
|
|
5408
|
+
return this.cacheCleanupResult([res2.png], res2.usage, roles, revision.sourceFps);
|
|
5409
|
+
}
|
|
5294
5410
|
if (revision.mode === "interpolate") {
|
|
5295
5411
|
const lastFrame = this.readLastFrame(spec);
|
|
5296
5412
|
if (!lastFrame) throw new Error(`${spec.styleId}/${spec.assetId}: interpolate needs lastFrame, the ending keyframe`);
|
|
@@ -5326,7 +5442,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5326
5442
|
frameCount: revision.frames,
|
|
5327
5443
|
seed: spec.seed,
|
|
5328
5444
|
noBackground: spec.noBackground,
|
|
5329
|
-
enhancePrompt: revision.enhancePrompt
|
|
5445
|
+
enhancePrompt: revision.enhancePrompt,
|
|
5446
|
+
driftThreshold: revision.driftThreshold
|
|
5330
5447
|
});
|
|
5331
5448
|
return { jobId: res3.background_job_id };
|
|
5332
5449
|
}
|
|
@@ -5338,7 +5455,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5338
5455
|
seed: spec.seed,
|
|
5339
5456
|
noBackground: spec.noBackground,
|
|
5340
5457
|
enhancePrompt: revision.enhancePrompt,
|
|
5341
|
-
direction: revision.direction
|
|
5458
|
+
direction: revision.direction,
|
|
5459
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5460
|
+
subjectDescription: revision.subjectDescription,
|
|
5461
|
+
initialPose: revision.initialPose,
|
|
5462
|
+
driftThreshold: revision.driftThreshold
|
|
5342
5463
|
});
|
|
5343
5464
|
return { jobId: res2.background_job_id };
|
|
5344
5465
|
}
|
|
@@ -5355,6 +5476,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5355
5476
|
firstFrameKeypoints: revision.skeleton.firstFrameKeypoints,
|
|
5356
5477
|
keypoints: revision.skeleton.frames,
|
|
5357
5478
|
direction: revision.direction,
|
|
5479
|
+
// The style's camera, when it is one this endpoint knows; its enum is
|
|
5480
|
+
// narrower than a style's free-string `view`.
|
|
5481
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5358
5482
|
templateId: revision.skeletonTemplate,
|
|
5359
5483
|
action: spec.prompt,
|
|
5360
5484
|
description: revision.description,
|
|
@@ -5425,7 +5549,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5425
5549
|
}
|
|
5426
5550
|
async poll(jobId, generator, context) {
|
|
5427
5551
|
const revisionMode = context?.spec?.revision?.mode;
|
|
5428
|
-
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart") {
|
|
5552
|
+
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart" || revisionMode === "remove-background") {
|
|
5429
5553
|
return this.pollCachedRevision(jobId, context);
|
|
5430
5554
|
}
|
|
5431
5555
|
if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "animate-skeleton" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
|
|
@@ -6133,9 +6257,9 @@ var RetroDiffusionClient = class {
|
|
|
6133
6257
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
6134
6258
|
return Buffer.from(await response.arrayBuffer());
|
|
6135
6259
|
}
|
|
6136
|
-
async call(
|
|
6260
|
+
async call(path37, init = {}) {
|
|
6137
6261
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
6138
|
-
const response = await this.request(`${this.baseUrl}${
|
|
6262
|
+
const response = await this.request(`${this.baseUrl}${path37}`, {
|
|
6139
6263
|
...init,
|
|
6140
6264
|
headers: {
|
|
6141
6265
|
"Content-Type": "application/json",
|
|
@@ -9141,6 +9265,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9141
9265
|
} : {},
|
|
9142
9266
|
...asset.revision.skeletonTemplate ? { skeletonTemplate: asset.revision.skeletonTemplate } : {},
|
|
9143
9267
|
...asset.revision.description ? { description: asset.revision.description } : {},
|
|
9268
|
+
...asset.revision.removalTask ? { removalTask: asset.revision.removalTask } : {},
|
|
9269
|
+
...asset.revision.driftThreshold == null ? {} : { driftThreshold: asset.revision.driftThreshold },
|
|
9270
|
+
...asset.revision.subjectDescription ? { subjectDescription: asset.revision.subjectDescription } : {},
|
|
9271
|
+
...asset.revision.initialPose ? { initialPose: asset.revision.initialPose } : {},
|
|
9144
9272
|
...asset.revision.engine ? { engine: asset.revision.engine } : {}
|
|
9145
9273
|
};
|
|
9146
9274
|
}
|
|
@@ -12424,10 +12552,10 @@ function isSha256Hash(value) {
|
|
|
12424
12552
|
function parseCache(value) {
|
|
12425
12553
|
return HashCacheSchema.parse(value);
|
|
12426
12554
|
}
|
|
12427
|
-
async function loadCache(
|
|
12428
|
-
if (!(0, import_node_fs14.existsSync)(
|
|
12555
|
+
async function loadCache(path37) {
|
|
12556
|
+
if (!(0, import_node_fs14.existsSync)(path37)) return { version: 1, hashes: {} };
|
|
12429
12557
|
try {
|
|
12430
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(
|
|
12558
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(path37, "utf8")));
|
|
12431
12559
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12432
12560
|
return {
|
|
12433
12561
|
version: 1,
|
|
@@ -12439,18 +12567,18 @@ async function loadCache(path36) {
|
|
|
12439
12567
|
return { version: 1, hashes: {} };
|
|
12440
12568
|
}
|
|
12441
12569
|
}
|
|
12442
|
-
async function saveCache(
|
|
12570
|
+
async function saveCache(path37, cache) {
|
|
12443
12571
|
const sorted = {};
|
|
12444
12572
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12445
12573
|
const hash = cache.hashes[key];
|
|
12446
12574
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12447
12575
|
sorted[key] = hash;
|
|
12448
12576
|
}
|
|
12449
|
-
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(import_node_path18.default.resolve(
|
|
12450
|
-
const tmp = `${
|
|
12577
|
+
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(import_node_path18.default.resolve(path37)), { recursive: true });
|
|
12578
|
+
const tmp = `${path37}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12451
12579
|
try {
|
|
12452
12580
|
await (0, import_promises11.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12453
|
-
await (0, import_promises11.rename)(tmp,
|
|
12581
|
+
await (0, import_promises11.rename)(tmp, path37);
|
|
12454
12582
|
} finally {
|
|
12455
12583
|
await (0, import_promises11.rm)(tmp, { force: true });
|
|
12456
12584
|
}
|
|
@@ -13966,6 +14094,10 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13966
14094
|
...spec.revision.keypointsSha256 ? { keypointsSha256: spec.revision.keypointsSha256 } : {},
|
|
13967
14095
|
...spec.revision.skeletonTemplate ? { skeletonTemplate: spec.revision.skeletonTemplate } : {},
|
|
13968
14096
|
...spec.revision.description ? { description: spec.revision.description } : {},
|
|
14097
|
+
...spec.revision.removalTask ? { removalTask: spec.revision.removalTask } : {},
|
|
14098
|
+
...spec.revision.driftThreshold == null ? {} : { driftThreshold: spec.revision.driftThreshold },
|
|
14099
|
+
...spec.revision.subjectDescription ? { subjectDescription: spec.revision.subjectDescription } : {},
|
|
14100
|
+
...spec.revision.initialPose ? { initialPose: spec.revision.initialPose } : {},
|
|
13969
14101
|
...spec.revision.engine ? { engine: spec.revision.engine } : {}
|
|
13970
14102
|
} : null,
|
|
13971
14103
|
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
@@ -15255,9 +15387,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15255
15387
|
|
|
15256
15388
|
// src/gallery/snapshot.ts
|
|
15257
15389
|
var import_node_crypto6 = require("crypto");
|
|
15258
|
-
var
|
|
15259
|
-
var
|
|
15260
|
-
var
|
|
15390
|
+
var import_node_fs28 = require("fs");
|
|
15391
|
+
var import_promises26 = require("fs/promises");
|
|
15392
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
15261
15393
|
|
|
15262
15394
|
// src/workspace.ts
|
|
15263
15395
|
var import_promises19 = require("fs/promises");
|
|
@@ -15393,15 +15525,16 @@ function validateWorkspace(ws, dir) {
|
|
|
15393
15525
|
}
|
|
15394
15526
|
|
|
15395
15527
|
// src/gallery/edit.ts
|
|
15396
|
-
var
|
|
15397
|
-
var
|
|
15398
|
-
var
|
|
15528
|
+
var import_promises25 = require("fs/promises");
|
|
15529
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
15530
|
+
var import_zod13 = require("zod");
|
|
15399
15531
|
|
|
15400
15532
|
// src/manifest-edit.ts
|
|
15401
15533
|
var import_promises20 = require("fs/promises");
|
|
15402
15534
|
var import_node_path27 = __toESM(require("path"), 1);
|
|
15403
15535
|
var import_zod8 = require("zod");
|
|
15404
15536
|
var HexSha = import_zod8.z.string().regex(/^[0-9a-f]{64}$/);
|
|
15537
|
+
var ProjectFileSchema = import_zod8.z.string().min(1).refine((file) => !import_node_path27.default.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
15405
15538
|
var AssetPatchSchema = import_zod8.z.object({
|
|
15406
15539
|
prompt: import_zod8.z.string().optional(),
|
|
15407
15540
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -15450,10 +15583,11 @@ var NewAssetSchema = import_zod8.z.object({
|
|
|
15450
15583
|
styles: import_zod8.z.array(import_zod8.z.string().min(1)).optional(),
|
|
15451
15584
|
/**
|
|
15452
15585
|
* A controlled regeneration of another asset in the same manifest,
|
|
15453
|
-
* instead of a fresh generation.
|
|
15454
|
-
*
|
|
15455
|
-
* `
|
|
15456
|
-
*
|
|
15586
|
+
* instead of a fresh generation. Every mode but `outpaint` can be
|
|
15587
|
+
* created here: `inpaint` names a mask file the gallery's mask editor
|
|
15588
|
+
* wrote first, `interpolate` names an existing image as its ending
|
|
15589
|
+
* keyframe, and `animate-skeleton` names the keypoints file the gallery
|
|
15590
|
+
* wrote first.
|
|
15457
15591
|
*/
|
|
15458
15592
|
revision: import_zod8.z.discriminatedUnion("mode", [
|
|
15459
15593
|
import_zod8.z.object({
|
|
@@ -15462,6 +15596,52 @@ var NewAssetSchema = import_zod8.z.object({
|
|
|
15462
15596
|
from: import_zod8.z.string().min(1),
|
|
15463
15597
|
strength: import_zod8.z.number().min(0).max(1).optional()
|
|
15464
15598
|
}).strict(),
|
|
15599
|
+
import_zod8.z.object({
|
|
15600
|
+
mode: import_zod8.z.literal("reduce-colors"),
|
|
15601
|
+
from: import_zod8.z.string().min(1),
|
|
15602
|
+
/** A target colour count; the gallery leaves PixelLab's default when this is omitted. */
|
|
15603
|
+
numColors: import_zod8.z.number().int().min(2).max(256).optional(),
|
|
15604
|
+
dithering: RevisionDitheringSchema.optional(),
|
|
15605
|
+
ditheringStrength: import_zod8.z.number().min(0).max(10).optional()
|
|
15606
|
+
}).strict(),
|
|
15607
|
+
import_zod8.z.object({
|
|
15608
|
+
mode: import_zod8.z.literal("inpaint"),
|
|
15609
|
+
from: import_zod8.z.string().min(1),
|
|
15610
|
+
/** Black-and-white PNG the same size as the parent; white is repainted. */
|
|
15611
|
+
mask: ProjectFileSchema
|
|
15612
|
+
}).strict(),
|
|
15613
|
+
import_zod8.z.object({
|
|
15614
|
+
mode: import_zod8.z.literal("interpolate"),
|
|
15615
|
+
from: import_zod8.z.string().min(1),
|
|
15616
|
+
/** The ending keyframe: an image already in the project, the parent's size. */
|
|
15617
|
+
lastFrame: ProjectFileSchema,
|
|
15618
|
+
fps: import_zod8.z.number().int().min(1).max(60).optional()
|
|
15619
|
+
}).strict(),
|
|
15620
|
+
import_zod8.z.object({ mode: import_zod8.z.literal("correct-pixelart"), from: import_zod8.z.string().min(1), strength: import_zod8.z.number().min(0).max(1).optional() }).strict(),
|
|
15621
|
+
import_zod8.z.object({
|
|
15622
|
+
mode: import_zod8.z.literal("remove-background"),
|
|
15623
|
+
from: import_zod8.z.string().min(1),
|
|
15624
|
+
removalTask: import_zod8.z.enum(["simple", "complex"]).optional(),
|
|
15625
|
+
/** A short description of the foreground, sent as PixelLab's text hint. */
|
|
15626
|
+
description: import_zod8.z.string().min(1).optional()
|
|
15627
|
+
}).strict(),
|
|
15628
|
+
import_zod8.z.object({
|
|
15629
|
+
mode: import_zod8.z.enum(["animate", "animate-pixminimax"]),
|
|
15630
|
+
from: import_zod8.z.string().min(1),
|
|
15631
|
+
frames: import_zod8.z.number().int().min(4).max(40).refine((n) => n % 2 === 0, "frames must be even").optional(),
|
|
15632
|
+
fps: import_zod8.z.number().int().min(1).max(60).optional(),
|
|
15633
|
+
enhancePrompt: import_zod8.z.boolean().optional(),
|
|
15634
|
+
driftThreshold: import_zod8.z.number().min(0).optional(),
|
|
15635
|
+
/** `animate-pixminimax` only; the loader refuses these on `animate`. */
|
|
15636
|
+
direction: CharacterDirectionSchema.optional(),
|
|
15637
|
+
subjectDescription: import_zod8.z.string().min(1).max(300).optional(),
|
|
15638
|
+
initialPose: import_zod8.z.string().min(1).max(300).optional()
|
|
15639
|
+
}).strict(),
|
|
15640
|
+
import_zod8.z.object({
|
|
15641
|
+
mode: import_zod8.z.literal("edit-animation"),
|
|
15642
|
+
from: import_zod8.z.string().min(1),
|
|
15643
|
+
fps: import_zod8.z.number().int().min(1).max(60).optional()
|
|
15644
|
+
}).strict(),
|
|
15465
15645
|
import_zod8.z.object({
|
|
15466
15646
|
mode: import_zod8.z.literal("animate-skeleton"),
|
|
15467
15647
|
from: import_zod8.z.string().min(1),
|
|
@@ -16106,62 +16286,118 @@ async function saveProjectImage(root, request) {
|
|
|
16106
16286
|
return relative.split(import_node_path29.default.sep).join("/");
|
|
16107
16287
|
}
|
|
16108
16288
|
|
|
16109
|
-
// src/gallery/
|
|
16289
|
+
// src/gallery/image-revision.ts
|
|
16110
16290
|
var import_node_fs26 = require("fs");
|
|
16111
16291
|
var import_promises23 = require("fs/promises");
|
|
16112
16292
|
var import_node_path30 = __toESM(require("path"), 1);
|
|
16113
16293
|
var import_zod11 = require("zod");
|
|
16114
|
-
var
|
|
16115
|
-
|
|
16116
|
-
styleId: import_zod11.z.string().min(1),
|
|
16117
|
-
assetId: import_zod11.z.string().min(1),
|
|
16118
|
-
frames: import_zod11.z.number().int().min(3).max(15).optional()
|
|
16119
|
-
}).strict();
|
|
16120
|
-
var SkeletonAnimationRequestSchema = import_zod11.z.object({
|
|
16121
|
-
action: import_zod11.z.literal("create-skeleton-animation"),
|
|
16294
|
+
var InpaintRevisionRequestSchema = import_zod11.z.object({
|
|
16295
|
+
action: import_zod11.z.literal("create-inpaint-revision"),
|
|
16122
16296
|
project: import_zod11.z.string().min(1).optional(),
|
|
16123
16297
|
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16124
16298
|
styleId: import_zod11.z.string().min(1),
|
|
16125
|
-
/** The sprite
|
|
16299
|
+
/** The sprite being repainted. */
|
|
16126
16300
|
from: import_zod11.z.string().min(1),
|
|
16127
16301
|
assetId: import_zod11.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
16128
|
-
/**
|
|
16302
|
+
/** What to paint into the masked area. */
|
|
16129
16303
|
prompt: import_zod11.z.string().min(1),
|
|
16304
|
+
/** Manifest-relative `.png` destination for the mask. */
|
|
16305
|
+
maskPath: import_zod11.z.string().min(1),
|
|
16306
|
+
maskBase64: import_zod11.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/),
|
|
16307
|
+
/** Replace a file already at `maskPath`. */
|
|
16308
|
+
overwrite: import_zod11.z.boolean().optional()
|
|
16309
|
+
}).strict();
|
|
16310
|
+
async function createInpaintRevision(manifestPath, request) {
|
|
16311
|
+
const root = import_node_path30.default.dirname(import_node_path30.default.resolve(manifestPath));
|
|
16312
|
+
if (import_node_path30.default.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
16313
|
+
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
16314
|
+
}
|
|
16315
|
+
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
16316
|
+
const metadata = imageMetadata(bytes);
|
|
16317
|
+
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
16318
|
+
const file = import_node_path30.default.resolve(root, request.maskPath);
|
|
16319
|
+
const previous = (0, import_node_fs26.existsSync)(file) ? await (0, import_promises23.readFile)(file) : null;
|
|
16320
|
+
const mask = await saveProjectImage(root, {
|
|
16321
|
+
action: "upload-image",
|
|
16322
|
+
path: request.maskPath,
|
|
16323
|
+
base64: request.maskBase64,
|
|
16324
|
+
...request.overwrite ? { overwrite: true } : {}
|
|
16325
|
+
});
|
|
16326
|
+
try {
|
|
16327
|
+
await applyManifestEdit(manifestPath, {
|
|
16328
|
+
action: "add-asset",
|
|
16329
|
+
assetId: request.assetId,
|
|
16330
|
+
expectedSha256: request.expectedSha256,
|
|
16331
|
+
asset: {
|
|
16332
|
+
prompt: request.prompt,
|
|
16333
|
+
styles: [request.styleId],
|
|
16334
|
+
revision: { mode: "inpaint", from: request.from, mask }
|
|
16335
|
+
}
|
|
16336
|
+
});
|
|
16337
|
+
} catch (error) {
|
|
16338
|
+
if (previous) await (0, import_promises23.writeFile)(file, previous);
|
|
16339
|
+
else await (0, import_promises23.rm)(file, { force: true });
|
|
16340
|
+
throw error;
|
|
16341
|
+
}
|
|
16342
|
+
return mask;
|
|
16343
|
+
}
|
|
16344
|
+
|
|
16345
|
+
// src/gallery/skeleton.ts
|
|
16346
|
+
var import_node_fs27 = require("fs");
|
|
16347
|
+
var import_promises24 = require("fs/promises");
|
|
16348
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
16349
|
+
var import_zod12 = require("zod");
|
|
16350
|
+
var SkeletonEstimateRequestSchema = import_zod12.z.object({
|
|
16351
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16352
|
+
styleId: import_zod12.z.string().min(1),
|
|
16353
|
+
assetId: import_zod12.z.string().min(1),
|
|
16354
|
+
frames: import_zod12.z.number().int().min(3).max(15).optional()
|
|
16355
|
+
}).strict();
|
|
16356
|
+
var SkeletonAnimationRequestSchema = import_zod12.z.object({
|
|
16357
|
+
action: import_zod12.z.literal("create-skeleton-animation"),
|
|
16358
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16359
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16360
|
+
styleId: import_zod12.z.string().min(1),
|
|
16361
|
+
/** The sprite the animation poses. */
|
|
16362
|
+
from: import_zod12.z.string().min(1),
|
|
16363
|
+
assetId: import_zod12.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
16364
|
+
/** The motion, sent as PixelLab's `action`. */
|
|
16365
|
+
prompt: import_zod12.z.string().min(1),
|
|
16130
16366
|
direction: CharacterDirectionSchema,
|
|
16131
|
-
description:
|
|
16132
|
-
skeletonTemplate:
|
|
16367
|
+
description: import_zod12.z.string().min(1).optional(),
|
|
16368
|
+
skeletonTemplate: import_zod12.z.string().min(1).optional(),
|
|
16133
16369
|
/** Manifest-relative JSON path inside the project. */
|
|
16134
|
-
keypointsFile:
|
|
16370
|
+
keypointsFile: import_zod12.z.string().min(1),
|
|
16135
16371
|
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
16136
16372
|
set: SkeletonSetSchema.optional(),
|
|
16137
16373
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
16138
|
-
overwrite:
|
|
16374
|
+
overwrite: import_zod12.z.boolean().optional()
|
|
16139
16375
|
}).strict();
|
|
16140
|
-
var SkeletonKeypointsUpdateSchema =
|
|
16141
|
-
action:
|
|
16142
|
-
project:
|
|
16143
|
-
styleId:
|
|
16144
|
-
assetId:
|
|
16376
|
+
var SkeletonKeypointsUpdateSchema = import_zod12.z.object({
|
|
16377
|
+
action: import_zod12.z.literal("update-skeleton-keypoints"),
|
|
16378
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16379
|
+
styleId: import_zod12.z.string().min(1),
|
|
16380
|
+
assetId: import_zod12.z.string().min(1),
|
|
16145
16381
|
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
16146
|
-
expectedSha256:
|
|
16382
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16147
16383
|
set: SkeletonSetSchema
|
|
16148
16384
|
}).strict();
|
|
16149
16385
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
16150
|
-
const root =
|
|
16151
|
-
const file =
|
|
16152
|
-
const relative =
|
|
16153
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
16386
|
+
const root = import_node_path31.default.dirname(import_node_path31.default.resolve(manifestPath));
|
|
16387
|
+
const file = import_node_path31.default.resolve(root, request.keypointsFile);
|
|
16388
|
+
const relative = import_node_path31.default.relative(root, file);
|
|
16389
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || import_node_path31.default.isAbsolute(relative)) {
|
|
16154
16390
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
16155
16391
|
}
|
|
16156
|
-
const existed = (0,
|
|
16392
|
+
const existed = (0, import_node_fs27.existsSync)(file);
|
|
16157
16393
|
let previous = null;
|
|
16158
16394
|
if (request.set) {
|
|
16159
16395
|
if (existed && !request.overwrite) {
|
|
16160
16396
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
16161
16397
|
}
|
|
16162
|
-
if (existed) previous = await (0,
|
|
16163
|
-
await (0,
|
|
16164
|
-
await (0,
|
|
16398
|
+
if (existed) previous = await (0, import_promises24.readFile)(file);
|
|
16399
|
+
await (0, import_promises24.mkdir)(import_node_path31.default.dirname(file), { recursive: true });
|
|
16400
|
+
await (0, import_promises24.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16165
16401
|
} else if (!existed) {
|
|
16166
16402
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
16167
16403
|
}
|
|
@@ -16176,7 +16412,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16176
16412
|
revision: {
|
|
16177
16413
|
mode: "animate-skeleton",
|
|
16178
16414
|
from: request.from,
|
|
16179
|
-
keypointsFile: relative.split(
|
|
16415
|
+
keypointsFile: relative.split(import_node_path31.default.sep).join("/"),
|
|
16180
16416
|
direction: request.direction,
|
|
16181
16417
|
...request.description ? { description: request.description } : {},
|
|
16182
16418
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16185,8 +16421,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16185
16421
|
});
|
|
16186
16422
|
} catch (error) {
|
|
16187
16423
|
if (request.set) {
|
|
16188
|
-
if (previous) await (0,
|
|
16189
|
-
else await (0,
|
|
16424
|
+
if (previous) await (0, import_promises24.writeFile)(file, previous);
|
|
16425
|
+
else await (0, import_promises24.rm)(file, { force: true });
|
|
16190
16426
|
}
|
|
16191
16427
|
throw error;
|
|
16192
16428
|
}
|
|
@@ -16197,64 +16433,64 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16197
16433
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16198
16434
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16199
16435
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16200
|
-
const relative =
|
|
16201
|
-
if (relative.startsWith("..") ||
|
|
16436
|
+
const relative = import_node_path31.default.relative(ctx.loaded.root, file);
|
|
16437
|
+
if (relative.startsWith("..") || import_node_path31.default.isAbsolute(relative)) {
|
|
16202
16438
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16203
16439
|
}
|
|
16204
|
-
const current = (0,
|
|
16440
|
+
const current = (0, import_node_fs27.existsSync)(file) ? sha256(await (0, import_promises24.readFile)(file)) : null;
|
|
16205
16441
|
if (current !== request.expectedSha256) {
|
|
16206
16442
|
throw Object.assign(
|
|
16207
|
-
new Error(`${relative.split(
|
|
16443
|
+
new Error(`${relative.split(import_node_path31.default.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
16208
16444
|
{ status: 409 }
|
|
16209
16445
|
);
|
|
16210
16446
|
}
|
|
16211
|
-
await (0,
|
|
16212
|
-
return relative.split(
|
|
16447
|
+
await (0, import_promises24.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16448
|
+
return relative.split(import_node_path31.default.sep).join("/");
|
|
16213
16449
|
}
|
|
16214
16450
|
|
|
16215
16451
|
// src/gallery/edit.ts
|
|
16216
|
-
var Base64 =
|
|
16217
|
-
var HandEditRequestSchema =
|
|
16218
|
-
|
|
16219
|
-
action:
|
|
16220
|
-
project:
|
|
16221
|
-
assetId:
|
|
16222
|
-
styleId:
|
|
16223
|
-
expectedSha256:
|
|
16452
|
+
var Base64 = import_zod13.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
16453
|
+
var HandEditRequestSchema = import_zod13.z.discriminatedUnion("action", [
|
|
16454
|
+
import_zod13.z.object({
|
|
16455
|
+
action: import_zod13.z.literal("start-edit"),
|
|
16456
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16457
|
+
assetId: import_zod13.z.string().min(1),
|
|
16458
|
+
styleId: import_zod13.z.string().min(1),
|
|
16459
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16224
16460
|
/** Also launch the author's editor on the file. */
|
|
16225
|
-
open:
|
|
16461
|
+
open: import_zod13.z.boolean().optional()
|
|
16226
16462
|
}).strict(),
|
|
16227
|
-
|
|
16228
|
-
action:
|
|
16229
|
-
project:
|
|
16230
|
-
assetId:
|
|
16231
|
-
styleId:
|
|
16232
|
-
expectedSha256:
|
|
16463
|
+
import_zod13.z.object({
|
|
16464
|
+
action: import_zod13.z.literal("detach-edit"),
|
|
16465
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16466
|
+
assetId: import_zod13.z.string().min(1),
|
|
16467
|
+
styleId: import_zod13.z.string().min(1),
|
|
16468
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/)
|
|
16233
16469
|
}).strict(),
|
|
16234
16470
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
16235
|
-
|
|
16236
|
-
action:
|
|
16237
|
-
project:
|
|
16238
|
-
assetId:
|
|
16239
|
-
styleId:
|
|
16240
|
-
expectedSha256:
|
|
16471
|
+
import_zod13.z.object({
|
|
16472
|
+
action: import_zod13.z.literal("save-edit"),
|
|
16473
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16474
|
+
assetId: import_zod13.z.string().min(1),
|
|
16475
|
+
styleId: import_zod13.z.string().min(1),
|
|
16476
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16241
16477
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
16242
16478
|
png: Base64.min(1).optional(),
|
|
16243
16479
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
16244
|
-
frames:
|
|
16480
|
+
frames: import_zod13.z.array(import_zod13.z.object({ role: import_zod13.z.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
16245
16481
|
/** The editor's layered project file, kept beside the edit. */
|
|
16246
16482
|
pxo: Base64.optional(),
|
|
16247
16483
|
/** Editor identity from the bridge's ready message. */
|
|
16248
|
-
editor:
|
|
16249
|
-
protocol:
|
|
16484
|
+
editor: import_zod13.z.string().min(1).max(200),
|
|
16485
|
+
protocol: import_zod13.z.number().int().positive()
|
|
16250
16486
|
}).strict()
|
|
16251
16487
|
]);
|
|
16252
16488
|
var UNDO_DEPTH = 20;
|
|
16253
|
-
var UndoRequestSchema =
|
|
16254
|
-
action:
|
|
16255
|
-
project:
|
|
16489
|
+
var UndoRequestSchema = import_zod13.z.object({
|
|
16490
|
+
action: import_zod13.z.literal("undo-edit"),
|
|
16491
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16256
16492
|
/** The manifest as the page last saw it; undo refuses a manifest someone else has changed since. */
|
|
16257
|
-
expectedSha256:
|
|
16493
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/)
|
|
16258
16494
|
}).strict();
|
|
16259
16495
|
function createGalleryEditHandler(opts) {
|
|
16260
16496
|
const log = opts.onProgress ?? (() => {
|
|
@@ -16263,7 +16499,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16263
16499
|
const history = /* @__PURE__ */ new Map();
|
|
16264
16500
|
const manifestOf = async (project) => {
|
|
16265
16501
|
try {
|
|
16266
|
-
return
|
|
16502
|
+
return import_node_path32.default.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
16267
16503
|
} catch {
|
|
16268
16504
|
return null;
|
|
16269
16505
|
}
|
|
@@ -16273,7 +16509,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16273
16509
|
if (undo.success) {
|
|
16274
16510
|
const manifestPath2 = await manifestOf(undo.data.project);
|
|
16275
16511
|
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
16276
|
-
const current = await (0,
|
|
16512
|
+
const current = await (0, import_promises25.readFile)(manifestPath2, "utf8");
|
|
16277
16513
|
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
16278
16514
|
const stack = history.get(manifestPath2) ?? [];
|
|
16279
16515
|
const last = stack[stack.length - 1];
|
|
@@ -16283,17 +16519,17 @@ function createGalleryEditHandler(opts) {
|
|
|
16283
16519
|
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
16284
16520
|
}
|
|
16285
16521
|
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
16286
|
-
await (0,
|
|
16287
|
-
await (0,
|
|
16522
|
+
await (0, import_promises25.writeFile)(tmp, last.before);
|
|
16523
|
+
await (0, import_promises25.rename)(tmp, manifestPath2);
|
|
16288
16524
|
stack.pop();
|
|
16289
|
-
log(` manifest edit undone in ${
|
|
16525
|
+
log(` manifest edit undone in ${import_node_path32.default.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
16290
16526
|
return opts.reload();
|
|
16291
16527
|
}
|
|
16292
16528
|
const manifestPath = await manifestOf(body?.project);
|
|
16293
|
-
const before = manifestPath ? await (0,
|
|
16529
|
+
const before = manifestPath ? await (0, import_promises25.readFile)(manifestPath, "utf8").catch(() => null) : null;
|
|
16294
16530
|
const build = await apply(body);
|
|
16295
16531
|
if (manifestPath && before !== null) {
|
|
16296
|
-
const after = await (0,
|
|
16532
|
+
const after = await (0, import_promises25.readFile)(manifestPath, "utf8").catch(() => null);
|
|
16297
16533
|
if (after !== null && after !== before) {
|
|
16298
16534
|
const stack = history.get(manifestPath) ?? [];
|
|
16299
16535
|
stack.push({ before, afterSha: sha256(after) });
|
|
@@ -16348,7 +16584,7 @@ function createEditApplier(opts, log) {
|
|
|
16348
16584
|
} catch (error) {
|
|
16349
16585
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16350
16586
|
}
|
|
16351
|
-
const written = await saveProjectImage(
|
|
16587
|
+
const written = await saveProjectImage(import_node_path32.default.dirname(import_node_path32.default.resolve(manifestPath2)), upload.data);
|
|
16352
16588
|
log(` image added: ${written}`);
|
|
16353
16589
|
return opts.reload();
|
|
16354
16590
|
}
|
|
@@ -16384,6 +16620,23 @@ function createEditApplier(opts, log) {
|
|
|
16384
16620
|
"invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16385
16621
|
);
|
|
16386
16622
|
}
|
|
16623
|
+
const inpaint = InpaintRevisionRequestSchema.safeParse(body);
|
|
16624
|
+
if (inpaint.success) {
|
|
16625
|
+
let manifestPath2;
|
|
16626
|
+
try {
|
|
16627
|
+
manifestPath2 = await opts.manifestFor(inpaint.data.project);
|
|
16628
|
+
} catch (error) {
|
|
16629
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16630
|
+
}
|
|
16631
|
+
const mask = await createInpaintRevision(manifestPath2, inpaint.data);
|
|
16632
|
+
log(` manifest edited: added inpaint revision ${inpaint.data.assetId} (mask ${mask})`);
|
|
16633
|
+
return opts.reload();
|
|
16634
|
+
}
|
|
16635
|
+
if (body?.action === "create-inpaint-revision") {
|
|
16636
|
+
throw new ManifestEditError(
|
|
16637
|
+
"invalid inpaint revision: " + inpaint.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16638
|
+
);
|
|
16639
|
+
}
|
|
16387
16640
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
16388
16641
|
if (!parsed.success) {
|
|
16389
16642
|
throw new ManifestEditError(
|
|
@@ -16398,7 +16651,7 @@ function createEditApplier(opts, log) {
|
|
|
16398
16651
|
}
|
|
16399
16652
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16400
16653
|
if (result.changed) {
|
|
16401
|
-
const relative =
|
|
16654
|
+
const relative = import_node_path32.default.relative(process.cwd(), result.manifestPath);
|
|
16402
16655
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16403
16656
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16404
16657
|
}
|
|
@@ -16426,7 +16679,7 @@ function describeEdit(edit) {
|
|
|
16426
16679
|
// src/gallery/snapshot.ts
|
|
16427
16680
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16428
16681
|
function galleryMediaId(absolutePath) {
|
|
16429
|
-
return (0, import_node_crypto6.createHash)("sha256").update(
|
|
16682
|
+
return (0, import_node_crypto6.createHash)("sha256").update(import_node_path33.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16430
16683
|
}
|
|
16431
16684
|
function galleryMediaRoute(id) {
|
|
16432
16685
|
return `/media/${id}`;
|
|
@@ -16445,9 +16698,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16445
16698
|
return (entry.history ?? []).map((generation, i) => {
|
|
16446
16699
|
const outputs = generation.outputs.map((output) => {
|
|
16447
16700
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16448
|
-
const file =
|
|
16701
|
+
const file = import_node_path33.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16449
16702
|
let url = null;
|
|
16450
|
-
if ((0,
|
|
16703
|
+
if ((0, import_node_fs28.existsSync)(file)) {
|
|
16451
16704
|
const id = galleryMediaId(file);
|
|
16452
16705
|
media.set(id, { path: file, contentType: mediaType });
|
|
16453
16706
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16479,7 +16732,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16479
16732
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16480
16733
|
async function samePixels(a, b) {
|
|
16481
16734
|
try {
|
|
16482
|
-
const [left, right] = await Promise.all([(0,
|
|
16735
|
+
const [left, right] = await Promise.all([(0, import_promises26.readFile)(a), (0, import_promises26.readFile)(b)]);
|
|
16483
16736
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16484
16737
|
const x = decodePng(left);
|
|
16485
16738
|
const y = decodePng(right);
|
|
@@ -16545,7 +16798,7 @@ function describeCharacter(spec, entry) {
|
|
|
16545
16798
|
}
|
|
16546
16799
|
async function fileInfo(absolutePath) {
|
|
16547
16800
|
try {
|
|
16548
|
-
const info = await (0,
|
|
16801
|
+
const info = await (0, import_promises26.stat)(absolutePath);
|
|
16549
16802
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
16550
16803
|
} catch {
|
|
16551
16804
|
return null;
|
|
@@ -16577,10 +16830,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16577
16830
|
async function describeQuality(media, root, inspection) {
|
|
16578
16831
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16579
16832
|
const outputs = await Promise.all(
|
|
16580
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16833
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path33.default.resolve(root, file)))
|
|
16581
16834
|
);
|
|
16582
|
-
const recordPath =
|
|
16583
|
-
const recordExists = (0,
|
|
16835
|
+
const recordPath = import_node_path33.default.resolve(root, inspection.record);
|
|
16836
|
+
const recordExists = (0, import_node_fs28.existsSync)(recordPath);
|
|
16584
16837
|
let options = null;
|
|
16585
16838
|
let check = null;
|
|
16586
16839
|
if (recordExists) {
|
|
@@ -16599,7 +16852,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16599
16852
|
return {
|
|
16600
16853
|
state: inspection.state,
|
|
16601
16854
|
reason: inspection.reason,
|
|
16602
|
-
output: portableOutputPath(
|
|
16855
|
+
output: portableOutputPath(import_node_path33.default.resolve(root, inspection.output), root),
|
|
16603
16856
|
record: portableOutputPath(recordPath, root),
|
|
16604
16857
|
recordExists,
|
|
16605
16858
|
outputs,
|
|
@@ -16637,9 +16890,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
16637
16890
|
const media = /* @__PURE__ */ new Map();
|
|
16638
16891
|
const plan = await buildPlan(specs, lock);
|
|
16639
16892
|
const items = [];
|
|
16640
|
-
const manifestText = await (0,
|
|
16893
|
+
const manifestText = await (0, import_promises26.readFile)(loaded.path, "utf8");
|
|
16641
16894
|
const manifestSha256 = sha256(manifestText);
|
|
16642
|
-
const cacheDir =
|
|
16895
|
+
const cacheDir = import_node_path33.default.resolve(import_node_path33.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16643
16896
|
let rawStyles = {};
|
|
16644
16897
|
try {
|
|
16645
16898
|
const raw = JSON.parse(manifestText);
|
|
@@ -16663,12 +16916,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
16663
16916
|
)
|
|
16664
16917
|
);
|
|
16665
16918
|
if (spec.source) {
|
|
16666
|
-
const editPath =
|
|
16919
|
+
const editPath = import_node_path33.default.resolve(root, spec.source);
|
|
16667
16920
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16668
16921
|
const editShas = await Promise.all(
|
|
16669
16922
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
16670
16923
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
16671
|
-
return { file, sha256: (0,
|
|
16924
|
+
return { file, sha256: (0, import_node_fs28.existsSync)(file) ? await sha256File(file) : null };
|
|
16672
16925
|
})
|
|
16673
16926
|
);
|
|
16674
16927
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -16676,7 +16929,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16676
16929
|
const companion = await readHandEditCompanion(editPath);
|
|
16677
16930
|
if (companion) {
|
|
16678
16931
|
const projectPath = handEditProjectPath(editPath);
|
|
16679
|
-
const project = companion.project && (0,
|
|
16932
|
+
const project = companion.project && (0, import_node_fs28.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
16680
16933
|
let projectUrl = null;
|
|
16681
16934
|
if (project) {
|
|
16682
16935
|
const id = galleryMediaId(projectPath);
|
|
@@ -16712,8 +16965,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16712
16965
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16713
16966
|
}
|
|
16714
16967
|
} else if (spec.source) {
|
|
16715
|
-
outputs = [await describeOutput(media, root,
|
|
16716
|
-
} else if (!entry && (0,
|
|
16968
|
+
outputs = [await describeOutput(media, root, import_node_path33.default.resolve(root, spec.source))];
|
|
16969
|
+
} else if (!entry && (0, import_node_fs28.existsSync)(spec.outFile)) {
|
|
16717
16970
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16718
16971
|
} else {
|
|
16719
16972
|
outputs = [];
|
|
@@ -16916,7 +17169,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16916
17169
|
id: loaded.manifest.name,
|
|
16917
17170
|
name: loaded.manifest.name,
|
|
16918
17171
|
manifest: loaded.path,
|
|
16919
|
-
lock:
|
|
17172
|
+
lock: import_node_path33.default.resolve(opts.lockPath),
|
|
16920
17173
|
root,
|
|
16921
17174
|
provider: loaded.manifest.provider,
|
|
16922
17175
|
account: null,
|
|
@@ -16942,7 +17195,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16942
17195
|
return { snapshot, media };
|
|
16943
17196
|
}
|
|
16944
17197
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16945
|
-
const dir =
|
|
17198
|
+
const dir = import_node_path33.default.dirname(import_node_path33.default.resolve(opts.workspacePath));
|
|
16946
17199
|
const media = /* @__PURE__ */ new Map();
|
|
16947
17200
|
const projects = [];
|
|
16948
17201
|
const items = [];
|
|
@@ -16985,11 +17238,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16985
17238
|
id: project.id,
|
|
16986
17239
|
name: loaded.manifest.name,
|
|
16987
17240
|
manifest: manifestPath,
|
|
16988
|
-
lock:
|
|
17241
|
+
lock: import_node_path33.default.resolve(lockPath),
|
|
16989
17242
|
root: loaded.root,
|
|
16990
17243
|
provider: loaded.manifest.provider,
|
|
16991
17244
|
account: project.account ?? null,
|
|
16992
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
17245
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises26.readFile)(manifestPath, "utf8")),
|
|
16993
17246
|
historyLimit: historyLimit(loaded.manifest),
|
|
16994
17247
|
entries: Object.keys(lock.entries).length,
|
|
16995
17248
|
items: projectItems.length,
|
|
@@ -17001,8 +17254,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17001
17254
|
id: project.id,
|
|
17002
17255
|
name: project.id,
|
|
17003
17256
|
manifest: manifestPath,
|
|
17004
|
-
lock:
|
|
17005
|
-
root:
|
|
17257
|
+
lock: import_node_path33.default.resolve(lockPath),
|
|
17258
|
+
root: import_node_path33.default.dirname(manifestPath),
|
|
17006
17259
|
provider: project.provider,
|
|
17007
17260
|
account: project.account ?? null,
|
|
17008
17261
|
manifestSha256: null,
|
|
@@ -17029,7 +17282,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17029
17282
|
version: 1,
|
|
17030
17283
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
17031
17284
|
project: null,
|
|
17032
|
-
workspace: { path:
|
|
17285
|
+
workspace: { path: import_node_path33.default.resolve(opts.workspacePath), dir, projects },
|
|
17033
17286
|
filter,
|
|
17034
17287
|
totals: {
|
|
17035
17288
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -17046,7 +17299,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17046
17299
|
}
|
|
17047
17300
|
|
|
17048
17301
|
// src/gallery/page.ts
|
|
17049
|
-
var
|
|
17302
|
+
var import_node_fs29 = require("fs");
|
|
17050
17303
|
|
|
17051
17304
|
// src/gallery/client-bundle.ts
|
|
17052
17305
|
var import_node_module = require("module");
|
|
@@ -17086,8 +17339,8 @@ var client;
|
|
|
17086
17339
|
function clientAssets() {
|
|
17087
17340
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
17088
17341
|
client ??= {
|
|
17089
|
-
css: (0,
|
|
17090
|
-
js: (0,
|
|
17342
|
+
css: (0, import_node_fs29.readFileSync)(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
17343
|
+
js: (0, import_node_fs29.existsSync)(prebuilt) ? (0, import_node_fs29.readFileSync)(prebuilt, "utf8") : bundleGalleryClient()
|
|
17091
17344
|
};
|
|
17092
17345
|
return client;
|
|
17093
17346
|
}
|
|
@@ -17230,10 +17483,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
17230
17483
|
|
|
17231
17484
|
// src/editor/install.ts
|
|
17232
17485
|
var import_node_crypto7 = require("crypto");
|
|
17233
|
-
var
|
|
17234
|
-
var
|
|
17486
|
+
var import_node_fs30 = require("fs");
|
|
17487
|
+
var import_promises27 = require("fs/promises");
|
|
17235
17488
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
17236
|
-
var
|
|
17489
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
17237
17490
|
var EditorInstallError = class extends Error {
|
|
17238
17491
|
constructor(message6, file) {
|
|
17239
17492
|
super(message6);
|
|
@@ -17244,15 +17497,15 @@ var EditorInstallError = class extends Error {
|
|
|
17244
17497
|
};
|
|
17245
17498
|
function editorToolsRoot() {
|
|
17246
17499
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17247
|
-
if (override) return
|
|
17248
|
-
if (process.platform === "darwin") return
|
|
17500
|
+
if (override) return import_node_path34.default.resolve(override);
|
|
17501
|
+
if (process.platform === "darwin") return import_node_path34.default.join(import_node_os3.default.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
17249
17502
|
if (process.platform === "win32") {
|
|
17250
|
-
return
|
|
17503
|
+
return import_node_path34.default.join(process.env.LOCALAPPDATA ?? import_node_path34.default.join(import_node_os3.default.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
17251
17504
|
}
|
|
17252
|
-
return
|
|
17505
|
+
return import_node_path34.default.join(process.env.XDG_CACHE_HOME ?? import_node_path34.default.join(import_node_os3.default.homedir(), ".cache"), "pixelkiln", "tools");
|
|
17253
17506
|
}
|
|
17254
17507
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17255
|
-
return
|
|
17508
|
+
return import_node_path34.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17256
17509
|
}
|
|
17257
17510
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17258
17511
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17262,7 +17515,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
17262
17515
|
}
|
|
17263
17516
|
async function sha256Of(file) {
|
|
17264
17517
|
try {
|
|
17265
|
-
return (0, import_node_crypto7.createHash)("sha256").update(await (0,
|
|
17518
|
+
return (0, import_node_crypto7.createHash)("sha256").update(await (0, import_promises27.readFile)(file)).digest("hex");
|
|
17266
17519
|
} catch {
|
|
17267
17520
|
return null;
|
|
17268
17521
|
}
|
|
@@ -17275,8 +17528,8 @@ async function editorStatus(opts = {}) {
|
|
|
17275
17528
|
let totalBytes = 0;
|
|
17276
17529
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17277
17530
|
totalBytes += expected.bytes;
|
|
17278
|
-
const file =
|
|
17279
|
-
const info = (0,
|
|
17531
|
+
const file = import_node_path34.default.join(dir, name);
|
|
17532
|
+
const info = (0, import_node_fs30.existsSync)(file) ? await (0, import_promises27.stat)(file).catch(() => null) : null;
|
|
17280
17533
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17281
17534
|
installedBytes += expected.bytes;
|
|
17282
17535
|
} else {
|
|
@@ -17305,7 +17558,7 @@ async function installEditor(opts = {}) {
|
|
|
17305
17558
|
);
|
|
17306
17559
|
}
|
|
17307
17560
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
17308
|
-
await (0,
|
|
17561
|
+
await (0, import_promises27.mkdir)(dir, { recursive: true });
|
|
17309
17562
|
const downloaded = [];
|
|
17310
17563
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
17311
17564
|
let fetchedBytes = before.installedBytes;
|
|
@@ -17333,13 +17586,13 @@ async function installEditor(opts = {}) {
|
|
|
17333
17586
|
if (digest2 !== expected.sha256) {
|
|
17334
17587
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17335
17588
|
}
|
|
17336
|
-
const final =
|
|
17589
|
+
const final = import_node_path34.default.join(dir, name);
|
|
17337
17590
|
const tmp = `${final}.${process.pid}.part`;
|
|
17338
17591
|
try {
|
|
17339
|
-
await (0,
|
|
17340
|
-
await (0,
|
|
17592
|
+
await (0, import_promises27.writeFile)(tmp, bytes);
|
|
17593
|
+
await (0, import_promises27.rename)(tmp, final);
|
|
17341
17594
|
} finally {
|
|
17342
|
-
await (0,
|
|
17595
|
+
await (0, import_promises27.rm)(tmp, { force: true });
|
|
17343
17596
|
}
|
|
17344
17597
|
fetchedBytes += bytes.length;
|
|
17345
17598
|
downloaded.push(name);
|
|
@@ -17379,12 +17632,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17379
17632
|
".json": "application/json; charset=utf-8"
|
|
17380
17633
|
};
|
|
17381
17634
|
function editorContentType(name) {
|
|
17382
|
-
return EDITOR_CONTENT_TYPES[
|
|
17635
|
+
return EDITOR_CONTENT_TYPES[import_node_path34.default.extname(name).toLowerCase()] ?? null;
|
|
17383
17636
|
}
|
|
17384
17637
|
|
|
17385
17638
|
// src/gallery/editor.ts
|
|
17386
|
-
var
|
|
17387
|
-
var
|
|
17639
|
+
var import_promises28 = require("fs/promises");
|
|
17640
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
17388
17641
|
function editorRoute(release, name = "index.html") {
|
|
17389
17642
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17390
17643
|
}
|
|
@@ -17457,7 +17710,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17457
17710
|
if (!status.installed) return null;
|
|
17458
17711
|
let bytes;
|
|
17459
17712
|
try {
|
|
17460
|
-
bytes = await (0,
|
|
17713
|
+
bytes = await (0, import_promises28.readFile)(import_node_path35.default.join(dir, name));
|
|
17461
17714
|
} catch {
|
|
17462
17715
|
verified = null;
|
|
17463
17716
|
return null;
|
|
@@ -17474,7 +17727,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17474
17727
|
// src/gallery/server.ts
|
|
17475
17728
|
var import_node_crypto8 = require("crypto");
|
|
17476
17729
|
var import_node_http2 = require("http");
|
|
17477
|
-
var
|
|
17730
|
+
var import_promises29 = require("fs/promises");
|
|
17478
17731
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17479
17732
|
var EDITOR_CSP = [
|
|
17480
17733
|
"default-src 'self'",
|
|
@@ -17655,7 +17908,7 @@ async function serveGallery(opts) {
|
|
|
17655
17908
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17656
17909
|
if (!asset) return fail(404, "no such review asset");
|
|
17657
17910
|
try {
|
|
17658
|
-
const bytes = await (0,
|
|
17911
|
+
const bytes = await (0, import_promises29.readFile)(asset.path);
|
|
17659
17912
|
res.writeHead(200, {
|
|
17660
17913
|
"Content-Type": asset.contentType,
|
|
17661
17914
|
"Cache-Control": "no-store",
|
|
@@ -17738,7 +17991,7 @@ async function serveGallery(opts) {
|
|
|
17738
17991
|
if (!asset) return fail(404, "no such gallery media");
|
|
17739
17992
|
let bytes;
|
|
17740
17993
|
try {
|
|
17741
|
-
bytes = await (0,
|
|
17994
|
+
bytes = await (0, import_promises29.readFile)(asset.path);
|
|
17742
17995
|
} catch {
|
|
17743
17996
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17744
17997
|
}
|
|
@@ -17782,27 +18035,27 @@ async function serveGallery(opts) {
|
|
|
17782
18035
|
|
|
17783
18036
|
// src/gallery/generate.ts
|
|
17784
18037
|
var import_node_crypto9 = require("crypto");
|
|
17785
|
-
var
|
|
17786
|
-
var GenerateRequestSchema =
|
|
17787
|
-
project:
|
|
17788
|
-
keys:
|
|
18038
|
+
var import_zod14 = require("zod");
|
|
18039
|
+
var GenerateRequestSchema = import_zod14.z.object({
|
|
18040
|
+
project: import_zod14.z.string().min(1).optional(),
|
|
18041
|
+
keys: import_zod14.z.array(import_zod14.z.string().min(1)).min(1).max(500),
|
|
17789
18042
|
/**
|
|
17790
18043
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
17791
18044
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
17792
18045
|
* the recorded bytes back over a modified file.
|
|
17793
18046
|
*/
|
|
17794
|
-
force:
|
|
18047
|
+
force: import_zod14.z.boolean().optional(),
|
|
17795
18048
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
17796
|
-
resume:
|
|
18049
|
+
resume: import_zod14.z.boolean().optional(),
|
|
17797
18050
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
17798
|
-
refresh:
|
|
18051
|
+
refresh: import_zod14.z.boolean().optional(),
|
|
17799
18052
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
17800
|
-
restore:
|
|
18053
|
+
restore: import_zod14.z.boolean().optional(),
|
|
17801
18054
|
/**
|
|
17802
18055
|
* Bring a replaced generation back: 1-based position in the record's
|
|
17803
18056
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
17804
18057
|
*/
|
|
17805
|
-
revert:
|
|
18058
|
+
revert: import_zod14.z.string().min(1).max(64).optional()
|
|
17806
18059
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
17807
18060
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
17808
18061
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -18148,19 +18401,19 @@ function createGenerateHandlers(opts) {
|
|
|
18148
18401
|
}
|
|
18149
18402
|
|
|
18150
18403
|
// src/pipeline/salvage.ts
|
|
18151
|
-
var
|
|
18152
|
-
var
|
|
18153
|
-
var
|
|
18404
|
+
var import_promises30 = require("fs/promises");
|
|
18405
|
+
var import_node_fs31 = require("fs");
|
|
18406
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
18154
18407
|
function providerClaimId(provider, objectId) {
|
|
18155
18408
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
18156
18409
|
}
|
|
18157
18410
|
async function loadClaims(lockPaths, opts = {}) {
|
|
18158
18411
|
const claimed = /* @__PURE__ */ new Set();
|
|
18159
18412
|
for (const p of lockPaths) {
|
|
18160
|
-
if (!(0,
|
|
18413
|
+
if (!(0, import_node_fs31.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
18161
18414
|
let parsed;
|
|
18162
18415
|
try {
|
|
18163
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
18416
|
+
parsed = parseLock(JSON.parse(await (0, import_promises30.readFile)(p, "utf8")));
|
|
18164
18417
|
} catch {
|
|
18165
18418
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
18166
18419
|
}
|
|
@@ -18264,20 +18517,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
18264
18517
|
return matchStyleByPattern(prompt, manifest);
|
|
18265
18518
|
}
|
|
18266
18519
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
18267
|
-
const own =
|
|
18520
|
+
const own = import_node_path36.default.resolve(ownManifestPath);
|
|
18268
18521
|
const siblingManifestPaths = [
|
|
18269
18522
|
.../* @__PURE__ */ new Set([
|
|
18270
18523
|
...workspaceManifestPaths,
|
|
18271
|
-
...claimPaths.map((c) =>
|
|
18524
|
+
...claimPaths.map((c) => import_node_path36.default.join(import_node_path36.default.dirname(import_node_path36.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
18272
18525
|
])
|
|
18273
18526
|
];
|
|
18274
18527
|
const siblings = [];
|
|
18275
18528
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
18276
|
-
if (
|
|
18277
|
-
if (!(0,
|
|
18529
|
+
if (import_node_path36.default.resolve(siblingManifestPath) === own) continue;
|
|
18530
|
+
if (!(0, import_node_fs31.existsSync)(siblingManifestPath)) continue;
|
|
18278
18531
|
try {
|
|
18279
18532
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
18280
|
-
siblings.push({ label:
|
|
18533
|
+
siblings.push({ label: import_node_path36.default.basename(import_node_path36.default.dirname(siblingManifestPath)), manifest });
|
|
18281
18534
|
} catch {
|
|
18282
18535
|
}
|
|
18283
18536
|
}
|
|
@@ -18397,8 +18650,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18397
18650
|
}
|
|
18398
18651
|
|
|
18399
18652
|
// src/pick/salvage-server.ts
|
|
18400
|
-
var
|
|
18401
|
-
var
|
|
18653
|
+
var import_promises31 = require("fs/promises");
|
|
18654
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
18402
18655
|
|
|
18403
18656
|
// src/pick/salvage-sheet.ts
|
|
18404
18657
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18564,7 +18817,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18564
18817
|
});
|
|
18565
18818
|
const html = renderSalvageSheet(orphans, {
|
|
18566
18819
|
styleId: ctx.styleId,
|
|
18567
|
-
importDir:
|
|
18820
|
+
importDir: import_node_path37.default.relative(process.cwd(), ctx.importDir) || "."
|
|
18568
18821
|
});
|
|
18569
18822
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18570
18823
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18620,10 +18873,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18620
18873
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18621
18874
|
decodePng(buf);
|
|
18622
18875
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18623
|
-
const rel =
|
|
18624
|
-
const outFile =
|
|
18625
|
-
await (0,
|
|
18626
|
-
await (0,
|
|
18876
|
+
const rel = import_node_path37.default.join("_salvaged", `${assetId}.png`);
|
|
18877
|
+
const outFile = import_node_path37.default.resolve(ctx.importDir, rel);
|
|
18878
|
+
await (0, import_promises31.mkdir)(import_node_path37.default.dirname(outFile), { recursive: true });
|
|
18879
|
+
await (0, import_promises31.writeFile)(outFile, buf);
|
|
18627
18880
|
ctx.manifest.assets[assetId] = {
|
|
18628
18881
|
prompt: orphan.prompt,
|
|
18629
18882
|
promptByStyle: {},
|
|
@@ -18655,7 +18908,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18655
18908
|
sourceUrl: durableSource,
|
|
18656
18909
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18657
18910
|
outputs: [{
|
|
18658
|
-
path: portableOutputPath(outFile,
|
|
18911
|
+
path: portableOutputPath(outFile, import_node_path37.default.dirname(ctx.manifestPath)),
|
|
18659
18912
|
sha256: sha256(buf)
|
|
18660
18913
|
}],
|
|
18661
18914
|
submittedAt: orphan.createdAt,
|
|
@@ -18679,9 +18932,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18679
18932
|
}
|
|
18680
18933
|
}
|
|
18681
18934
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18682
|
-
const raw = JSON.parse(await (0,
|
|
18935
|
+
const raw = JSON.parse(await (0, import_promises31.readFile)(ctx.manifestPath, "utf8"));
|
|
18683
18936
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18684
|
-
await (0,
|
|
18937
|
+
await (0, import_promises31.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18685
18938
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18686
18939
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18687
18940
|
return result;
|