pixelkiln 0.83.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 +712 -482
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +2 -0
- package/dist/client/gallery.js +3366 -3087
- package/dist/index.cjs +425 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +767 -71
- package/dist/index.d.ts +767 -71
- package/dist/index.js +421 -191
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +10 -6
- package/docs/REVISIONS.md +62 -13
- 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 +6 -3
- package/skills/pixelkiln/references/revisions.md +13 -7
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({
|
|
@@ -15470,15 +15604,38 @@ var NewAssetSchema = import_zod8.z.object({
|
|
|
15470
15604
|
dithering: RevisionDitheringSchema.optional(),
|
|
15471
15605
|
ditheringStrength: import_zod8.z.number().min(0).max(10).optional()
|
|
15472
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(),
|
|
15473
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(),
|
|
15474
15628
|
import_zod8.z.object({
|
|
15475
15629
|
mode: import_zod8.z.enum(["animate", "animate-pixminimax"]),
|
|
15476
15630
|
from: import_zod8.z.string().min(1),
|
|
15477
15631
|
frames: import_zod8.z.number().int().min(4).max(40).refine((n) => n % 2 === 0, "frames must be even").optional(),
|
|
15478
15632
|
fps: import_zod8.z.number().int().min(1).max(60).optional(),
|
|
15479
15633
|
enhancePrompt: import_zod8.z.boolean().optional(),
|
|
15480
|
-
|
|
15481
|
-
|
|
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()
|
|
15482
15639
|
}).strict(),
|
|
15483
15640
|
import_zod8.z.object({
|
|
15484
15641
|
mode: import_zod8.z.literal("edit-animation"),
|
|
@@ -16129,62 +16286,118 @@ async function saveProjectImage(root, request) {
|
|
|
16129
16286
|
return relative.split(import_node_path29.default.sep).join("/");
|
|
16130
16287
|
}
|
|
16131
16288
|
|
|
16132
|
-
// src/gallery/
|
|
16289
|
+
// src/gallery/image-revision.ts
|
|
16133
16290
|
var import_node_fs26 = require("fs");
|
|
16134
16291
|
var import_promises23 = require("fs/promises");
|
|
16135
16292
|
var import_node_path30 = __toESM(require("path"), 1);
|
|
16136
16293
|
var import_zod11 = require("zod");
|
|
16137
|
-
var
|
|
16138
|
-
|
|
16139
|
-
styleId: import_zod11.z.string().min(1),
|
|
16140
|
-
assetId: import_zod11.z.string().min(1),
|
|
16141
|
-
frames: import_zod11.z.number().int().min(3).max(15).optional()
|
|
16142
|
-
}).strict();
|
|
16143
|
-
var SkeletonAnimationRequestSchema = import_zod11.z.object({
|
|
16144
|
-
action: import_zod11.z.literal("create-skeleton-animation"),
|
|
16294
|
+
var InpaintRevisionRequestSchema = import_zod11.z.object({
|
|
16295
|
+
action: import_zod11.z.literal("create-inpaint-revision"),
|
|
16145
16296
|
project: import_zod11.z.string().min(1).optional(),
|
|
16146
16297
|
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16147
16298
|
styleId: import_zod11.z.string().min(1),
|
|
16148
|
-
/** The sprite
|
|
16299
|
+
/** The sprite being repainted. */
|
|
16149
16300
|
from: import_zod11.z.string().min(1),
|
|
16150
16301
|
assetId: import_zod11.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
16151
|
-
/**
|
|
16302
|
+
/** What to paint into the masked area. */
|
|
16152
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),
|
|
16153
16366
|
direction: CharacterDirectionSchema,
|
|
16154
|
-
description:
|
|
16155
|
-
skeletonTemplate:
|
|
16367
|
+
description: import_zod12.z.string().min(1).optional(),
|
|
16368
|
+
skeletonTemplate: import_zod12.z.string().min(1).optional(),
|
|
16156
16369
|
/** Manifest-relative JSON path inside the project. */
|
|
16157
|
-
keypointsFile:
|
|
16370
|
+
keypointsFile: import_zod12.z.string().min(1),
|
|
16158
16371
|
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
16159
16372
|
set: SkeletonSetSchema.optional(),
|
|
16160
16373
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
16161
|
-
overwrite:
|
|
16374
|
+
overwrite: import_zod12.z.boolean().optional()
|
|
16162
16375
|
}).strict();
|
|
16163
|
-
var SkeletonKeypointsUpdateSchema =
|
|
16164
|
-
action:
|
|
16165
|
-
project:
|
|
16166
|
-
styleId:
|
|
16167
|
-
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),
|
|
16168
16381
|
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
16169
|
-
expectedSha256:
|
|
16382
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16170
16383
|
set: SkeletonSetSchema
|
|
16171
16384
|
}).strict();
|
|
16172
16385
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
16173
|
-
const root =
|
|
16174
|
-
const file =
|
|
16175
|
-
const relative =
|
|
16176
|
-
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)) {
|
|
16177
16390
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
16178
16391
|
}
|
|
16179
|
-
const existed = (0,
|
|
16392
|
+
const existed = (0, import_node_fs27.existsSync)(file);
|
|
16180
16393
|
let previous = null;
|
|
16181
16394
|
if (request.set) {
|
|
16182
16395
|
if (existed && !request.overwrite) {
|
|
16183
16396
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
16184
16397
|
}
|
|
16185
|
-
if (existed) previous = await (0,
|
|
16186
|
-
await (0,
|
|
16187
|
-
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");
|
|
16188
16401
|
} else if (!existed) {
|
|
16189
16402
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
16190
16403
|
}
|
|
@@ -16199,7 +16412,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16199
16412
|
revision: {
|
|
16200
16413
|
mode: "animate-skeleton",
|
|
16201
16414
|
from: request.from,
|
|
16202
|
-
keypointsFile: relative.split(
|
|
16415
|
+
keypointsFile: relative.split(import_node_path31.default.sep).join("/"),
|
|
16203
16416
|
direction: request.direction,
|
|
16204
16417
|
...request.description ? { description: request.description } : {},
|
|
16205
16418
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16208,8 +16421,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16208
16421
|
});
|
|
16209
16422
|
} catch (error) {
|
|
16210
16423
|
if (request.set) {
|
|
16211
|
-
if (previous) await (0,
|
|
16212
|
-
else await (0,
|
|
16424
|
+
if (previous) await (0, import_promises24.writeFile)(file, previous);
|
|
16425
|
+
else await (0, import_promises24.rm)(file, { force: true });
|
|
16213
16426
|
}
|
|
16214
16427
|
throw error;
|
|
16215
16428
|
}
|
|
@@ -16220,64 +16433,64 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16220
16433
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16221
16434
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16222
16435
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16223
|
-
const relative =
|
|
16224
|
-
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)) {
|
|
16225
16438
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16226
16439
|
}
|
|
16227
|
-
const current = (0,
|
|
16440
|
+
const current = (0, import_node_fs27.existsSync)(file) ? sha256(await (0, import_promises24.readFile)(file)) : null;
|
|
16228
16441
|
if (current !== request.expectedSha256) {
|
|
16229
16442
|
throw Object.assign(
|
|
16230
|
-
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`),
|
|
16231
16444
|
{ status: 409 }
|
|
16232
16445
|
);
|
|
16233
16446
|
}
|
|
16234
|
-
await (0,
|
|
16235
|
-
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("/");
|
|
16236
16449
|
}
|
|
16237
16450
|
|
|
16238
16451
|
// src/gallery/edit.ts
|
|
16239
|
-
var Base64 =
|
|
16240
|
-
var HandEditRequestSchema =
|
|
16241
|
-
|
|
16242
|
-
action:
|
|
16243
|
-
project:
|
|
16244
|
-
assetId:
|
|
16245
|
-
styleId:
|
|
16246
|
-
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}$/),
|
|
16247
16460
|
/** Also launch the author's editor on the file. */
|
|
16248
|
-
open:
|
|
16461
|
+
open: import_zod13.z.boolean().optional()
|
|
16249
16462
|
}).strict(),
|
|
16250
|
-
|
|
16251
|
-
action:
|
|
16252
|
-
project:
|
|
16253
|
-
assetId:
|
|
16254
|
-
styleId:
|
|
16255
|
-
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}$/)
|
|
16256
16469
|
}).strict(),
|
|
16257
16470
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
16258
|
-
|
|
16259
|
-
action:
|
|
16260
|
-
project:
|
|
16261
|
-
assetId:
|
|
16262
|
-
styleId:
|
|
16263
|
-
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}$/),
|
|
16264
16477
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
16265
16478
|
png: Base64.min(1).optional(),
|
|
16266
16479
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
16267
|
-
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(),
|
|
16268
16481
|
/** The editor's layered project file, kept beside the edit. */
|
|
16269
16482
|
pxo: Base64.optional(),
|
|
16270
16483
|
/** Editor identity from the bridge's ready message. */
|
|
16271
|
-
editor:
|
|
16272
|
-
protocol:
|
|
16484
|
+
editor: import_zod13.z.string().min(1).max(200),
|
|
16485
|
+
protocol: import_zod13.z.number().int().positive()
|
|
16273
16486
|
}).strict()
|
|
16274
16487
|
]);
|
|
16275
16488
|
var UNDO_DEPTH = 20;
|
|
16276
|
-
var UndoRequestSchema =
|
|
16277
|
-
action:
|
|
16278
|
-
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(),
|
|
16279
16492
|
/** The manifest as the page last saw it; undo refuses a manifest someone else has changed since. */
|
|
16280
|
-
expectedSha256:
|
|
16493
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/)
|
|
16281
16494
|
}).strict();
|
|
16282
16495
|
function createGalleryEditHandler(opts) {
|
|
16283
16496
|
const log = opts.onProgress ?? (() => {
|
|
@@ -16286,7 +16499,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16286
16499
|
const history = /* @__PURE__ */ new Map();
|
|
16287
16500
|
const manifestOf = async (project) => {
|
|
16288
16501
|
try {
|
|
16289
|
-
return
|
|
16502
|
+
return import_node_path32.default.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
16290
16503
|
} catch {
|
|
16291
16504
|
return null;
|
|
16292
16505
|
}
|
|
@@ -16296,7 +16509,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16296
16509
|
if (undo.success) {
|
|
16297
16510
|
const manifestPath2 = await manifestOf(undo.data.project);
|
|
16298
16511
|
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
16299
|
-
const current = await (0,
|
|
16512
|
+
const current = await (0, import_promises25.readFile)(manifestPath2, "utf8");
|
|
16300
16513
|
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
16301
16514
|
const stack = history.get(manifestPath2) ?? [];
|
|
16302
16515
|
const last = stack[stack.length - 1];
|
|
@@ -16306,17 +16519,17 @@ function createGalleryEditHandler(opts) {
|
|
|
16306
16519
|
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
16307
16520
|
}
|
|
16308
16521
|
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
16309
|
-
await (0,
|
|
16310
|
-
await (0,
|
|
16522
|
+
await (0, import_promises25.writeFile)(tmp, last.before);
|
|
16523
|
+
await (0, import_promises25.rename)(tmp, manifestPath2);
|
|
16311
16524
|
stack.pop();
|
|
16312
|
-
log(` manifest edit undone in ${
|
|
16525
|
+
log(` manifest edit undone in ${import_node_path32.default.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
16313
16526
|
return opts.reload();
|
|
16314
16527
|
}
|
|
16315
16528
|
const manifestPath = await manifestOf(body?.project);
|
|
16316
|
-
const before = manifestPath ? await (0,
|
|
16529
|
+
const before = manifestPath ? await (0, import_promises25.readFile)(manifestPath, "utf8").catch(() => null) : null;
|
|
16317
16530
|
const build = await apply(body);
|
|
16318
16531
|
if (manifestPath && before !== null) {
|
|
16319
|
-
const after = await (0,
|
|
16532
|
+
const after = await (0, import_promises25.readFile)(manifestPath, "utf8").catch(() => null);
|
|
16320
16533
|
if (after !== null && after !== before) {
|
|
16321
16534
|
const stack = history.get(manifestPath) ?? [];
|
|
16322
16535
|
stack.push({ before, afterSha: sha256(after) });
|
|
@@ -16371,7 +16584,7 @@ function createEditApplier(opts, log) {
|
|
|
16371
16584
|
} catch (error) {
|
|
16372
16585
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16373
16586
|
}
|
|
16374
|
-
const written = await saveProjectImage(
|
|
16587
|
+
const written = await saveProjectImage(import_node_path32.default.dirname(import_node_path32.default.resolve(manifestPath2)), upload.data);
|
|
16375
16588
|
log(` image added: ${written}`);
|
|
16376
16589
|
return opts.reload();
|
|
16377
16590
|
}
|
|
@@ -16407,6 +16620,23 @@ function createEditApplier(opts, log) {
|
|
|
16407
16620
|
"invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16408
16621
|
);
|
|
16409
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
|
+
}
|
|
16410
16640
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
16411
16641
|
if (!parsed.success) {
|
|
16412
16642
|
throw new ManifestEditError(
|
|
@@ -16421,7 +16651,7 @@ function createEditApplier(opts, log) {
|
|
|
16421
16651
|
}
|
|
16422
16652
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16423
16653
|
if (result.changed) {
|
|
16424
|
-
const relative =
|
|
16654
|
+
const relative = import_node_path32.default.relative(process.cwd(), result.manifestPath);
|
|
16425
16655
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16426
16656
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16427
16657
|
}
|
|
@@ -16449,7 +16679,7 @@ function describeEdit(edit) {
|
|
|
16449
16679
|
// src/gallery/snapshot.ts
|
|
16450
16680
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16451
16681
|
function galleryMediaId(absolutePath) {
|
|
16452
|
-
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);
|
|
16453
16683
|
}
|
|
16454
16684
|
function galleryMediaRoute(id) {
|
|
16455
16685
|
return `/media/${id}`;
|
|
@@ -16468,9 +16698,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16468
16698
|
return (entry.history ?? []).map((generation, i) => {
|
|
16469
16699
|
const outputs = generation.outputs.map((output) => {
|
|
16470
16700
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16471
|
-
const file =
|
|
16701
|
+
const file = import_node_path33.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16472
16702
|
let url = null;
|
|
16473
|
-
if ((0,
|
|
16703
|
+
if ((0, import_node_fs28.existsSync)(file)) {
|
|
16474
16704
|
const id = galleryMediaId(file);
|
|
16475
16705
|
media.set(id, { path: file, contentType: mediaType });
|
|
16476
16706
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16502,7 +16732,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16502
16732
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16503
16733
|
async function samePixels(a, b) {
|
|
16504
16734
|
try {
|
|
16505
|
-
const [left, right] = await Promise.all([(0,
|
|
16735
|
+
const [left, right] = await Promise.all([(0, import_promises26.readFile)(a), (0, import_promises26.readFile)(b)]);
|
|
16506
16736
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16507
16737
|
const x = decodePng(left);
|
|
16508
16738
|
const y = decodePng(right);
|
|
@@ -16568,7 +16798,7 @@ function describeCharacter(spec, entry) {
|
|
|
16568
16798
|
}
|
|
16569
16799
|
async function fileInfo(absolutePath) {
|
|
16570
16800
|
try {
|
|
16571
|
-
const info = await (0,
|
|
16801
|
+
const info = await (0, import_promises26.stat)(absolutePath);
|
|
16572
16802
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
16573
16803
|
} catch {
|
|
16574
16804
|
return null;
|
|
@@ -16600,10 +16830,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16600
16830
|
async function describeQuality(media, root, inspection) {
|
|
16601
16831
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16602
16832
|
const outputs = await Promise.all(
|
|
16603
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16833
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path33.default.resolve(root, file)))
|
|
16604
16834
|
);
|
|
16605
|
-
const recordPath =
|
|
16606
|
-
const recordExists = (0,
|
|
16835
|
+
const recordPath = import_node_path33.default.resolve(root, inspection.record);
|
|
16836
|
+
const recordExists = (0, import_node_fs28.existsSync)(recordPath);
|
|
16607
16837
|
let options = null;
|
|
16608
16838
|
let check = null;
|
|
16609
16839
|
if (recordExists) {
|
|
@@ -16622,7 +16852,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16622
16852
|
return {
|
|
16623
16853
|
state: inspection.state,
|
|
16624
16854
|
reason: inspection.reason,
|
|
16625
|
-
output: portableOutputPath(
|
|
16855
|
+
output: portableOutputPath(import_node_path33.default.resolve(root, inspection.output), root),
|
|
16626
16856
|
record: portableOutputPath(recordPath, root),
|
|
16627
16857
|
recordExists,
|
|
16628
16858
|
outputs,
|
|
@@ -16660,9 +16890,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
16660
16890
|
const media = /* @__PURE__ */ new Map();
|
|
16661
16891
|
const plan = await buildPlan(specs, lock);
|
|
16662
16892
|
const items = [];
|
|
16663
|
-
const manifestText = await (0,
|
|
16893
|
+
const manifestText = await (0, import_promises26.readFile)(loaded.path, "utf8");
|
|
16664
16894
|
const manifestSha256 = sha256(manifestText);
|
|
16665
|
-
const cacheDir =
|
|
16895
|
+
const cacheDir = import_node_path33.default.resolve(import_node_path33.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16666
16896
|
let rawStyles = {};
|
|
16667
16897
|
try {
|
|
16668
16898
|
const raw = JSON.parse(manifestText);
|
|
@@ -16686,12 +16916,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
16686
16916
|
)
|
|
16687
16917
|
);
|
|
16688
16918
|
if (spec.source) {
|
|
16689
|
-
const editPath =
|
|
16919
|
+
const editPath = import_node_path33.default.resolve(root, spec.source);
|
|
16690
16920
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16691
16921
|
const editShas = await Promise.all(
|
|
16692
16922
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
16693
16923
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
16694
|
-
return { file, sha256: (0,
|
|
16924
|
+
return { file, sha256: (0, import_node_fs28.existsSync)(file) ? await sha256File(file) : null };
|
|
16695
16925
|
})
|
|
16696
16926
|
);
|
|
16697
16927
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -16699,7 +16929,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16699
16929
|
const companion = await readHandEditCompanion(editPath);
|
|
16700
16930
|
if (companion) {
|
|
16701
16931
|
const projectPath = handEditProjectPath(editPath);
|
|
16702
|
-
const project = companion.project && (0,
|
|
16932
|
+
const project = companion.project && (0, import_node_fs28.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
16703
16933
|
let projectUrl = null;
|
|
16704
16934
|
if (project) {
|
|
16705
16935
|
const id = galleryMediaId(projectPath);
|
|
@@ -16735,8 +16965,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16735
16965
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16736
16966
|
}
|
|
16737
16967
|
} else if (spec.source) {
|
|
16738
|
-
outputs = [await describeOutput(media, root,
|
|
16739
|
-
} 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)) {
|
|
16740
16970
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16741
16971
|
} else {
|
|
16742
16972
|
outputs = [];
|
|
@@ -16939,7 +17169,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16939
17169
|
id: loaded.manifest.name,
|
|
16940
17170
|
name: loaded.manifest.name,
|
|
16941
17171
|
manifest: loaded.path,
|
|
16942
|
-
lock:
|
|
17172
|
+
lock: import_node_path33.default.resolve(opts.lockPath),
|
|
16943
17173
|
root,
|
|
16944
17174
|
provider: loaded.manifest.provider,
|
|
16945
17175
|
account: null,
|
|
@@ -16965,7 +17195,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16965
17195
|
return { snapshot, media };
|
|
16966
17196
|
}
|
|
16967
17197
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16968
|
-
const dir =
|
|
17198
|
+
const dir = import_node_path33.default.dirname(import_node_path33.default.resolve(opts.workspacePath));
|
|
16969
17199
|
const media = /* @__PURE__ */ new Map();
|
|
16970
17200
|
const projects = [];
|
|
16971
17201
|
const items = [];
|
|
@@ -17008,11 +17238,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17008
17238
|
id: project.id,
|
|
17009
17239
|
name: loaded.manifest.name,
|
|
17010
17240
|
manifest: manifestPath,
|
|
17011
|
-
lock:
|
|
17241
|
+
lock: import_node_path33.default.resolve(lockPath),
|
|
17012
17242
|
root: loaded.root,
|
|
17013
17243
|
provider: loaded.manifest.provider,
|
|
17014
17244
|
account: project.account ?? null,
|
|
17015
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
17245
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises26.readFile)(manifestPath, "utf8")),
|
|
17016
17246
|
historyLimit: historyLimit(loaded.manifest),
|
|
17017
17247
|
entries: Object.keys(lock.entries).length,
|
|
17018
17248
|
items: projectItems.length,
|
|
@@ -17024,8 +17254,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17024
17254
|
id: project.id,
|
|
17025
17255
|
name: project.id,
|
|
17026
17256
|
manifest: manifestPath,
|
|
17027
|
-
lock:
|
|
17028
|
-
root:
|
|
17257
|
+
lock: import_node_path33.default.resolve(lockPath),
|
|
17258
|
+
root: import_node_path33.default.dirname(manifestPath),
|
|
17029
17259
|
provider: project.provider,
|
|
17030
17260
|
account: project.account ?? null,
|
|
17031
17261
|
manifestSha256: null,
|
|
@@ -17052,7 +17282,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17052
17282
|
version: 1,
|
|
17053
17283
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
17054
17284
|
project: null,
|
|
17055
|
-
workspace: { path:
|
|
17285
|
+
workspace: { path: import_node_path33.default.resolve(opts.workspacePath), dir, projects },
|
|
17056
17286
|
filter,
|
|
17057
17287
|
totals: {
|
|
17058
17288
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -17069,7 +17299,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17069
17299
|
}
|
|
17070
17300
|
|
|
17071
17301
|
// src/gallery/page.ts
|
|
17072
|
-
var
|
|
17302
|
+
var import_node_fs29 = require("fs");
|
|
17073
17303
|
|
|
17074
17304
|
// src/gallery/client-bundle.ts
|
|
17075
17305
|
var import_node_module = require("module");
|
|
@@ -17109,8 +17339,8 @@ var client;
|
|
|
17109
17339
|
function clientAssets() {
|
|
17110
17340
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
17111
17341
|
client ??= {
|
|
17112
|
-
css: (0,
|
|
17113
|
-
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()
|
|
17114
17344
|
};
|
|
17115
17345
|
return client;
|
|
17116
17346
|
}
|
|
@@ -17253,10 +17483,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
17253
17483
|
|
|
17254
17484
|
// src/editor/install.ts
|
|
17255
17485
|
var import_node_crypto7 = require("crypto");
|
|
17256
|
-
var
|
|
17257
|
-
var
|
|
17486
|
+
var import_node_fs30 = require("fs");
|
|
17487
|
+
var import_promises27 = require("fs/promises");
|
|
17258
17488
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
17259
|
-
var
|
|
17489
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
17260
17490
|
var EditorInstallError = class extends Error {
|
|
17261
17491
|
constructor(message6, file) {
|
|
17262
17492
|
super(message6);
|
|
@@ -17267,15 +17497,15 @@ var EditorInstallError = class extends Error {
|
|
|
17267
17497
|
};
|
|
17268
17498
|
function editorToolsRoot() {
|
|
17269
17499
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17270
|
-
if (override) return
|
|
17271
|
-
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");
|
|
17272
17502
|
if (process.platform === "win32") {
|
|
17273
|
-
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");
|
|
17274
17504
|
}
|
|
17275
|
-
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");
|
|
17276
17506
|
}
|
|
17277
17507
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17278
|
-
return
|
|
17508
|
+
return import_node_path34.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17279
17509
|
}
|
|
17280
17510
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17281
17511
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17285,7 +17515,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
17285
17515
|
}
|
|
17286
17516
|
async function sha256Of(file) {
|
|
17287
17517
|
try {
|
|
17288
|
-
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");
|
|
17289
17519
|
} catch {
|
|
17290
17520
|
return null;
|
|
17291
17521
|
}
|
|
@@ -17298,8 +17528,8 @@ async function editorStatus(opts = {}) {
|
|
|
17298
17528
|
let totalBytes = 0;
|
|
17299
17529
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17300
17530
|
totalBytes += expected.bytes;
|
|
17301
|
-
const file =
|
|
17302
|
-
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;
|
|
17303
17533
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17304
17534
|
installedBytes += expected.bytes;
|
|
17305
17535
|
} else {
|
|
@@ -17328,7 +17558,7 @@ async function installEditor(opts = {}) {
|
|
|
17328
17558
|
);
|
|
17329
17559
|
}
|
|
17330
17560
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
17331
|
-
await (0,
|
|
17561
|
+
await (0, import_promises27.mkdir)(dir, { recursive: true });
|
|
17332
17562
|
const downloaded = [];
|
|
17333
17563
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
17334
17564
|
let fetchedBytes = before.installedBytes;
|
|
@@ -17356,13 +17586,13 @@ async function installEditor(opts = {}) {
|
|
|
17356
17586
|
if (digest2 !== expected.sha256) {
|
|
17357
17587
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17358
17588
|
}
|
|
17359
|
-
const final =
|
|
17589
|
+
const final = import_node_path34.default.join(dir, name);
|
|
17360
17590
|
const tmp = `${final}.${process.pid}.part`;
|
|
17361
17591
|
try {
|
|
17362
|
-
await (0,
|
|
17363
|
-
await (0,
|
|
17592
|
+
await (0, import_promises27.writeFile)(tmp, bytes);
|
|
17593
|
+
await (0, import_promises27.rename)(tmp, final);
|
|
17364
17594
|
} finally {
|
|
17365
|
-
await (0,
|
|
17595
|
+
await (0, import_promises27.rm)(tmp, { force: true });
|
|
17366
17596
|
}
|
|
17367
17597
|
fetchedBytes += bytes.length;
|
|
17368
17598
|
downloaded.push(name);
|
|
@@ -17402,12 +17632,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17402
17632
|
".json": "application/json; charset=utf-8"
|
|
17403
17633
|
};
|
|
17404
17634
|
function editorContentType(name) {
|
|
17405
|
-
return EDITOR_CONTENT_TYPES[
|
|
17635
|
+
return EDITOR_CONTENT_TYPES[import_node_path34.default.extname(name).toLowerCase()] ?? null;
|
|
17406
17636
|
}
|
|
17407
17637
|
|
|
17408
17638
|
// src/gallery/editor.ts
|
|
17409
|
-
var
|
|
17410
|
-
var
|
|
17639
|
+
var import_promises28 = require("fs/promises");
|
|
17640
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
17411
17641
|
function editorRoute(release, name = "index.html") {
|
|
17412
17642
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17413
17643
|
}
|
|
@@ -17480,7 +17710,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17480
17710
|
if (!status.installed) return null;
|
|
17481
17711
|
let bytes;
|
|
17482
17712
|
try {
|
|
17483
|
-
bytes = await (0,
|
|
17713
|
+
bytes = await (0, import_promises28.readFile)(import_node_path35.default.join(dir, name));
|
|
17484
17714
|
} catch {
|
|
17485
17715
|
verified = null;
|
|
17486
17716
|
return null;
|
|
@@ -17497,7 +17727,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17497
17727
|
// src/gallery/server.ts
|
|
17498
17728
|
var import_node_crypto8 = require("crypto");
|
|
17499
17729
|
var import_node_http2 = require("http");
|
|
17500
|
-
var
|
|
17730
|
+
var import_promises29 = require("fs/promises");
|
|
17501
17731
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17502
17732
|
var EDITOR_CSP = [
|
|
17503
17733
|
"default-src 'self'",
|
|
@@ -17678,7 +17908,7 @@ async function serveGallery(opts) {
|
|
|
17678
17908
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17679
17909
|
if (!asset) return fail(404, "no such review asset");
|
|
17680
17910
|
try {
|
|
17681
|
-
const bytes = await (0,
|
|
17911
|
+
const bytes = await (0, import_promises29.readFile)(asset.path);
|
|
17682
17912
|
res.writeHead(200, {
|
|
17683
17913
|
"Content-Type": asset.contentType,
|
|
17684
17914
|
"Cache-Control": "no-store",
|
|
@@ -17761,7 +17991,7 @@ async function serveGallery(opts) {
|
|
|
17761
17991
|
if (!asset) return fail(404, "no such gallery media");
|
|
17762
17992
|
let bytes;
|
|
17763
17993
|
try {
|
|
17764
|
-
bytes = await (0,
|
|
17994
|
+
bytes = await (0, import_promises29.readFile)(asset.path);
|
|
17765
17995
|
} catch {
|
|
17766
17996
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17767
17997
|
}
|
|
@@ -17805,27 +18035,27 @@ async function serveGallery(opts) {
|
|
|
17805
18035
|
|
|
17806
18036
|
// src/gallery/generate.ts
|
|
17807
18037
|
var import_node_crypto9 = require("crypto");
|
|
17808
|
-
var
|
|
17809
|
-
var GenerateRequestSchema =
|
|
17810
|
-
project:
|
|
17811
|
-
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),
|
|
17812
18042
|
/**
|
|
17813
18043
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
17814
18044
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
17815
18045
|
* the recorded bytes back over a modified file.
|
|
17816
18046
|
*/
|
|
17817
|
-
force:
|
|
18047
|
+
force: import_zod14.z.boolean().optional(),
|
|
17818
18048
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
17819
|
-
resume:
|
|
18049
|
+
resume: import_zod14.z.boolean().optional(),
|
|
17820
18050
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
17821
|
-
refresh:
|
|
18051
|
+
refresh: import_zod14.z.boolean().optional(),
|
|
17822
18052
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
17823
|
-
restore:
|
|
18053
|
+
restore: import_zod14.z.boolean().optional(),
|
|
17824
18054
|
/**
|
|
17825
18055
|
* Bring a replaced generation back: 1-based position in the record's
|
|
17826
18056
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
17827
18057
|
*/
|
|
17828
|
-
revert:
|
|
18058
|
+
revert: import_zod14.z.string().min(1).max(64).optional()
|
|
17829
18059
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
17830
18060
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
17831
18061
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -18171,19 +18401,19 @@ function createGenerateHandlers(opts) {
|
|
|
18171
18401
|
}
|
|
18172
18402
|
|
|
18173
18403
|
// src/pipeline/salvage.ts
|
|
18174
|
-
var
|
|
18175
|
-
var
|
|
18176
|
-
var
|
|
18404
|
+
var import_promises30 = require("fs/promises");
|
|
18405
|
+
var import_node_fs31 = require("fs");
|
|
18406
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
18177
18407
|
function providerClaimId(provider, objectId) {
|
|
18178
18408
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
18179
18409
|
}
|
|
18180
18410
|
async function loadClaims(lockPaths, opts = {}) {
|
|
18181
18411
|
const claimed = /* @__PURE__ */ new Set();
|
|
18182
18412
|
for (const p of lockPaths) {
|
|
18183
|
-
if (!(0,
|
|
18413
|
+
if (!(0, import_node_fs31.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
18184
18414
|
let parsed;
|
|
18185
18415
|
try {
|
|
18186
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
18416
|
+
parsed = parseLock(JSON.parse(await (0, import_promises30.readFile)(p, "utf8")));
|
|
18187
18417
|
} catch {
|
|
18188
18418
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
18189
18419
|
}
|
|
@@ -18287,20 +18517,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
18287
18517
|
return matchStyleByPattern(prompt, manifest);
|
|
18288
18518
|
}
|
|
18289
18519
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
18290
|
-
const own =
|
|
18520
|
+
const own = import_node_path36.default.resolve(ownManifestPath);
|
|
18291
18521
|
const siblingManifestPaths = [
|
|
18292
18522
|
.../* @__PURE__ */ new Set([
|
|
18293
18523
|
...workspaceManifestPaths,
|
|
18294
|
-
...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"))
|
|
18295
18525
|
])
|
|
18296
18526
|
];
|
|
18297
18527
|
const siblings = [];
|
|
18298
18528
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
18299
|
-
if (
|
|
18300
|
-
if (!(0,
|
|
18529
|
+
if (import_node_path36.default.resolve(siblingManifestPath) === own) continue;
|
|
18530
|
+
if (!(0, import_node_fs31.existsSync)(siblingManifestPath)) continue;
|
|
18301
18531
|
try {
|
|
18302
18532
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
18303
|
-
siblings.push({ label:
|
|
18533
|
+
siblings.push({ label: import_node_path36.default.basename(import_node_path36.default.dirname(siblingManifestPath)), manifest });
|
|
18304
18534
|
} catch {
|
|
18305
18535
|
}
|
|
18306
18536
|
}
|
|
@@ -18420,8 +18650,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18420
18650
|
}
|
|
18421
18651
|
|
|
18422
18652
|
// src/pick/salvage-server.ts
|
|
18423
|
-
var
|
|
18424
|
-
var
|
|
18653
|
+
var import_promises31 = require("fs/promises");
|
|
18654
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
18425
18655
|
|
|
18426
18656
|
// src/pick/salvage-sheet.ts
|
|
18427
18657
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18587,7 +18817,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18587
18817
|
});
|
|
18588
18818
|
const html = renderSalvageSheet(orphans, {
|
|
18589
18819
|
styleId: ctx.styleId,
|
|
18590
|
-
importDir:
|
|
18820
|
+
importDir: import_node_path37.default.relative(process.cwd(), ctx.importDir) || "."
|
|
18591
18821
|
});
|
|
18592
18822
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18593
18823
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18643,10 +18873,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18643
18873
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18644
18874
|
decodePng(buf);
|
|
18645
18875
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18646
|
-
const rel =
|
|
18647
|
-
const outFile =
|
|
18648
|
-
await (0,
|
|
18649
|
-
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);
|
|
18650
18880
|
ctx.manifest.assets[assetId] = {
|
|
18651
18881
|
prompt: orphan.prompt,
|
|
18652
18882
|
promptByStyle: {},
|
|
@@ -18678,7 +18908,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18678
18908
|
sourceUrl: durableSource,
|
|
18679
18909
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18680
18910
|
outputs: [{
|
|
18681
|
-
path: portableOutputPath(outFile,
|
|
18911
|
+
path: portableOutputPath(outFile, import_node_path37.default.dirname(ctx.manifestPath)),
|
|
18682
18912
|
sha256: sha256(buf)
|
|
18683
18913
|
}],
|
|
18684
18914
|
submittedAt: orphan.createdAt,
|
|
@@ -18702,9 +18932,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18702
18932
|
}
|
|
18703
18933
|
}
|
|
18704
18934
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18705
|
-
const raw = JSON.parse(await (0,
|
|
18935
|
+
const raw = JSON.parse(await (0, import_promises31.readFile)(ctx.manifestPath, "utf8"));
|
|
18706
18936
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18707
|
-
await (0,
|
|
18937
|
+
await (0, import_promises31.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18708
18938
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18709
18939
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18710
18940
|
return result;
|